From 58dad190a1d36aa9cd4c91e8b3485c1018a5c09f Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Thu, 16 Jul 2020 15:05:24 +0200 Subject: [PATCH 01/15] Refactor pmctl into new portmaster-start --- cmds/portmaster-start/.gitignore | 6 + {pmctl => cmds/portmaster-start}/build | 0 .../portmaster-start}/console_default.go | 0 .../portmaster-start}/console_windows.go | 0 .../portmaster-start}/install_windows.go | 37 +- {pmctl => cmds/portmaster-start}/lock.go | 8 +- {pmctl => cmds/portmaster-start}/logs.go | 73 ++-- cmds/portmaster-start/main.go | 221 ++++++++++ {pmctl => cmds/portmaster-start}/pack | 20 +- cmds/portmaster-start/run.go | 384 +++++++++++++++++ .../portmaster-start}/service_windows.go | 9 +- cmds/portmaster-start/show.go | 41 ++ .../portmaster-start/shutdown.go | 22 +- cmds/portmaster-start/snoretoast_windows.go | 14 + {pmctl => cmds/portmaster-start}/update.go | 4 +- cmds/portmaster-start/version.go | 79 ++++ pmctl/.gitignore | 6 - pmctl/main.go | 227 ---------- pmctl/run.go | 406 ------------------ pmctl/show.go | 90 ---- pmctl/snoretoast_windows.go | 40 -- 21 files changed, 819 insertions(+), 868 deletions(-) create mode 100644 cmds/portmaster-start/.gitignore rename {pmctl => cmds/portmaster-start}/build (100%) rename {pmctl => cmds/portmaster-start}/console_default.go (100%) rename {pmctl => cmds/portmaster-start}/console_windows.go (100%) rename {pmctl => cmds/portmaster-start}/install_windows.go (86%) rename {pmctl => cmds/portmaster-start}/lock.go (84%) rename {pmctl => cmds/portmaster-start}/logs.go (59%) create mode 100644 cmds/portmaster-start/main.go rename {pmctl => cmds/portmaster-start}/pack (76%) create mode 100644 cmds/portmaster-start/run.go rename {pmctl => cmds/portmaster-start}/service_windows.go (94%) create mode 100644 cmds/portmaster-start/show.go rename pmctl/service.go => cmds/portmaster-start/shutdown.go (50%) create mode 100644 cmds/portmaster-start/snoretoast_windows.go rename {pmctl => cmds/portmaster-start}/update.go (92%) create mode 100644 cmds/portmaster-start/version.go delete mode 100644 pmctl/.gitignore delete mode 100644 pmctl/main.go delete mode 100644 pmctl/run.go delete mode 100644 pmctl/show.go delete mode 100644 pmctl/snoretoast_windows.go diff --git a/cmds/portmaster-start/.gitignore b/cmds/portmaster-start/.gitignore new file mode 100644 index 00000000..e3db7ed6 --- /dev/null +++ b/cmds/portmaster-start/.gitignore @@ -0,0 +1,6 @@ +# binaries +portmaster-start +portmaster-start.exe + +# test dir +test diff --git a/pmctl/build b/cmds/portmaster-start/build similarity index 100% rename from pmctl/build rename to cmds/portmaster-start/build diff --git a/pmctl/console_default.go b/cmds/portmaster-start/console_default.go similarity index 100% rename from pmctl/console_default.go rename to cmds/portmaster-start/console_default.go diff --git a/pmctl/console_windows.go b/cmds/portmaster-start/console_windows.go similarity index 100% rename from pmctl/console_windows.go rename to cmds/portmaster-start/console_windows.go diff --git a/pmctl/install_windows.go b/cmds/portmaster-start/install_windows.go similarity index 86% rename from pmctl/install_windows.go rename to cmds/portmaster-start/install_windows.go index 3446c493..c0728ad8 100644 --- a/pmctl/install_windows.go +++ b/cmds/portmaster-start/install_windows.go @@ -19,9 +19,7 @@ import ( "golang.org/x/sys/windows/svc/mgr" ) -const ( - exeSuffix = ".exe" -) +const exeSuffix = ".exe" func init() { rootCmd.AddCommand(installCmd) @@ -57,39 +55,18 @@ var uninstallService = &cobra.Command{ RunE: uninstallWindowsService, } -func getExePath() (string, error) { - // get own filepath - prog := os.Args[0] - p, err := filepath.Abs(prog) +func getAbsBinaryPath() (string, error) { + p, err := filepath.Abs(os.Args[0]) if err != nil { return "", err } - // check if the path is valid - fi, err := os.Stat(p) - if err == nil { - if !fi.Mode().IsDir() { - return p, nil - } - err = fmt.Errorf("%s is directory", p) - } - // check if we have a .exe extension, add and check if not - if filepath.Ext(p) == "" { - p += exeSuffix - fi, err = os.Stat(p) - if err == nil { - if !fi.Mode().IsDir() { - return p, nil - } - err = fmt.Errorf("%s is directory", p) - } - } - return "", err + + return p, nil } func getServiceExecCommand(exePath string, escape bool) []string { return []string{ maybeEscape(exePath, escape), - "run", "core-service", "--data", maybeEscape(dataRoot.Path, escape), @@ -126,7 +103,7 @@ func getRecoveryActions() (recoveryActions []mgr.RecoveryAction, resetPeriod uin func installWindowsService(cmd *cobra.Command, args []string) error { // get exe path - exePath, err := getExePath() + exePath, err := getAbsBinaryPath() if err != nil { return fmt.Errorf("failed to get exe path: %s", err) } @@ -180,7 +157,7 @@ func uninstallWindowsService(cmd *cobra.Command, args []string) error { if err != nil { return err } - defer m.Disconnect() //nolint:errcheck // TODO + defer m.Disconnect() //nolint:errcheck // we don't care if we failed to disconnect from the service manager, we're quitting anyway. // open service s, err := m.OpenService(serviceName) diff --git a/pmctl/lock.go b/cmds/portmaster-start/lock.go similarity index 84% rename from pmctl/lock.go rename to cmds/portmaster-start/lock.go index 8117109c..7d7af1bc 100644 --- a/pmctl/lock.go +++ b/cmds/portmaster-start/lock.go @@ -35,13 +35,7 @@ func checkAndCreateInstanceLock(name string) (pid int32, err error) { // check if process exists p, err := processInfo.NewProcess(int32(parsedPid)) if err == nil { - // TODO: remove this workaround as soon as NewProcess really returns an error on windows when the process does not exist - // Issue: https://github.com/shirou/gopsutil/issues/729 - _, err = p.Name() - if err == nil { - // process exists - return p.Pid, nil - } + return p.Pid, nil } // else create new lock diff --git a/pmctl/logs.go b/cmds/portmaster-start/logs.go similarity index 59% rename from pmctl/logs.go rename to cmds/portmaster-start/logs.go index 4f2c623b..2a13e097 100644 --- a/pmctl/logs.go +++ b/cmds/portmaster-start/logs.go @@ -35,7 +35,7 @@ func initializeLogFile(logFilePath string, identifier string, version string) *o metaSection, err := dsd.Dump(meta, dsd.JSON) if err != nil { log.Printf("failed to serialize header for log file %s: %s\n", logFilePath, err) - finalizeLogFile(logFile, logFilePath) + finalizeLogFile(logFile) return nil } c.AppendAsBlock(metaSection) @@ -46,14 +46,16 @@ func initializeLogFile(logFilePath string, identifier string, version string) *o _, err = logFile.Write(c.CompileData()) if err != nil { log.Printf("failed to write header for log file %s: %s\n", logFilePath, err) - finalizeLogFile(logFile, logFilePath) + finalizeLogFile(logFile) return nil } return logFile } -func finalizeLogFile(logFile *os.File, logFilePath string) { +func finalizeLogFile(logFile *os.File) { + logFilePath := logFile.Name() + err := logFile.Close() if err != nil { log.Printf("failed to close log file %s: %s\n", logFilePath, err) @@ -61,28 +63,38 @@ func finalizeLogFile(logFile *os.File, logFilePath string) { // check file size stat, err := os.Stat(logFilePath) - if err == nil { - // delete if file is smaller than - if stat.Size() < 200 { // header + info is about 150 bytes - err := os.Remove(logFilePath) - if err != nil { - log.Printf("failed to delete empty log file %s: %s\n", logFilePath, err) - } - } + if err != nil { + return + } + + // delete if file is smaller than + if stat.Size() >= 200 { // header + info is about 150 bytes + return + } + + if err := os.Remove(logFilePath); err != nil { + log.Printf("failed to delete empty log file %s: %s\n", logFilePath, err) } } -func initControlLogFile() *os.File { +func getLogFile(options *Options, version, ext string) *os.File { // check logging dir - logFileBasePath := filepath.Join(logsRoot.Path, "control") + logFileBasePath := filepath.Join(logsRoot.Path, options.ShortIdentifier) err := logsRoot.EnsureAbsPath(logFileBasePath) if err != nil { log.Printf("failed to check/create log file folder %s: %s\n", logFileBasePath, err) } // open log file - logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.log", time.Now().UTC().Format("2006-01-02-15-04-05"))) - return initializeLogFile(logFilePath, "control/portmaster-control", info.Version()) + logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s%s", time.Now().UTC().Format("2006-01-02-15-04-05"), ext)) + return initializeLogFile(logFilePath, options.Identifier, version) +} + +func getPmStartLogFile(ext string) *os.File { + return getLogFile(&Options{ + ShortIdentifier: "start", + Identifier: "start/portmaster-start", + }, info.Version(), ext) } //nolint:deadcode,unused // false positive on linux, currently used by windows only @@ -92,44 +104,25 @@ func logControlError(cErr error) { return } - // check logging dir - logFileBasePath := filepath.Join(logsRoot.Path, "control") - err := logsRoot.EnsureAbsPath(logFileBasePath) - if err != nil { - log.Printf("failed to check/create log file folder %s: %s\n", logFileBasePath, err) - } - - // open log file - logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.error.log", time.Now().UTC().Format("2006-01-02-15-04-05"))) - errorFile := initializeLogFile(logFilePath, "control/portmaster-control", info.Version()) + errorFile := getPmStartLogFile(".error.log") if errorFile == nil { return } + defer errorFile.Close() - // write error and close fmt.Fprintln(errorFile, cErr.Error()) - errorFile.Close() } //nolint:deadcode,unused // TODO func logControlStack() { - // check logging dir - logFileBasePath := filepath.Join(logsRoot.Path, "control") - err := logsRoot.EnsureAbsPath(logFileBasePath) - if err != nil { - log.Printf("failed to check/create log file folder %s: %s\n", logFileBasePath, err) - } - - // open log file - logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.stack.log", time.Now().UTC().Format("2006-01-02-15-04-05"))) - errorFile := initializeLogFile(logFilePath, "control/portmaster-control", info.Version()) - if errorFile == nil { + fp := getPmStartLogFile(".stack.log") + if fp == nil { return } + defer fp.Close() // write error and close - _ = pprof.Lookup("goroutine").WriteTo(errorFile, 2) - errorFile.Close() + _ = pprof.Lookup("goroutine").WriteTo(fp, 2) } //nolint:deadcode,unused // false positive on linux, currently used by windows only diff --git a/cmds/portmaster-start/main.go b/cmds/portmaster-start/main.go new file mode 100644 index 00000000..52ef057a --- /dev/null +++ b/cmds/portmaster-start/main.go @@ -0,0 +1,221 @@ +package main + +import ( + "context" + "errors" + "fmt" + "log" + "os" + "os/signal" + "strings" + "syscall" + + "github.com/safing/portbase/dataroot" + "github.com/safing/portbase/info" + portlog "github.com/safing/portbase/log" + "github.com/safing/portbase/updater" + "github.com/safing/portbase/utils" + + "github.com/spf13/cobra" +) + +var ( + dataDir string + maxRetries int + dataRoot *utils.DirStructure + logsRoot *utils.DirStructure + + // create registry + registry = &updater.ResourceRegistry{ + Name: "updates", + UpdateURLs: []string{ + "https://updates.safing.io", + }, + Beta: false, + DevMode: false, + Online: true, // is disabled later based on command + } + + rootCmd = &cobra.Command{ + Use: "portmaster-start", + Short: "Start Portmaster components", + PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { + + if err := configureDataRoot(); err != nil { + return err + } + + if err := configureLogging(); err != nil { + return err + } + + return nil + }, + SilenceUsage: true, + } +) + +func init() { + // Let cobra ignore if we are running as "GUI" or not + cobra.MousetrapHelpText = "" + + flags := rootCmd.PersistentFlags() + { + flags.StringVar(&dataDir, "data", "", "Configures the data directory. Alternatively, this can also be set via the environment variable PORTMASTER_DATA.") + flags.IntVar(&maxRetries, "max-retries", 5, "Maximum number of retries when starting a Portmaster component") + flags.BoolVar(&stdinSignals, "input-signals", false, "Emulate signals using stdid.") + _ = rootCmd.MarkPersistentFlagDirname("data") + _ = flags.MarkHidden("input-signals") + } +} + +func main() { + cobra.OnInitialize(initCobra) + + // set meta info + info.Set("Portmaster Start", "0.3.5", "AGPLv3", false) + + // for debugging + // log.Start() + // log.SetLogLevel(log.TraceLevel) + // go func() { + // time.Sleep(3 * time.Second) + // pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) + // os.Exit(1) + // }() + + // catch interrupt for clean shutdown + signalCh := make(chan os.Signal, 2) + signal.Notify( + signalCh, + os.Interrupt, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + + // start root command + go func() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } + os.Exit(0) + }() + + // for debugging windows service (no stdout/err) + // go func() { + // time.Sleep(10 * time.Second) + // // initiateShutdown(nil) + // // logControlStack() + // }() + + // wait for signals + for sig := range signalCh { + if childIsRunning.IsSet() { + log.Printf("got %s signal (ignoring), waiting for child to exit...\n", sig) + continue + } + + log.Printf("got %s signal, exiting... (not executing anything)\n", sig) + os.Exit(0) + } +} + +func initCobra() { + // check if we are running in a console (try to attach to parent console if available) + var err error + runningInConsole, err = attachToParentConsole() + if err != nil { + log.Fatalf("failed to attach to parent console: %s\n", err) + } + + // check if meta info is ok + err = info.CheckVersion() + if err != nil { + log.Fatalf("compile error: please compile using the provided build script") + } + + // set up logging + log.SetFlags(log.Ldate | log.Ltime | log.LUTC) + log.SetPrefix("[control] ") + log.SetOutput(os.Stdout) + + // not using portbase logger + portlog.SetLogLevel(portlog.CriticalLevel) +} + +func configureDataRoot() error { + // The data directory is not + // check for environment variable + // PORTMASTER_DATA + if dataDir == "" { + dataDir = os.Getenv("PORTMASTER_DATA") + } + + // check data dir + if dataDir == "" { + return errors.New("please set the data directory using --data=/path/to/data/dir") + } + + // remove redundant escape characters and quotes + dataDir = strings.Trim(dataDir, `\"`) + // initialize dataroot + err := dataroot.Initialize(dataDir, 0755) + if err != nil { + return fmt.Errorf("failed to initialize data root: %s", err) + } + dataRoot = dataroot.Root() + + // initialize registry + err = registry.Initialize(dataRoot.ChildDir("updates", 0755)) + if err != nil { + return err + } + + registry.AddIndex(updater.Index{ + Path: "stable.json", + Stable: true, + Beta: false, + }) + + // TODO: enable loading beta versions + // registry.AddIndex(updater.Index{ + // Path: "beta.json", + // Stable: false, + // Beta: true, + // }) + + updateRegistryIndex() + return nil +} + +func configureLogging() error { + // set up logs root + logsRoot = dataRoot.ChildDir("logs", 0777) + err := logsRoot.Ensure() + if err != nil { + return fmt.Errorf("failed to initialize logs root: %s", err) + } + + // warn about CTRL-C on windows + if runningInConsole && onWindows { + log.Println("WARNING: portmaster-start is marked as a GUI application in order to get rid of the console window.") + log.Println("WARNING: CTRL-C will immediately kill without clean shutdown.") + } + return nil +} + +func updateRegistryIndex() { + err := registry.LoadIndexes(context.Background()) + if err != nil { + log.Printf("WARNING: error loading indexes: %s\n", err) + } + + err = registry.ScanStorage("") + if err != nil { + log.Printf("WARNING: error during storage scan: %s\n", err) + } + + registry.SelectVersions() +} diff --git a/pmctl/pack b/cmds/portmaster-start/pack similarity index 76% rename from pmctl/pack rename to cmds/portmaster-start/pack index 1e83e602..79fdf784 100755 --- a/pmctl/pack +++ b/cmds/portmaster-start/pack @@ -8,15 +8,15 @@ COL_BOLD="\033[01;01m" COL_RED="\033[31m" destDirPart1="../dist" -destDirPart2="control" +destDirPart2="start" function check { # output - output="pmctl" + output="portmaster-start" # get version version=$(grep "info.Set" main.go | cut -d'"' -f4) # build versioned file name - filename="portmaster-control_v${version//./-}" + filename="portmaster-start_v${version//./-}" # platform platform="${GOOS}_${GOARCH}" if [[ $GOOS == "windows" ]]; then @@ -28,19 +28,19 @@ function check { # check if file exists if [[ -f $destPath ]]; then - echo "[control] $platform $version already built" + echo "[start] $platform $version already built" else - echo -e "${COL_BOLD}[control] $platform $version${COL_OFF}" + echo -e "${COL_BOLD}[start] $platform $version${COL_OFF}" fi } function build { # output - output="pmctl" + output="portmaster-start" # get version version=$(grep "info.Set" main.go | cut -d'"' -f4) # build versioned file name - filename="portmaster-control_v${version//./-}" + filename="portmaster-start_v${version//./-}" # platform platform="${GOOS}_${GOARCH}" if [[ $GOOS == "windows" ]]; then @@ -52,19 +52,19 @@ function build { # check if file exists if [[ -f $destPath ]]; then - echo "[control] $platform already built in version $version, skipping..." + echo "[start] $platform already built in version $version, skipping..." return fi # build ./build if [[ $? -ne 0 ]]; then - echo -e "\n${COL_BOLD}[control] $platform: ${COL_RED}BUILD FAILED.${COL_OFF}" + echo -e "\n${COL_BOLD}[start] $platform: ${COL_RED}BUILD FAILED.${COL_OFF}" exit 1 fi mkdir -p $(dirname $destPath) cp $output $destPath - echo -e "\n${COL_BOLD}[control] $platform: successfully built.${COL_OFF}" + echo -e "\n${COL_BOLD}[start] $platform: successfully built.${COL_OFF}" } function check_all { diff --git a/cmds/portmaster-start/run.go b/cmds/portmaster-start/run.go new file mode 100644 index 00000000..f01574d2 --- /dev/null +++ b/cmds/portmaster-start/run.go @@ -0,0 +1,384 @@ +package main + +import ( + "fmt" + "io" + "log" + "os" + "os/exec" + "path" + "runtime" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/tevino/abool" +) + +const ( + // RestartExitCode is the exit code that any service started by portmaster-start + // can return in order to trigger a restart after a clean shutdown. + RestartExitCode = 23 +) + +var ( + runningInConsole bool + onWindows = runtime.GOOS == "windows" + stdinSignals bool + childIsRunning = abool.NewBool(false) +) + +// Options for starting component +type Options struct { + Name string + Identifier string // component identifier + ShortIdentifier string // populated automatically + SuppressArgs bool // do not use any args + AllowDownload bool // allow download of component if it is not yet available + AllowHidingWindow bool // allow hiding the window of the subprocess + NoOutput bool // do not use stdout/err if logging to file is available (did not fail to open log file) +} + +func init() { + registerComponent([]Options{ + { + Name: "Portmaster Core", + Identifier: "core/portmaster-core", + AllowDownload: true, + AllowHidingWindow: true, + }, + { + Name: "Portmaster App", + Identifier: "app/portmaster-app", + AllowDownload: false, + AllowHidingWindow: false, + }, + { + Name: "Portmaster Notifier", + Identifier: "notifier/portmaster-notifier", + AllowDownload: false, + AllowHidingWindow: true, + }, + }) +} + +func registerComponent(opts []Options) { + for idx := range opts { + opt := &opts[idx] // we need a copy + if opt.ShortIdentifier == "" { + opt.ShortIdentifier = path.Dir(opt.Identifier) + } + + rootCmd.AddCommand( + &cobra.Command{ + Use: opt.ShortIdentifier, + Short: "Run the " + opt.Name, + RunE: func(cmd *cobra.Command, args []string) error { + err := run(cmd, opt, args) + initiateShutdown(err) + return err + }, + }, + ) + + showCmd.AddCommand( + &cobra.Command{ + Use: opt.ShortIdentifier, + Short: "Show command to execute the " + opt.Name, + RunE: func(cmd *cobra.Command, args []string) error { + return show(cmd, opt, args) + }, + }, + ) + } +} + +func getExecArgs(opts *Options, cmdArgs []string) []string { + if opts.SuppressArgs { + return nil + } + + args := []string{"--data", dataDir} + if stdinSignals { + args = append(args, "-input-signals") + } + args = append(args, cmdArgs...) + return args +} + +func run(cmd *cobra.Command, opts *Options, cmdArgs []string) (err error) { + // set download option + registry.Online = opts.AllowDownload + + if isShutdown() { + return nil + } + + // get original arguments + // additional parameters can be specified using -- --some-parameter + args := getExecArgs(opts, cmdArgs) + + // check for duplicate instances + if opts.ShortIdentifier == "core" { + pid, _ := checkAndCreateInstanceLock(opts.ShortIdentifier) + if pid != 0 { + return fmt.Errorf("another instance of Portmaster Core is already running: PID %d", pid) + } + defer func() { + err := deleteInstanceLock(opts.ShortIdentifier) + if err != nil { + log.Printf("failed to delete instance lock: %s\n", err) + } + }() + } + + // notify service after some time + go func() { + // assume that after 3 seconds service has finished starting + time.Sleep(3 * time.Second) + startupComplete <- struct{}{} + }() + + // adapt identifier + if onWindows { + opts.Identifier += ".exe" + } + + // setup logging + // init log file + logFile := getPmStartLogFile(".log") + if logFile != nil { + // don't close logFile, will be closed by system + if opts.NoOutput { + log.Println("disabling log output to stdout... bye!") + log.SetOutput(logFile) + } else { + log.SetOutput(io.MultiWriter(os.Stdout, logFile)) + } + } + + return runAndRestart(opts, args) +} + +func runAndRestart(opts *Options, args []string) error { + tries := 0 + for { + tryAgain, err := execute(opts, args) + if err != nil { + log.Printf("%s failed with: %s\n", opts.Identifier, err) + tries++ + if tries >= maxRetries { + log.Printf("encountered %d consecutive errors, giving up ...", tries) + return err + } + } else { + tries = 0 + log.Printf("%s exited without error", opts.Identifier) + } + + if !tryAgain { + return err + } + + // if a restart was requested `tries` is set to 0 so + // this becomes a no-op. + time.Sleep(time.Duration(2*tries) * time.Second) + + if tries >= 2 || err == nil { + // if we are constantly failing or a restart was requested + // try to update the resources. + log.Printf("updating registry index") + updateRegistryIndex() + } + } +} + +func fixExecPerm(path string) error { + if onWindows { + return nil + } + + info, err := os.Stat(path) + if err != nil { + return fmt.Errorf("failed to stat %s: %w", path, err) + } + + if info.Mode() == 0755 { + return nil + } + + if err := os.Chmod(path, 0755); err != nil { + return fmt.Errorf("failed to chmod %s: %w", path, err) + } + + return nil +} + +func copyLogs(opts *Options, consoleSink io.Writer, version, ext string, logSource io.Reader, notifier chan<- struct{}) { + defer func() { notifier <- struct{}{} }() + + sink := consoleSink + + fileSink := getLogFile(opts, version, ext) + if fileSink != nil { + defer finalizeLogFile(fileSink) + if opts.NoOutput { + sink = fileSink + } else { + sink = io.MultiWriter(consoleSink, fileSink) + } + } + + if bytes, err := io.Copy(sink, logSource); err != nil { + log.Printf("%s: writting logs failed after %d bytes: %s", fileSink.Name(), bytes, err) + } +} + +func persistOutputStreams(opts *Options, version string, cmd *exec.Cmd) (chan struct{}, error) { + var ( + done = make(chan struct{}) + copyNotifier = make(chan struct{}, 2) + ) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return nil, fmt.Errorf("failed to connect stdout: %w", err) + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return nil, fmt.Errorf("failed to connect stderr: %w", err) + } + + go copyLogs(opts, os.Stdout, version, ".log", stdout, copyNotifier) + go copyLogs(opts, os.Stderr, version, ".error.log", stderr, copyNotifier) + + go func() { + <-copyNotifier + <-copyNotifier + close(copyNotifier) + close(done) + }() + + return done, nil +} + +func execute(opts *Options, args []string) (cont bool, err error) { + file, err := registry.GetFile(platform(opts.Identifier)) + if err != nil { + return true, fmt.Errorf("could not get component: %w", err) + } + + // check permission + if err := fixExecPerm(file.Path()); err != nil { + return true, err + } + + log.Printf("starting %s %s\n", file.Path(), strings.Join(args, " ")) + + // create command + exc := exec.Command(file.Path(), args...) //nolint:gosec // everything is okay + + if !runningInConsole && opts.AllowHidingWindow { + // Windows only: + // only hide (all) windows of program if we are not running in console and windows may be hidden + hideWindow(exc) + } + + outputsWritten, err := persistOutputStreams(opts, file.Version(), exc) + if err != nil { + return true, err + } + + interrupt, err := getProcessSignalFunc(exc) + if err != nil { + return true, err + } + + err = exc.Start() + if err != nil { + return true, fmt.Errorf("failed to start %s: %w", opts.Identifier, err) + } + childIsRunning.Set() + + // wait for completion + finished := make(chan error, 1) + go func() { + defer close(finished) + + <-outputsWritten + // wait for process to return + finished <- exc.Wait() + // update status + childIsRunning.UnSet() + }() + + // state change listeners + select { + case <-shuttingDown: + if err := interrupt(); err != nil { + log.Printf("failed to signal %s to shutdown: %s\n", opts.Identifier, err) + err = exc.Process.Kill() + if err != nil { + return false, fmt.Errorf("failed to kill %s: %w", opts.Identifier, err) + } + return false, fmt.Errorf("killed %s", opts.Identifier) + } + + // wait until shut down + select { + case <-finished: + case <-time.After(11 * time.Second): // portmaster core prints stack if not able to shutdown in 10 seconds + // kill + err = exc.Process.Kill() + if err != nil { + return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err) + } + return false, fmt.Errorf("killed %s", opts.Identifier) + } + return false, nil + + case err := <-finished: + return parseExitError(err) + } +} + +func getProcessSignalFunc(cmd *exec.Cmd) (func() error, error) { + if stdinSignals { + stdin, err := cmd.StdinPipe() + if err != nil { + return nil, fmt.Errorf("failed to connect stdin: %w", err) + } + + return func() error { + _, err := fmt.Fprintln(stdin, "SIGINT") + return err + }, nil + } + + return func() error { + return cmd.Process.Signal(os.Interrupt) + }, nil +} + +func parseExitError(err error) (restart bool, errWithCtx error) { + if err == nil { + // clean and coordinated exit + return false, nil + } + + if exErr, ok := err.(*exec.ExitError); ok { + switch exErr.ProcessState.ExitCode() { + case 0: + return false, fmt.Errorf("clean exit with error: %w", err) + case 1: + return true, fmt.Errorf("error during execution: %w", err) + case RestartExitCode: + return true, nil + default: + return true, fmt.Errorf("unknown exit code %w", exErr) + } + } + + return true, fmt.Errorf("unexpected error type: %w", err) +} diff --git a/pmctl/service_windows.go b/cmds/portmaster-start/service_windows.go similarity index 94% rename from pmctl/service_windows.go rename to cmds/portmaster-start/service_windows.go index 8b02815d..d22dba18 100644 --- a/pmctl/service_windows.go +++ b/cmds/portmaster-start/service_windows.go @@ -27,7 +27,7 @@ var ( AllowDownload: true, AllowHidingWindow: false, NoOutput: true, - }) + }, args) }), FParseErrWhitelist: cobra.FParseErrWhitelist{ // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags @@ -41,7 +41,7 @@ var ( ) func init() { - runCmd.AddCommand(runCoreService) + rootCmd.AddCommand(runCoreService) } const serviceName = "PortmasterCore" @@ -88,7 +88,7 @@ service: return ssec, errno } -func runService(cmd *cobra.Command, opts *Options) error { +func runService(cmd *cobra.Command, opts *Options, cmdArgs []string) error { // check if we are running interactively isDebug, err := svc.IsAnInteractiveSession() if err != nil { @@ -122,7 +122,8 @@ func runService(cmd *cobra.Command, opts *Options) error { go func() { // run slightly delayed time.Sleep(250 * time.Millisecond) - _ = handleRun(cmd, opts) // error handled by shutdown routine + err := run(cmd, opts, getExecArgs(opts, cmdArgs)) + initiateShutdown(err) finishWg.Done() runWg.Done() }() diff --git a/cmds/portmaster-start/show.go b/cmds/portmaster-start/show.go new file mode 100644 index 00000000..0e3696c2 --- /dev/null +++ b/cmds/portmaster-start/show.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(showCmd) + // sub-commands of show are registered using registerComponent +} + +var showCmd = &cobra.Command{ + Use: "show", + PersistentPreRunE: func(*cobra.Command, []string) error { + // all show sub-commands need the data-root but no logging. + return configureDataRoot() + }, + Short: "Show the command to run a Portmaster component yourself", +} + +func show(cmd *cobra.Command, opts *Options, cmdArgs []string) error { + // get original arguments + args := getExecArgs(opts, cmdArgs) + + // adapt identifier + if onWindows { + opts.Identifier += ".exe" + } + + file, err := registry.GetFile(platform(opts.Identifier)) + if err != nil { + return fmt.Errorf("could not get component: %s", err) + } + + fmt.Printf("%s %s\n", file.Path(), strings.Join(args, " ")) + + return nil +} diff --git a/pmctl/service.go b/cmds/portmaster-start/shutdown.go similarity index 50% rename from pmctl/service.go rename to cmds/portmaster-start/shutdown.go index cec7fa34..20ffd2de 100644 --- a/pmctl/service.go +++ b/cmds/portmaster-start/shutdown.go @@ -5,11 +5,10 @@ import ( ) var ( - startupComplete = make(chan struct{}) // signal that the start procedure completed (is never closed, just signaled once) - shuttingDown = make(chan struct{}) // signal that we are shutting down (will be closed, may not be closed directly, use initiateShutdown) - shutdownInitiated = false // not to be used directly + startupComplete = make(chan struct{}) // signal that the start procedure completed (is never closed, just signaled once) + shuttingDown = make(chan struct{}) // signal that we are shutting down (will be closed, may not be closed directly, use initiateShutdown) //nolint:deadcode,unused // false positive on linux, currently used by windows only - shutdownError error // may not be read or written to directly + shutdownError error // protected by shutdownLock shutdownLock sync.Mutex ) @@ -17,13 +16,24 @@ func initiateShutdown(err error) { shutdownLock.Lock() defer shutdownLock.Unlock() - if !shutdownInitiated { - shutdownInitiated = true + select { + case <-shuttingDown: + return + default: shutdownError = err close(shuttingDown) } } +func isShutdown() bool { + select { + case <-shuttingDown: + return true + default: + return false + } +} + //nolint:deadcode,unused // false positive on linux, currently used by windows only func getShutdownError() error { shutdownLock.Lock() diff --git a/cmds/portmaster-start/snoretoast_windows.go b/cmds/portmaster-start/snoretoast_windows.go new file mode 100644 index 00000000..a9f12630 --- /dev/null +++ b/cmds/portmaster-start/snoretoast_windows.go @@ -0,0 +1,14 @@ +package main + +func init() { + registerComponent([]Options{ + { + Name: "Portmaster SnoreToast Notifier", + ShortIdentifier: "notifier-snoretoast", // would otherwise conflict with notifier. + Identifier: "notifier/portmaster-snoretoast", + AllowDownload: false, + AllowHidingWindow: true, + SuppressArgs: true, + }, + }) +} diff --git a/pmctl/update.go b/cmds/portmaster-start/update.go similarity index 92% rename from pmctl/update.go rename to cmds/portmaster-start/update.go index 205247a1..45369af5 100644 --- a/pmctl/update.go +++ b/cmds/portmaster-start/update.go @@ -26,7 +26,7 @@ func downloadUpdates() error { if onWindows { registry.MandatoryUpdates = []string{ platform("core/portmaster-core.exe"), - platform("control/portmaster-control.exe"), + platform("start/portmaster-start.exe"), platform("app/portmaster-app.exe"), platform("notifier/portmaster-notifier.exe"), platform("notifier/portmaster-snoretoast.exe"), @@ -34,7 +34,7 @@ func downloadUpdates() error { } else { registry.MandatoryUpdates = []string{ platform("core/portmaster-core"), - platform("control/portmaster-control"), + platform("start/portmaster-start"), platform("app/portmaster-app"), platform("notifier/portmaster-notifier"), } diff --git a/cmds/portmaster-start/version.go b/cmds/portmaster-start/version.go new file mode 100644 index 00000000..2f80c77c --- /dev/null +++ b/cmds/portmaster-start/version.go @@ -0,0 +1,79 @@ +package main + +import ( + "fmt" + "os" + "runtime" + "sort" + "strings" + "text/tabwriter" + + "github.com/safing/portbase/info" + "github.com/spf13/cobra" +) + +var showShortVersion bool +var showAllVersions bool +var versionCmd = &cobra.Command{ + Use: "version", + Short: "Display various portmaster versions", + Args: cobra.NoArgs, + PersistentPreRunE: func(*cobra.Command, []string) error { + if showAllVersions { + // if we are going to show all component versions + // we need the dataroot to be configured. + if err := configureDataRoot(); err != nil { + return err + } + } + + return nil + }, + RunE: func(*cobra.Command, []string) error { + if !showAllVersions { + if showShortVersion { + fmt.Println(info.Version()) + } + + fmt.Println(info.FullVersion()) + return nil + } + + fmt.Printf("portmaster-start %s\n\n", info.Version()) + fmt.Printf("Components:\n") + + all := registry.Export() + keys := make([]string, 0, len(all)) + for identifier := range all { + keys = append(keys, identifier) + } + sort.Strings(keys) + + tw := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + for _, identifier := range keys { + res := all[identifier] + + if showShortVersion { + // in "short" mode, skip all resources that are irrelevant on that platform + if !strings.HasPrefix(identifier, "all") && !strings.HasPrefix(identifier, runtime.GOOS) { + continue + } + } + + fmt.Fprintf(tw, " %s\t%s\n", identifier, res.SelectedVersion.VersionNumber) + } + tw.Flush() + + return nil + }, +} + +func init() { + flags := versionCmd.Flags() + { + flags.BoolVar(&showShortVersion, "short", false, "Print only the verison number.") + flags.BoolVar(&showAllVersions, "all", false, "Dump versions for all components.") + } + + rootCmd.AddCommand(versionCmd) +} diff --git a/pmctl/.gitignore b/pmctl/.gitignore deleted file mode 100644 index cd5f05f4..00000000 --- a/pmctl/.gitignore +++ /dev/null @@ -1,6 +0,0 @@ -# binaries -pmctl -pmctl.exe - -# test dir -test diff --git a/pmctl/main.go b/pmctl/main.go deleted file mode 100644 index 3271661a..00000000 --- a/pmctl/main.go +++ /dev/null @@ -1,227 +0,0 @@ -package main - -import ( - "context" - "errors" - "fmt" - "log" - "os" - "os/signal" - "strings" - "syscall" - - "github.com/safing/portbase/dataroot" - "github.com/safing/portbase/info" - portlog "github.com/safing/portbase/log" - "github.com/safing/portbase/updater" - "github.com/safing/portbase/utils" - - "github.com/spf13/cobra" -) - -var ( - dataDir string - databaseDir string - dataRoot *utils.DirStructure - logsRoot *utils.DirStructure - - showShortVersion bool - showFullVersion bool - - // create registry - registry = &updater.ResourceRegistry{ - Name: "updates", - UpdateURLs: []string{ - "https://updates.safing.io", - }, - Beta: false, - DevMode: false, - Online: true, // is disabled later based on command - } - - rootCmd = &cobra.Command{ - Use: "portmaster-control", - Short: "Controller for all portmaster components", - PersistentPreRunE: cmdSetup, - RunE: func(cmd *cobra.Command, args []string) error { - if showShortVersion { - fmt.Println(info.Version()) - return nil - } - if showFullVersion { - fmt.Println(info.FullVersion()) - return nil - } - return cmd.Help() - }, - SilenceUsage: true, - } -) - -func init() { - // Let cobra ignore if we are running as "GUI" or not - cobra.MousetrapHelpText = "" - - rootCmd.PersistentFlags().StringVar(&dataDir, "data", "", "Configures the data directory. Alternatively, this can also be set via the environment variable PORTMASTER_DATA.") - rootCmd.PersistentFlags().StringVar(&databaseDir, "db", "", "Alias to --data (deprecated)") - _ = rootCmd.MarkPersistentFlagDirname("data") - _ = rootCmd.MarkPersistentFlagDirname("db") - rootCmd.Flags().BoolVar(&showFullVersion, "version", false, "Print version of portmaster-control.") - rootCmd.Flags().BoolVar(&showShortVersion, "ver", false, "Print version number only") -} - -func main() { - // set meta info - info.Set("Portmaster Control", "0.3.5", "AGPLv3", false) - - // for debugging - // log.Start() - // log.SetLogLevel(log.TraceLevel) - // go func() { - // time.Sleep(3 * time.Second) - // pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) - // os.Exit(1) - // }() - - // catch interrupt for clean shutdown - signalCh := make(chan os.Signal, 2) - signal.Notify( - signalCh, - os.Interrupt, - syscall.SIGHUP, - syscall.SIGINT, - syscall.SIGTERM, - syscall.SIGQUIT, - ) - - // start root command - go func() { - if err := rootCmd.Execute(); err != nil { - os.Exit(1) - } - os.Exit(0) - }() - - // for debugging windows service (no stdout/err) - // go func() { - // time.Sleep(10 * time.Second) - // // initiateShutdown(nil) - // // logControlStack() - // }() - - // wait for signals - for sig := range signalCh { - if childIsRunning.IsSet() { - log.Printf("got %s signal (ignoring), waiting for child to exit...\n", sig) - } else { - log.Printf("got %s signal, exiting... (not executing anything)\n", sig) - os.Exit(0) - } - } -} - -func cmdSetup(cmd *cobra.Command, args []string) (err error) { - // check if we are running in a console (try to attach to parent console if available) - runningInConsole, err = attachToParentConsole() - if err != nil { - log.Printf("failed to attach to parent console: %s\n", err) - os.Exit(1) - } - - // check if meta info is ok - err = info.CheckVersion() - if err != nil { - fmt.Println("compile error: please compile using the provided build script") - os.Exit(1) - } - - // set up logging - log.SetFlags(log.Ldate | log.Ltime | log.LUTC) - log.SetPrefix("[control] ") - log.SetOutput(os.Stdout) - - // not using portbase logger - portlog.SetLogLevel(portlog.CriticalLevel) - - // data directory - if !showShortVersion && !showFullVersion { - // set data root - // backwards compatibility - if dataDir == "" { - dataDir = databaseDir - } - - // check for environment variable - // PORTMASTER_DATA - if dataDir == "" { - dataDir = os.Getenv("PORTMASTER_DATA") - } - - // check data dir - if dataDir == "" { - return errors.New("please set the data directory using --data=/path/to/data/dir") - } - - // remove redundant escape characters and quotes - dataDir = strings.Trim(dataDir, `\"`) - // initialize dataroot - err = dataroot.Initialize(dataDir, 0755) - if err != nil { - return fmt.Errorf("failed to initialize data root: %s", err) - } - dataRoot = dataroot.Root() - - // initialize registry - err := registry.Initialize(dataRoot.ChildDir("updates", 0755)) - if err != nil { - return err - } - - registry.AddIndex(updater.Index{ - Path: "stable.json", - Stable: true, - Beta: false, - }) - - // TODO: enable loading beta versions - // registry.AddIndex(updater.Index{ - // Path: "beta.json", - // Stable: false, - // Beta: true, - // }) - - updateRegistryIndex() - } - - // logs and warning - if !showShortVersion && !showFullVersion && !strings.Contains(cmd.CommandPath(), " show ") { - // set up logs root - logsRoot = dataRoot.ChildDir("logs", 0777) - err = logsRoot.Ensure() - if err != nil { - return fmt.Errorf("failed to initialize logs root: %s", err) - } - - // warn about CTRL-C on windows - if runningInConsole && onWindows { - log.Println("WARNING: portmaster-control is marked as a GUI application in order to get rid of the console window.") - log.Println("WARNING: CTRL-C will immediately kill without clean shutdown.") - } - } - - return nil -} - -func updateRegistryIndex() { - err := registry.LoadIndexes(context.TODO()) - if err != nil { - log.Printf("WARNING: error loading indexes: %s\n", err) - } - - err = registry.ScanStorage("") - if err != nil { - log.Printf("WARNING: error during storage scan: %s\n", err) - } - - registry.SelectVersions() -} diff --git a/pmctl/run.go b/pmctl/run.go deleted file mode 100644 index eb5fc367..00000000 --- a/pmctl/run.go +++ /dev/null @@ -1,406 +0,0 @@ -package main - -import ( - "fmt" - "io" - "log" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "strings" - "sync" - "time" - - "github.com/spf13/cobra" - "github.com/tevino/abool" -) - -const ( - restartCode = 23 -) - -var ( - runningInConsole bool - onWindows = runtime.GOOS == "windows" - - childIsRunning = abool.NewBool(false) -) - -// Options for starting component -type Options struct { - Identifier string // component identifier - ShortIdentifier string // populated automatically - SuppressArgs bool // do not use any args - AllowDownload bool // allow download of component if it is not yet available - AllowHidingWindow bool // allow hiding the window of the subprocess - NoOutput bool // do not use stdout/err if logging to file is available (did not fail to open log file) -} - -func init() { - rootCmd.AddCommand(runCmd) - runCmd.AddCommand(runCore) - runCmd.AddCommand(runApp) - runCmd.AddCommand(runNotifier) -} - -var runCmd = &cobra.Command{ - Use: "run", - Short: "Run a Portmaster component in the foreground", -} - -var runCore = &cobra.Command{ - Use: "core", - Short: "Run the Portmaster Core", - RunE: func(cmd *cobra.Command, args []string) error { - return handleRun(cmd, &Options{ - Identifier: "core/portmaster-core", - AllowDownload: true, - AllowHidingWindow: true, - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -var runApp = &cobra.Command{ - Use: "app", - Short: "Run the Portmaster App", - RunE: func(cmd *cobra.Command, args []string) error { - return handleRun(cmd, &Options{ - Identifier: "app/portmaster-app", - AllowDownload: false, - AllowHidingWindow: false, - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -var runNotifier = &cobra.Command{ - Use: "notifier", - Short: "Run the Portmaster Notifier", - RunE: func(cmd *cobra.Command, args []string) error { - return handleRun(cmd, &Options{ - Identifier: "notifier/portmaster-notifier", - AllowDownload: false, - AllowHidingWindow: true, - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -func handleRun(cmd *cobra.Command, opts *Options) (err error) { - err = run(cmd, opts) - initiateShutdown(err) - return -} - -func run(cmd *cobra.Command, opts *Options) (err error) { //nolint:gocognit - - // set download option - registry.Online = opts.AllowDownload - - // parse identifier - opts.ShortIdentifier = path.Dir(opts.Identifier) - - // check for concurrent error (eg. service) - shutdownLock.Lock() - alreadyDead := shutdownInitiated - shutdownLock.Unlock() - if alreadyDead { - return - } - - // check for duplicate instances - if opts.ShortIdentifier == "core" { - pid, _ := checkAndCreateInstanceLock(opts.ShortIdentifier) - if pid != 0 { - return fmt.Errorf("another instance of Portmaster Core is already running: PID %d", pid) - } - defer func() { - err := deleteInstanceLock(opts.ShortIdentifier) - if err != nil { - log.Printf("failed to delete instance lock: %s\n", err) - } - }() - - } - - // notify service after some time - go func() { - // assume that after 3 seconds service has finished starting - time.Sleep(3 * time.Second) - startupComplete <- struct{}{} - }() - - // get original arguments - var args []string - if len(os.Args) < 4 { - return cmd.Help() - } - args = os.Args[3:] - if opts.SuppressArgs { - args = nil - } - - // adapt identifier - if onWindows { - opts.Identifier += ".exe" - } - - // setup logging - // init log file - logFile := initControlLogFile() - if logFile != nil { - // don't close logFile, will be closed by system - if opts.NoOutput { - log.Println("disabling log output to stdout... bye!") - log.SetOutput(logFile) - } else { - log.SetOutput(io.MultiWriter(os.Stdout, logFile)) - } - } - - // run - tries := 0 - for { - // normal execution - tryAgain := false - tryAgain, err = execute(opts, args) - switch { - case tryAgain && err != nil: - // temporary? execution error - log.Printf("execution of %s failed: %s\n", opts.Identifier, err) - tries++ - if tries >= 5 { - log.Println("error seems to be permanent, giving up...") - return err - } - // resilience - time.Sleep(time.Duration(tries) * 2 * time.Second) - if tries >= 2 { - // try updating - updateRegistryIndex() - } - log.Println("trying again...") - case tryAgain && err == nil: - // reset error count - tries = 0 - // upgrade - log.Println("restarting by request...") - // update index - log.Println("checking versions...") - updateRegistryIndex() - case !tryAgain && err != nil: - // fatal error - return err - case !tryAgain && err == nil: - // clean exit - log.Printf("%s completed successfully\n", opts.Identifier) - return nil - } - } -} - -// nolint:gocyclo,gocognit // TODO: simplify -func execute(opts *Options, args []string) (cont bool, err error) { - file, err := registry.GetFile(platform(opts.Identifier)) - if err != nil { - return true, fmt.Errorf("could not get component: %s", err) - } - - // check permission - if !onWindows { - info, err := os.Stat(file.Path()) - if err != nil { - return true, fmt.Errorf("failed to get file info on %s: %s", file.Path(), err) - } - if info.Mode() != 0755 { - err := os.Chmod(file.Path(), 0755) - if err != nil { - return true, fmt.Errorf("failed to set exec permissions on %s: %s", file.Path(), err) - } - } - } - - log.Printf("starting %s %s\n", file.Path(), strings.Join(args, " ")) - - // log files - var logFile, errorFile *os.File - logFileBasePath := filepath.Join(logsRoot.Path, opts.ShortIdentifier) - err = logsRoot.EnsureAbsPath(logFileBasePath) - if err != nil { - log.Printf("failed to check/create log file dir %s: %s\n", logFileBasePath, err) - } else { - // open log file - logFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.log", time.Now().UTC().Format("2006-01-02-15-04-05"))) - logFile = initializeLogFile(logFilePath, opts.Identifier, file.Version()) - if logFile != nil { - defer finalizeLogFile(logFile, logFilePath) - } - // open error log file - errorFilePath := filepath.Join(logFileBasePath, fmt.Sprintf("%s.error.log", time.Now().UTC().Format("2006-01-02-15-04-05"))) - errorFile = initializeLogFile(errorFilePath, opts.Identifier, file.Version()) - if errorFile != nil { - defer finalizeLogFile(errorFile, errorFilePath) - } - } - - // create command - exc := exec.Command(file.Path(), args...) //nolint:gosec // everything is okay - - if !runningInConsole && opts.AllowHidingWindow { - // Windows only: - // only hide (all) windows of program if we are not running in console and windows may be hidden - hideWindow(exc) - } - - // check if input signals are enabled - inputSignalsEnabled := false - for _, arg := range args { - if strings.HasSuffix(arg, "-input-signals") { - inputSignalsEnabled = true - break - } - } - - // consume stdout/stderr - stdout, err := exc.StdoutPipe() - if err != nil { - return true, fmt.Errorf("failed to connect stdout: %s", err) - } - stderr, err := exc.StderrPipe() - if err != nil { - return true, fmt.Errorf("failed to connect stderr: %s", err) - } - var stdin io.WriteCloser - if inputSignalsEnabled { - stdin, err = exc.StdinPipe() - if err != nil { - return true, fmt.Errorf("failed to connect stdin: %s", err) - } - } - - // start - err = exc.Start() - if err != nil { - return true, fmt.Errorf("failed to start %s: %s", opts.Identifier, err) - } - childIsRunning.Set() - - // start output writers - var wg sync.WaitGroup - wg.Add(2) - go func() { - var logFileError error - if logFile == nil { - _, logFileError = io.Copy(os.Stdout, stdout) - } else { - if opts.NoOutput { - _, logFileError = io.Copy(logFile, stdout) - } else { - _, logFileError = io.Copy(io.MultiWriter(os.Stdout, logFile), stdout) - } - } - if logFileError != nil { - log.Printf("failed write logs: %s\n", logFileError) - } - wg.Done() - }() - go func() { - var errorFileError error - if logFile == nil { - _, errorFileError = io.Copy(os.Stderr, stderr) - } else { - if opts.NoOutput { - _, errorFileError = io.Copy(errorFile, stderr) - } else { - _, errorFileError = io.Copy(io.MultiWriter(os.Stderr, errorFile), stderr) - } - } - if errorFileError != nil { - log.Printf("failed write error logs: %s\n", errorFileError) - } - wg.Done() - }() - - // wait for completion - finished := make(chan error) - go func() { - // wait for output writers to complete - wg.Wait() - // wait for process to return - finished <- exc.Wait() - // update status - childIsRunning.UnSet() - // notify manager - close(finished) - }() - - // state change listeners - for { - select { - case <-shuttingDown: - // signal process shutdown - if inputSignalsEnabled { - // for windows - _, err = stdin.Write([]byte("SIGINT\n")) - } else { - err = exc.Process.Signal(os.Interrupt) - } - if err != nil { - log.Printf("failed to signal %s to shutdown: %s\n", opts.Identifier, err) - err = exc.Process.Kill() - if err != nil { - return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err) - } - return false, fmt.Errorf("killed %s", opts.Identifier) - } - // wait until shut down - select { - case <-finished: - case <-time.After(11 * time.Second): // portmaster core prints stack if not able to shutdown in 10 seconds - // kill - err = exc.Process.Kill() - if err != nil { - return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err) - } - return false, fmt.Errorf("killed %s", opts.Identifier) - } - return false, nil - case err := <-finished: - if err != nil { - exErr, ok := err.(*exec.ExitError) - if ok { - switch exErr.ProcessState.ExitCode() { - case 0: - // clean exit - return false, fmt.Errorf("clean exit, but with error: %s", err) - case 1: - // error exit - return true, fmt.Errorf("error during execution: %s", err) - case restartCode: - // restart request - log.Printf("restarting %s\n", opts.Identifier) - return true, nil - default: - return true, fmt.Errorf("unexpected error during execution: %s", err) - } - } else { - return true, fmt.Errorf("unexpected error type during execution: %s", err) - } - } - // clean exit - return false, nil - } - } -} diff --git a/pmctl/show.go b/pmctl/show.go deleted file mode 100644 index ab89d13d..00000000 --- a/pmctl/show.go +++ /dev/null @@ -1,90 +0,0 @@ -package main - -import ( - "fmt" - "os" - "strings" - - "github.com/spf13/cobra" -) - -func init() { - rootCmd.AddCommand(showCmd) - showCmd.AddCommand(showCore) - showCmd.AddCommand(showApp) - showCmd.AddCommand(showNotifier) -} - -var showCmd = &cobra.Command{ - Use: "show", - Short: "Show the command to run a Portmaster component yourself", -} - -var showCore = &cobra.Command{ - Use: "core", - Short: "Show command to run the Portmaster Core", - RunE: func(cmd *cobra.Command, args []string) error { - return show(cmd, &Options{ - Identifier: "core/portmaster-core", - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -var showApp = &cobra.Command{ - Use: "app", - Short: "Show command to run the Portmaster App", - RunE: func(cmd *cobra.Command, args []string) error { - return show(cmd, &Options{ - Identifier: "app/portmaster-app", - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -var showNotifier = &cobra.Command{ - Use: "notifier", - Short: "Show command to run the Portmaster Notifier", - RunE: func(cmd *cobra.Command, args []string) error { - return show(cmd, &Options{ - Identifier: "notifier/portmaster-notifier", - SuppressArgs: true, - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -func show(cmd *cobra.Command, opts *Options) error { - // get original arguments - var args []string - if len(os.Args) < 4 { - return cmd.Help() - } - args = os.Args[3:] - if opts.SuppressArgs { - args = nil - } - - // adapt identifier - if onWindows { - opts.Identifier += ".exe" - } - - file, err := registry.GetFile(platform(opts.Identifier)) - if err != nil { - return fmt.Errorf("could not get component: %s", err) - } - - fmt.Printf("%s %s\n", file.Path(), strings.Join(args, " ")) - - return nil -} diff --git a/pmctl/snoretoast_windows.go b/pmctl/snoretoast_windows.go deleted file mode 100644 index 75421253..00000000 --- a/pmctl/snoretoast_windows.go +++ /dev/null @@ -1,40 +0,0 @@ -package main - -import "github.com/spf13/cobra" - -func init() { - showCmd.AddCommand(showSnoreToast) - runCmd.AddCommand(runSnoreToast) -} - -var showSnoreToast = &cobra.Command{ - Use: "notifier-snoretoast", - Short: "Show command to run the Notifier component SnoreToast", - RunE: func(cmd *cobra.Command, args []string) error { - return show(cmd, &Options{ - Identifier: "notifier/portmaster-snoretoast", - SuppressArgs: true, - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} - -var runSnoreToast = &cobra.Command{ - Use: "notifier-snoretoast", - Short: "Run the Notifier component SnoreToast", - RunE: func(cmd *cobra.Command, args []string) error { - return handleRun(cmd, &Options{ - Identifier: "notifier/portmaster-snoretoast", - AllowDownload: false, - AllowHidingWindow: true, - SuppressArgs: true, - }) - }, - FParseErrWhitelist: cobra.FParseErrWhitelist{ - // UnknownFlags will ignore unknown flags errors and continue parsing rest of the flags - UnknownFlags: true, - }, -} From 5580cbf4a4bbabd010c451470a48d7a64b95e3dc Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Thu, 16 Jul 2020 15:17:21 +0200 Subject: [PATCH 02/15] Update updates module to download and upgrade portmaster-start instead of portmaster-control --- updates/main.go | 4 ++-- updates/upgrader.go | 26 +++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/updates/main.go b/updates/main.go index ba08d808..6718b7e0 100644 --- a/updates/main.go +++ b/updates/main.go @@ -101,7 +101,7 @@ func start() error { if onWindows { mandatoryUpdates = []string{ platform("core/portmaster-core.exe"), - platform("control/portmaster-control.exe"), + platform("start/portmaster-start.exe"), platform("app/portmaster-app.exe"), platform("notifier/portmaster-notifier.exe"), platform("notifier/portmaster-snoretoast.exe"), @@ -109,7 +109,7 @@ func start() error { } else { mandatoryUpdates = []string{ platform("core/portmaster-core"), - platform("control/portmaster-control"), + platform("start/portmaster-start"), platform("app/portmaster-app"), platform("notifier/portmaster-notifier"), } diff --git a/updates/upgrader.go b/updates/upgrader.go index e4bb75a1..cb8100ee 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -51,10 +51,10 @@ func upgrader(_ context.Context, _ interface{}) error { } defer upgraderActive.SetTo(false) - // upgrade portmaster control - err := upgradePortmasterControl() + // upgrade portmaster-start + err := upgradePortmasterStart() if err != nil { - log.Warningf("updates: failed to upgrade portmaster-control: %s", err) + log.Warningf("updates: failed to upgrade portmaster-start: %s", err) } err = upgradeCoreNotify() @@ -122,16 +122,16 @@ func upgradeCoreNotifyActionHandler(n *notifications.Notification) { } } -func upgradePortmasterControl() error { - filename := "portmaster-control" +func upgradePortmasterStart() error { + filename := "portmaster-start" if onWindows { filename += ".exe" } // check if we can upgrade if pmCtrlUpdate == nil || pmCtrlUpdate.UpgradeAvailable() { - // get newest portmaster-control - new, err := GetPlatformFile("control/" + filename) // identifier, use forward slash! + // get newest portmaster-start + new, err := GetPlatformFile("start/" + filename) // identifier, use forward slash! if err != nil { return err } @@ -140,15 +140,15 @@ func upgradePortmasterControl() error { return nil } - // update portmaster-control in data root - rootControlPath := filepath.Join(filepath.Dir(registry.StorageDir().Path), filename) - err := upgradeFile(rootControlPath, pmCtrlUpdate) + // update portmaster-start in data root + rootPmStartPath := filepath.Join(filepath.Dir(registry.StorageDir().Path), filename) + err := upgradeFile(rootPmStartPath, pmCtrlUpdate) if err != nil { return err } - log.Infof("updates: upgraded %s", rootControlPath) + log.Infof("updates: upgraded %s", rootPmStartPath) - // upgrade parent process, if it's portmaster-control + // upgrade parent process, if it's portmaster-start parent, err := processInfo.NewProcess(int32(os.Getppid())) if err != nil { return fmt.Errorf("could not get parent process for upgrade checks: %w", err) @@ -158,7 +158,7 @@ func upgradePortmasterControl() error { return fmt.Errorf("could not get parent process name for upgrade checks: %w", err) } if parentName != filename { - log.Tracef("updates: parent process does not seem to be portmaster-control, name is %s", parentName) + log.Tracef("updates: parent process does not seem to be portmaster-start, name is %s", parentName) return nil } parentPath, err := parent.Exe() From fe6af5f26335d3ba9c80df99306b9f9b52b22a14 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Thu, 16 Jul 2020 15:54:37 +0200 Subject: [PATCH 03/15] Fix linter warnings --- cmds/portmaster-start/run.go | 8 ++++---- cmds/portmaster-start/service_windows.go | 2 +- cmds/portmaster-start/show.go | 2 +- cmds/portmaster-start/version.go | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/cmds/portmaster-start/run.go b/cmds/portmaster-start/run.go index f01574d2..e9fa40f4 100644 --- a/cmds/portmaster-start/run.go +++ b/cmds/portmaster-start/run.go @@ -74,7 +74,7 @@ func registerComponent(opts []Options) { Use: opt.ShortIdentifier, Short: "Run the " + opt.Name, RunE: func(cmd *cobra.Command, args []string) error { - err := run(cmd, opt, args) + err := run(opt, args) initiateShutdown(err) return err }, @@ -86,7 +86,7 @@ func registerComponent(opts []Options) { Use: opt.ShortIdentifier, Short: "Show command to execute the " + opt.Name, RunE: func(cmd *cobra.Command, args []string) error { - return show(cmd, opt, args) + return show(opt, args) }, }, ) @@ -106,7 +106,7 @@ func getExecArgs(opts *Options, cmdArgs []string) []string { return args } -func run(cmd *cobra.Command, opts *Options, cmdArgs []string) (err error) { +func run(opts *Options, cmdArgs []string) (err error) { // set download option registry.Online = opts.AllowDownload @@ -230,7 +230,7 @@ func copyLogs(opts *Options, consoleSink io.Writer, version, ext string, logSour } if bytes, err := io.Copy(sink, logSource); err != nil { - log.Printf("%s: writting logs failed after %d bytes: %s", fileSink.Name(), bytes, err) + log.Printf("%s: writing logs failed after %d bytes: %s", fileSink.Name(), bytes, err) } } diff --git a/cmds/portmaster-start/service_windows.go b/cmds/portmaster-start/service_windows.go index d22dba18..598d478f 100644 --- a/cmds/portmaster-start/service_windows.go +++ b/cmds/portmaster-start/service_windows.go @@ -122,7 +122,7 @@ func runService(cmd *cobra.Command, opts *Options, cmdArgs []string) error { go func() { // run slightly delayed time.Sleep(250 * time.Millisecond) - err := run(cmd, opts, getExecArgs(opts, cmdArgs)) + err := run(opts, getExecArgs(opts, cmdArgs)) initiateShutdown(err) finishWg.Done() runWg.Done() diff --git a/cmds/portmaster-start/show.go b/cmds/portmaster-start/show.go index 0e3696c2..f7044d31 100644 --- a/cmds/portmaster-start/show.go +++ b/cmds/portmaster-start/show.go @@ -21,7 +21,7 @@ var showCmd = &cobra.Command{ Short: "Show the command to run a Portmaster component yourself", } -func show(cmd *cobra.Command, opts *Options, cmdArgs []string) error { +func show(opts *Options, cmdArgs []string) error { // get original arguments args := getExecArgs(opts, cmdArgs) diff --git a/cmds/portmaster-start/version.go b/cmds/portmaster-start/version.go index 2f80c77c..d2588a19 100644 --- a/cmds/portmaster-start/version.go +++ b/cmds/portmaster-start/version.go @@ -71,7 +71,7 @@ var versionCmd = &cobra.Command{ func init() { flags := versionCmd.Flags() { - flags.BoolVar(&showShortVersion, "short", false, "Print only the verison number.") + flags.BoolVar(&showShortVersion, "short", false, "Print only the version number.") flags.BoolVar(&showAllVersions, "all", false, "Dump versions for all components.") } From 7690793c6613b87db6f66d62a7c15db3a3f9cb3d Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Thu, 16 Jul 2020 16:02:32 +0200 Subject: [PATCH 04/15] Add recover-iptables sub-comment. Fixes #6 --- cmds/portmaster-start/recover_linux.go | 21 +++++++++++++++++++++ firewall/interception/nfqueue_linux.go | 17 +++++++++-------- 2 files changed, 30 insertions(+), 8 deletions(-) create mode 100644 cmds/portmaster-start/recover_linux.go diff --git a/cmds/portmaster-start/recover_linux.go b/cmds/portmaster-start/recover_linux.go new file mode 100644 index 00000000..b87723b0 --- /dev/null +++ b/cmds/portmaster-start/recover_linux.go @@ -0,0 +1,21 @@ +package main + +import ( + "github.com/safing/portmaster/firewall/interception" + "github.com/spf13/cobra" +) + +var recoverForceFlag bool +var recoverIPTablesCmd = &cobra.Command{ + Use: "recover-iptables", + Short: "Removes obsolete IP tables rules in case of an unclean shutdown", + RunE: func(*cobra.Command, []string) error { + return interception.DeactivateNfqueueFirewall(recoverForceFlag) + }, + SilenceUsage: true, +} + +func init() { + recoverIPTablesCmd.Flags().BoolVarP(&recoverForceFlag, "force", "f", false, "Force removal ignoring errors") + rootCmd.AddCommand(recoverIPTablesCmd) +} diff --git a/firewall/interception/nfqueue_linux.go b/firewall/interception/nfqueue_linux.go index 312478cb..27b81976 100644 --- a/firewall/interception/nfqueue_linux.go +++ b/firewall/interception/nfqueue_linux.go @@ -179,7 +179,8 @@ func activateNfqueueFirewall() error { return nil } -func deactivateNfqueueFirewall() error { +// DeactivateNfqueueFirewall drops portmaster related IP tables rules. +func DeactivateNfqueueFirewall(force bool) error { // IPv4 ip4tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) if err != nil { @@ -194,7 +195,7 @@ func deactivateNfqueueFirewall() error { return err } if ok { - if err = ip4tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil { + if err = ip4tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil && !force { return err } } @@ -202,10 +203,10 @@ func deactivateNfqueueFirewall() error { for _, chain := range v4chains { splittedRule := strings.Split(chain, " ") - if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil { + if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil && !force { return err } - if err = ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil { + if err = ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil && !force { return err } } @@ -223,7 +224,7 @@ func deactivateNfqueueFirewall() error { return err } if ok { - if err = ip6tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil { + if err = ip6tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil && !force { return err } } @@ -231,10 +232,10 @@ func deactivateNfqueueFirewall() error { for _, chain := range v6chains { splittedRule := strings.Split(chain, " ") - if err := ip6tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil { + if err := ip6tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil && !force { return err } - if err := ip6tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil { + if err := ip6tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil && !force { return err } } @@ -293,7 +294,7 @@ func StopNfqueueInterception() error { in6Queue.Destroy() } - err := deactivateNfqueueFirewall() + err := DeactivateNfqueueFirewall(false) if err != nil { return fmt.Errorf("interception: error while deactivating nfqueue: %s", err) } From 6d69039c20d2002ba3f77b84faf23b9dcf128422 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Jul 2020 11:07:46 +0200 Subject: [PATCH 05/15] Implement review comments --- cmds/portmaster-start/logs.go | 13 --------- cmds/portmaster-start/main.go | 16 ----------- cmds/portmaster-start/run.go | 7 ++--- cmds/portmaster-start/shutdown.go | 2 +- cmds/portmaster-start/update.go | 10 ++++++- updates/upgrader.go | 48 +++++++++++++++++++++++-------- 6 files changed, 49 insertions(+), 47 deletions(-) diff --git a/cmds/portmaster-start/logs.go b/cmds/portmaster-start/logs.go index 2a13e097..2e73f4ca 100644 --- a/cmds/portmaster-start/logs.go +++ b/cmds/portmaster-start/logs.go @@ -6,7 +6,6 @@ import ( "os" "path/filepath" "runtime" - "runtime/pprof" "time" "github.com/safing/portbase/container" @@ -113,18 +112,6 @@ func logControlError(cErr error) { fmt.Fprintln(errorFile, cErr.Error()) } -//nolint:deadcode,unused // TODO -func logControlStack() { - fp := getPmStartLogFile(".stack.log") - if fp == nil { - return - } - defer fp.Close() - - // write error and close - _ = pprof.Lookup("goroutine").WriteTo(fp, 2) -} - //nolint:deadcode,unused // false positive on linux, currently used by windows only func runAndLogControlError(wrappedFunc func(cmd *cobra.Command, args []string) error) func(cmd *cobra.Command, args []string) error { return func(cmd *cobra.Command, args []string) error { diff --git a/cmds/portmaster-start/main.go b/cmds/portmaster-start/main.go index 52ef057a..925dde68 100644 --- a/cmds/portmaster-start/main.go +++ b/cmds/portmaster-start/main.go @@ -75,15 +75,6 @@ func main() { // set meta info info.Set("Portmaster Start", "0.3.5", "AGPLv3", false) - // for debugging - // log.Start() - // log.SetLogLevel(log.TraceLevel) - // go func() { - // time.Sleep(3 * time.Second) - // pprof.Lookup("goroutine").WriteTo(os.Stdout, 2) - // os.Exit(1) - // }() - // catch interrupt for clean shutdown signalCh := make(chan os.Signal, 2) signal.Notify( @@ -103,13 +94,6 @@ func main() { os.Exit(0) }() - // for debugging windows service (no stdout/err) - // go func() { - // time.Sleep(10 * time.Second) - // // initiateShutdown(nil) - // // logControlStack() - // }() - // wait for signals for sig := range signalCh { if childIsRunning.IsSet() { diff --git a/cmds/portmaster-start/run.go b/cmds/portmaster-start/run.go index e9fa40f4..f4f26974 100644 --- a/cmds/portmaster-start/run.go +++ b/cmds/portmaster-start/run.go @@ -100,7 +100,7 @@ func getExecArgs(opts *Options, cmdArgs []string) []string { args := []string{"--data", dataDir} if stdinSignals { - args = append(args, "-input-signals") + args = append(args, "--input-signals") } args = append(args, cmdArgs...) return args @@ -110,7 +110,7 @@ func run(opts *Options, cmdArgs []string) (err error) { // set download option registry.Online = opts.AllowDownload - if isShutdown() { + if isShuttingDown() { return nil } @@ -328,8 +328,7 @@ func execute(opts *Options, args []string) (cont bool, err error) { // wait until shut down select { case <-finished: - case <-time.After(11 * time.Second): // portmaster core prints stack if not able to shutdown in 10 seconds - // kill + case <-time.After(3 * time.Minute): // portmaster core prints stack if not able to shutdown in 3 minutes, give it one more ... err = exc.Process.Kill() if err != nil { return false, fmt.Errorf("failed to kill %s: %s", opts.Identifier, err) diff --git a/cmds/portmaster-start/shutdown.go b/cmds/portmaster-start/shutdown.go index 20ffd2de..b42218a1 100644 --- a/cmds/portmaster-start/shutdown.go +++ b/cmds/portmaster-start/shutdown.go @@ -25,7 +25,7 @@ func initiateShutdown(err error) { } } -func isShutdown() bool { +func isShuttingDown() bool { select { case <-shuttingDown: return true diff --git a/cmds/portmaster-start/update.go b/cmds/portmaster-start/update.go index 45369af5..edb4c8f1 100644 --- a/cmds/portmaster-start/update.go +++ b/cmds/portmaster-start/update.go @@ -40,7 +40,15 @@ func downloadUpdates() error { } } - // ok, now we want logging. + // add updates that we require on all platforms. + registry.MandatoryUpdates = append( + registry.MandatoryUpdates, + "all/ui/modules/base.zip", + ) + + // logging is configured as a presistent pre-run method inherited from + // the root command but since we don't use run.Run() we need to start + // logging ourself. err := log.Start() if err != nil { fmt.Printf("failed to start logging: %s\n", err) diff --git a/updates/upgrader.go b/updates/upgrader.go index cb8100ee..254e42d8 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -148,30 +148,54 @@ func upgradePortmasterStart() error { } log.Infof("updates: upgraded %s", rootPmStartPath) + // TODO(ppacher): remove once we released a few more versions ... + warnOnIncorrectParentPath(filename) + + return nil +} + +func warnOnIncorrectParentPath(expectedFileName string) { // upgrade parent process, if it's portmaster-start parent, err := processInfo.NewProcess(int32(os.Getppid())) if err != nil { - return fmt.Errorf("could not get parent process for upgrade checks: %w", err) + log.Tracef("could not get parent process: %s", err) + return } parentName, err := parent.Name() if err != nil { - return fmt.Errorf("could not get parent process name for upgrade checks: %w", err) + log.Tracef("could not get parent process name: %s", err) + return } - if parentName != filename { - log.Tracef("updates: parent process does not seem to be portmaster-start, name is %s", parentName) - return nil + if parentName != expectedFileName { + log.Warningf("updates: parent process does not seem to be portmaster-start, name is %s", parentName) + + // TODO(ppacher): once we released a new installer and folks had time + // to update we should send a module warning/hint to the + // UI notifying the user that he's still using portmaster-control. + return } + parentPath, err := parent.Exe() if err != nil { - return fmt.Errorf("could not get parent process path for upgrade: %w", err) + log.Tracef("could not get parent process path: %s", err) + return } - err = upgradeFile(parentPath, pmCtrlUpdate) - if err != nil { - return err - } - log.Infof("updates: upgraded %s", parentPath) - return nil + absPath, err := filepath.Abs(parentPath) + if err != nil { + log.Tracef("could not get absolut parent process path: %s", err) + return + } + + root := filepath.Dir(registry.StorageDir().Path) + if !strings.HasPrefix(absPath, root) { + log.Warningf("detected unexpected path %s for portmaster-start", absPath) + + // TODO(ppacher): once we released a new installer and folks had time + // to update we should send a module warning/hint to the + // UI notifying the user that he's using portmaster-start + // from a wrong location. + } } func upgradeFile(fileToUpgrade string, file *updater.File) error { From 9eb7195bd862de5c174ee75e854b32f33db9ab0c Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Jul 2020 11:21:09 +0200 Subject: [PATCH 06/15] Try as hard as possible to remove the nfqueue rules --- cmds/portmaster-start/recover_linux.go | 4 +- firewall/interception/nfqueue_linux.go | 124 ++++++++----------------- 2 files changed, 42 insertions(+), 86 deletions(-) diff --git a/cmds/portmaster-start/recover_linux.go b/cmds/portmaster-start/recover_linux.go index b87723b0..bd344cbf 100644 --- a/cmds/portmaster-start/recover_linux.go +++ b/cmds/portmaster-start/recover_linux.go @@ -5,17 +5,15 @@ import ( "github.com/spf13/cobra" ) -var recoverForceFlag bool var recoverIPTablesCmd = &cobra.Command{ Use: "recover-iptables", Short: "Removes obsolete IP tables rules in case of an unclean shutdown", RunE: func(*cobra.Command, []string) error { - return interception.DeactivateNfqueueFirewall(recoverForceFlag) + return interception.DeactivateNfqueueFirewall() }, SilenceUsage: true, } func init() { - recoverIPTablesCmd.Flags().BoolVarP(&recoverForceFlag, "force", "f", false, "Force removal ignoring errors") rootCmd.AddCommand(recoverIPTablesCmd) } diff --git a/firewall/interception/nfqueue_linux.go b/firewall/interception/nfqueue_linux.go index 27b81976..d6613bda 100644 --- a/firewall/interception/nfqueue_linux.go +++ b/firewall/interception/nfqueue_linux.go @@ -108,69 +108,58 @@ func init() { } func activateNfqueueFirewall() error { - - // IPv4 - ip4tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) - if err != nil { + if err := activateIPTables(iptables.ProtocolIPv4, v4rules, v4once, v4chains); err != nil { return err } - for _, chain := range v4chains { - splittedRule := strings.Split(chain, " ") - if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil { - return err - } + if err := activateIPTables(iptables.ProtocolIPv6, v6rules, v6once, v6chains); err != nil { + return err } - for _, rule := range v4rules { - splittedRule := strings.Split(rule, " ") - if err = ip4tables.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil { - return err - } - } + return nil +} - var ok bool - for _, rule := range v4once { - splittedRule := strings.Split(rule, " ") - ok, err = ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) - if err != nil { - return err - } - if !ok { - if err = ip4tables.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil { - return err - } - } - } +// DeactivateNfqueueFirewall drops portmaster related IP tables rules. +func DeactivateNfqueueFirewall() error { + // IPv4 + errV4 := deactivateIPTables(iptables.ProtocolIPv4, v4once, v4chains) // IPv6 - ip6tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv6) + if errV6 := deactivateIPTables(iptables.ProtocolIPv6, v6once, v6chains); errV6 != nil && errV4 == nil { + return errV6 + } + + return errV4 +} + +func activateIPTables(protocol iptables.Protocol, rules, once, chains []string) error { + tbls, err := iptables.NewWithProtocol(protocol) if err != nil { return err } - for _, chain := range v6chains { + for _, chain := range chains { splittedRule := strings.Split(chain, " ") - if err = ip6tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil { + if err = tbls.ClearChain(splittedRule[0], splittedRule[1]); err != nil { return err } } - for _, rule := range v6rules { + for _, rule := range rules { splittedRule := strings.Split(rule, " ") - if err = ip6tables.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil { + if err = tbls.Append(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil { return err } } - for _, rule := range v6once { + for _, rule := range once { splittedRule := strings.Split(rule, " ") - ok, err := ip6tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) + ok, err := tbls.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) if err != nil { return err } if !ok { - if err = ip6tables.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil { + if err = tbls.Insert(splittedRule[0], splittedRule[1], 1, splittedRule[2:]...); err != nil { return err } } @@ -179,68 +168,37 @@ func activateNfqueueFirewall() error { return nil } -// DeactivateNfqueueFirewall drops portmaster related IP tables rules. -func DeactivateNfqueueFirewall(force bool) error { - // IPv4 - ip4tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv4) +func deactivateIPTables(protocol iptables.Protocol, rules, chains []string) error { + tbls, err := iptables.NewWithProtocol(protocol) if err != nil { return err } - var ok bool - for _, rule := range v4once { + var firstErr error + for _, rule := range rules { splittedRule := strings.Split(rule, " ") - ok, err = ip4tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) - if err != nil { - return err + ok, err := tbls.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) + if err != nil && firstErr == nil { + firstErr = err } if ok { - if err = ip4tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil && !force { - return err + if err = tbls.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil && firstErr == nil { + firstErr = err } } } - for _, chain := range v4chains { + for _, chain := range chains { splittedRule := strings.Split(chain, " ") - if err = ip4tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil && !force { - return err + if err = tbls.ClearChain(splittedRule[0], splittedRule[1]); err != nil && firstErr == nil { + firstErr = err } - if err = ip4tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil && !force { - return err + if err = tbls.DeleteChain(splittedRule[0], splittedRule[1]); err != nil && firstErr == nil { + firstErr = err } } - // IPv6 - ip6tables, err := iptables.NewWithProtocol(iptables.ProtocolIPv6) - if err != nil { - return err - } - - for _, rule := range v6once { - splittedRule := strings.Split(rule, " ") - ok, err := ip6tables.Exists(splittedRule[0], splittedRule[1], splittedRule[2:]...) - if err != nil { - return err - } - if ok { - if err = ip6tables.Delete(splittedRule[0], splittedRule[1], splittedRule[2:]...); err != nil && !force { - return err - } - } - } - - for _, chain := range v6chains { - splittedRule := strings.Split(chain, " ") - if err := ip6tables.ClearChain(splittedRule[0], splittedRule[1]); err != nil && !force { - return err - } - if err := ip6tables.DeleteChain(splittedRule[0], splittedRule[1]); err != nil && !force { - return err - } - } - - return nil + return firstErr } // StartNfqueueInterception starts the nfqueue interception. @@ -294,7 +252,7 @@ func StopNfqueueInterception() error { in6Queue.Destroy() } - err := DeactivateNfqueueFirewall(false) + err := DeactivateNfqueueFirewall() if err != nil { return fmt.Errorf("interception: error while deactivating nfqueue: %s", err) } From 3428035dd4a2a2b694a30e1d707bb2034c676851 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Jul 2020 11:29:02 +0200 Subject: [PATCH 07/15] =?UTF-8?q?=F0=9F=8E=89=20import=20uptool=20from=20p?= =?UTF-8?q?ortbase?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmds/uptool/.gitignore | 1 + cmds/uptool/main.go | 34 ++++++++++++++++++ cmds/uptool/scan.go | 80 ++++++++++++++++++++++++++++++++++++++++++ cmds/uptool/update.go | 64 +++++++++++++++++++++++++++++++++ 4 files changed, 179 insertions(+) create mode 100644 cmds/uptool/.gitignore create mode 100644 cmds/uptool/main.go create mode 100644 cmds/uptool/scan.go create mode 100644 cmds/uptool/update.go diff --git a/cmds/uptool/.gitignore b/cmds/uptool/.gitignore new file mode 100644 index 00000000..c5074cf6 --- /dev/null +++ b/cmds/uptool/.gitignore @@ -0,0 +1 @@ +uptool diff --git a/cmds/uptool/main.go b/cmds/uptool/main.go new file mode 100644 index 00000000..fe22d78d --- /dev/null +++ b/cmds/uptool/main.go @@ -0,0 +1,34 @@ +package main + +import ( + "os" + "path/filepath" + + "github.com/safing/portbase/updater" + "github.com/safing/portbase/utils" + "github.com/spf13/cobra" +) + +var registry *updater.ResourceRegistry + +var rootCmd = &cobra.Command{ + Use: "uptool", + Short: "A simple tool to assist in the update and release process", + Args: cobra.ExactArgs(1), + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + absPath, err := filepath.Abs(args[0]) + if err != nil { + return err + } + + registry = &updater.ResourceRegistry{} + return registry.Initialize(utils.NewDirStructure(absPath, 0o755)) + }, + SilenceUsage: true, +} + +func main() { + if err := rootCmd.Execute(); err != nil { + os.Exit(1) + } +} diff --git a/cmds/uptool/scan.go b/cmds/uptool/scan.go new file mode 100644 index 00000000..8ec15905 --- /dev/null +++ b/cmds/uptool/scan.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + "strings" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(scanCmd) +} + +var scanCmd = &cobra.Command{ + Use: "scan", + Short: "Scan the specified directory and print the result", + Args: cobra.ExactArgs(1), + RunE: scan, +} + +func scan(cmd *cobra.Command, args []string) error { + err := scanStorage() + if err != nil { + return err + } + + // export beta + data, err := json.MarshalIndent(exportSelected(true), "", " ") + if err != nil { + return err + } + // print + fmt.Println("beta:") + fmt.Println(string(data)) + + // export stable + data, err = json.MarshalIndent(exportSelected(false), "", " ") + if err != nil { + return err + } + // print + fmt.Println("\nstable:") + fmt.Println(string(data)) + + return nil +} + +func scanStorage() error { + files, err := ioutil.ReadDir(registry.StorageDir().Path) + if err != nil { + return err + } + + // scan "all" and all "os_platform" dirs + for _, file := range files { + if file.IsDir() && (file.Name() == "all" || strings.Contains(file.Name(), "_")) { + err := registry.ScanStorage(filepath.Join(registry.StorageDir().Path, file.Name())) + if err != nil { + return err + } + } + } + + return nil +} + +func exportSelected(beta bool) map[string]string { + registry.SetBeta(beta) + registry.SelectVersions() + export := registry.Export() + + versions := make(map[string]string) + for _, rv := range export { + versions[rv.Identifier] = rv.SelectedVersion.VersionNumber + } + return versions +} diff --git a/cmds/uptool/update.go b/cmds/uptool/update.go new file mode 100644 index 00000000..442c31eb --- /dev/null +++ b/cmds/uptool/update.go @@ -0,0 +1,64 @@ +package main + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "path/filepath" + + "github.com/spf13/cobra" +) + +func init() { + rootCmd.AddCommand(updateCmd) +} + +var updateCmd = &cobra.Command{ + Use: "update", + Short: "Update scans the specified directory and registry the index and symlink structure", + Args: cobra.ExactArgs(1), + RunE: update, +} + +func update(cmd *cobra.Command, args []string) error { + err := scanStorage() + if err != nil { + return err + } + + // export beta + data, err := json.MarshalIndent(exportSelected(true), "", " ") + if err != nil { + return err + } + // print + fmt.Println("beta:") + fmt.Println(string(data)) + // write index + err = ioutil.WriteFile(filepath.Join(registry.StorageDir().Dir, "beta.json"), data, 0o644) //nolint:gosec // 0644 is intended + if err != nil { + return err + } + + // export stable + data, err = json.MarshalIndent(exportSelected(false), "", " ") + if err != nil { + return err + } + // print + fmt.Println("\nstable:") + fmt.Println(string(data)) + // write index + err = ioutil.WriteFile(filepath.Join(registry.StorageDir().Dir, "stable.json"), data, 0o644) //nolint:gosec // 0644 is intended + if err != nil { + return err + } + // create symlinks + err = registry.CreateSymlinks(registry.StorageDir().ChildDir("latest", 0o755)) + if err != nil { + return err + } + fmt.Println("\nstable symlinks created") + + return nil +} From cef2a4e9be4f3a5de56175edafc9c19cd6958cb8 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Jul 2020 12:13:13 +0200 Subject: [PATCH 08/15] Move portmaster binary to cmds/portmaster-core --- cmds/portmaster-core/.gitignore | 7 +++++++ build => cmds/portmaster-core/build | 0 main.go => cmds/portmaster-core/main.go | 0 pack_core => cmds/portmaster-core/pack | 2 +- cmds/portmaster-start/pack | 2 +- cmds/portmaster-start/update.go | 2 +- pack | 22 +++++++++++----------- 7 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 cmds/portmaster-core/.gitignore rename build => cmds/portmaster-core/build (100%) rename main.go => cmds/portmaster-core/main.go (100%) rename pack_core => cmds/portmaster-core/pack (98%) diff --git a/cmds/portmaster-core/.gitignore b/cmds/portmaster-core/.gitignore new file mode 100644 index 00000000..eff36331 --- /dev/null +++ b/cmds/portmaster-core/.gitignore @@ -0,0 +1,7 @@ +# Compiled binaries +portmaster +portmaster.exe +dnsonly +dnsonly.exe +main +main.exe diff --git a/build b/cmds/portmaster-core/build similarity index 100% rename from build rename to cmds/portmaster-core/build diff --git a/main.go b/cmds/portmaster-core/main.go similarity index 100% rename from main.go rename to cmds/portmaster-core/main.go diff --git a/pack_core b/cmds/portmaster-core/pack similarity index 98% rename from pack_core rename to cmds/portmaster-core/pack index de86b2f8..0b2a925b 100755 --- a/pack_core +++ b/cmds/portmaster-core/pack @@ -7,7 +7,7 @@ COL_OFF="\033[00m" COL_BOLD="\033[01;01m" COL_RED="\033[31m" -destDirPart1="dist" +destDirPart1="../../dist" destDirPart2="core" function check { diff --git a/cmds/portmaster-start/pack b/cmds/portmaster-start/pack index 79fdf784..cf213517 100755 --- a/cmds/portmaster-start/pack +++ b/cmds/portmaster-start/pack @@ -7,7 +7,7 @@ COL_OFF="\033[00m" COL_BOLD="\033[01;01m" COL_RED="\033[31m" -destDirPart1="../dist" +destDirPart1="../../dist" destDirPart2="start" function check { diff --git a/cmds/portmaster-start/update.go b/cmds/portmaster-start/update.go index edb4c8f1..af018e66 100644 --- a/cmds/portmaster-start/update.go +++ b/cmds/portmaster-start/update.go @@ -46,7 +46,7 @@ func downloadUpdates() error { "all/ui/modules/base.zip", ) - // logging is configured as a presistent pre-run method inherited from + // logging is configured as a persistent pre-run method inherited from // the root command but since we don't use run.Run() we need to start // logging ourself. err := log.Start() diff --git a/pack b/pack index 0b74c350..144f303b 100755 --- a/pack +++ b/pack @@ -5,12 +5,19 @@ cd "$baseDir" # first check what will be built +function packAll() { + for i in ./cmds/* ; do + if [ -e $i/pack ]; then + $i/pack $1 + fi + done +} + echo "" echo "pack list:" echo "" -./pmctl/pack check -./pack_core check +packAll check # confirm @@ -20,15 +27,8 @@ echo "" # build -./pmctl/pack build -if [[ $? -ne 0 ]]; then - exit 1 -fi - -./pack_core build -if [[ $? -ne 0 ]]; then - exit 1 -fi +set -e +packAll build echo "" echo "finished packing." From b5dbd35e3ca73f5d05c6f32538a57833463d2944 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Fri, 17 Jul 2020 14:12:01 +0200 Subject: [PATCH 09/15] Fix incorrect flag for portmaster-start --- updates/upgrader.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/updates/upgrader.go b/updates/upgrader.go index 254e42d8..281efb92 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -209,7 +209,7 @@ func upgradeFile(fileToUpgrade string, file *updater.File) error { if fileExists { // get current version var currentVersion string - cmd := exec.Command(fileToUpgrade, "--ver") + cmd := exec.Command(fileToUpgrade, "version", "--short") out, err := cmd.Output() if err == nil { // abort if version matches From 1fa6cbcc6b8d6b09588ad12f63e7afbaee36d61a Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 22 Jul 2020 09:09:15 +0200 Subject: [PATCH 10/15] Bump version of portmaster-start to 0.4.0 --- cmds/portmaster-start/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmds/portmaster-start/main.go b/cmds/portmaster-start/main.go index 925dde68..1c2a4d20 100644 --- a/cmds/portmaster-start/main.go +++ b/cmds/portmaster-start/main.go @@ -73,7 +73,7 @@ func main() { cobra.OnInitialize(initCobra) // set meta info - info.Set("Portmaster Start", "0.3.5", "AGPLv3", false) + info.Set("Portmaster Start", "0.4.0", "AGPLv3", false) // catch interrupt for clean shutdown signalCh := make(chan os.Signal, 2) From 088fef6f55519476e4beb0757a9b686388491439 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 22 Jul 2020 09:26:11 +0200 Subject: [PATCH 11/15] Try to auto-detect installation directory --- cmds/portmaster-start/main.go | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/cmds/portmaster-start/main.go b/cmds/portmaster-start/main.go index 1c2a4d20..683c3bbf 100644 --- a/cmds/portmaster-start/main.go +++ b/cmds/portmaster-start/main.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/signal" + "path/filepath" "strings" "syscall" @@ -137,7 +138,12 @@ func configureDataRoot() error { dataDir = os.Getenv("PORTMASTER_DATA") } - // check data dir + // if it's still empty try to auto-detect it + if dataDir == "" { + dataDir = detectInstallationDir() + } + + // finally, if it's still empty the user must provide it if dataDir == "" { return errors.New("please set the data directory using --data=/path/to/data/dir") } @@ -203,3 +209,23 @@ func updateRegistryIndex() { registry.SelectVersions() } + +func detectInstallationDir() string { + exePath, err := filepath.Abs(os.Args[0]) + if err != nil { + return "" + } + + parent := filepath.Dir(exePath) + stableJSONFile := filepath.Join(parent, "updates", "stable.json") + stat, err := os.Stat(stableJSONFile) + if err != nil { + return "" + } + + if stat.IsDir() { + return "" + } + + return parent +} From 504c753a9b2fdb2fc0f27bd636d0a5c12a1ff020 Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 22 Jul 2020 14:16:13 +0200 Subject: [PATCH 12/15] Let portmaster-start exit with an error if index update fails --- cmds/portmaster-start/build | 13 ------------- cmds/portmaster-start/main.go | 15 +++++++++------ cmds/portmaster-start/run.go | 2 +- cmds/portmaster-start/show.go | 2 +- cmds/portmaster-start/version.go | 2 +- 5 files changed, 12 insertions(+), 22 deletions(-) diff --git a/cmds/portmaster-start/build b/cmds/portmaster-start/build index 26fb3525..dc0992a0 100755 --- a/cmds/portmaster-start/build +++ b/cmds/portmaster-start/build @@ -46,19 +46,6 @@ fi # build tools EXTRA_LD_FLAGS="" if [[ $GOOS == "windows" ]]; then - # checks - if [[ $CC_FOR_windows_amd64 == "" ]]; then - echo "ENV variable CC_FOR_windows_amd64 (c compiler) is not set. Please set it to the cross compiler you want to use for compiling for windows_amd64" - exit 1 - fi - if [[ $CXX_FOR_windows_amd64 == "" ]]; then - echo "ENV variable CXX_FOR_windows_amd64 (c++ compiler) is not set. Please set it to the cross compiler you want to use for compiling for windows_amd64" - exit 1 - fi - # compilers - export CC=$CC_FOR_windows_amd64 - export CXX=$CXX_FOR_windows_amd64 - # custom export CGO_ENABLED=1 EXTRA_LD_FLAGS='-H windowsgui' # Hide console window by default (but we attach to parent console if available) # generate resource.syso for windows metadata / icon diff --git a/cmds/portmaster-start/main.go b/cmds/portmaster-start/main.go index 683c3bbf..9ebfcbdb 100644 --- a/cmds/portmaster-start/main.go +++ b/cmds/portmaster-start/main.go @@ -41,8 +41,8 @@ var ( Use: "portmaster-start", Short: "Start Portmaster components", PersistentPreRunE: func(cmd *cobra.Command, args []string) (err error) { - - if err := configureDataRoot(); err != nil { + mustLoadIndex := cmd == updatesCmd + if err := configureDataRoot(mustLoadIndex); err != nil { return err } @@ -130,7 +130,7 @@ func initCobra() { portlog.SetLogLevel(portlog.CriticalLevel) } -func configureDataRoot() error { +func configureDataRoot(mustLoadIndex bool) error { // The data directory is not // check for environment variable // PORTMASTER_DATA @@ -176,8 +176,7 @@ func configureDataRoot() error { // Beta: true, // }) - updateRegistryIndex() - return nil + return updateRegistryIndex(mustLoadIndex) } func configureLogging() error { @@ -196,10 +195,13 @@ func configureLogging() error { return nil } -func updateRegistryIndex() { +func updateRegistryIndex(mustLoadIndex bool) error { err := registry.LoadIndexes(context.Background()) if err != nil { log.Printf("WARNING: error loading indexes: %s\n", err) + if mustLoadIndex { + return err + } } err = registry.ScanStorage("") @@ -208,6 +210,7 @@ func updateRegistryIndex() { } registry.SelectVersions() + return nil } func detectInstallationDir() string { diff --git a/cmds/portmaster-start/run.go b/cmds/portmaster-start/run.go index f4f26974..0efd8fa6 100644 --- a/cmds/portmaster-start/run.go +++ b/cmds/portmaster-start/run.go @@ -188,7 +188,7 @@ func runAndRestart(opts *Options, args []string) error { // if we are constantly failing or a restart was requested // try to update the resources. log.Printf("updating registry index") - updateRegistryIndex() + updateRegistryIndex(false) } } } diff --git a/cmds/portmaster-start/show.go b/cmds/portmaster-start/show.go index f7044d31..64a6d3f0 100644 --- a/cmds/portmaster-start/show.go +++ b/cmds/portmaster-start/show.go @@ -16,7 +16,7 @@ var showCmd = &cobra.Command{ Use: "show", PersistentPreRunE: func(*cobra.Command, []string) error { // all show sub-commands need the data-root but no logging. - return configureDataRoot() + return configureDataRoot(false) }, Short: "Show the command to run a Portmaster component yourself", } diff --git a/cmds/portmaster-start/version.go b/cmds/portmaster-start/version.go index d2588a19..d5badc30 100644 --- a/cmds/portmaster-start/version.go +++ b/cmds/portmaster-start/version.go @@ -22,7 +22,7 @@ var versionCmd = &cobra.Command{ if showAllVersions { // if we are going to show all component versions // we need the dataroot to be configured. - if err := configureDataRoot(); err != nil { + if err := configureDataRoot(false); err != nil { return err } } From 81c6ed2906622343c69b3c03e48700ae7abaea8b Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 22 Jul 2020 15:44:56 +0200 Subject: [PATCH 13/15] Show warning on unexpected portmaster-start binary path --- cmds/portmaster-start/build | 13 +++++++++++++ updates/main.go | 8 +++++++- updates/upgrader.go | 18 ++++++++++-------- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/cmds/portmaster-start/build b/cmds/portmaster-start/build index dc0992a0..26fb3525 100755 --- a/cmds/portmaster-start/build +++ b/cmds/portmaster-start/build @@ -46,6 +46,19 @@ fi # build tools EXTRA_LD_FLAGS="" if [[ $GOOS == "windows" ]]; then + # checks + if [[ $CC_FOR_windows_amd64 == "" ]]; then + echo "ENV variable CC_FOR_windows_amd64 (c compiler) is not set. Please set it to the cross compiler you want to use for compiling for windows_amd64" + exit 1 + fi + if [[ $CXX_FOR_windows_amd64 == "" ]]; then + echo "ENV variable CXX_FOR_windows_amd64 (c++ compiler) is not set. Please set it to the cross compiler you want to use for compiling for windows_amd64" + exit 1 + fi + # compilers + export CC=$CC_FOR_windows_amd64 + export CXX=$CXX_FOR_windows_amd64 + # custom export CGO_ENABLED=1 EXTRA_LD_FLAGS='-H windowsgui' # Hide console window by default (but we attach to parent console if available) # generate resource.syso for windows metadata / icon diff --git a/updates/main.go b/updates/main.go index 6718b7e0..2d2310c7 100644 --- a/updates/main.go +++ b/updates/main.go @@ -185,7 +185,13 @@ func start() error { } // react to upgrades - return initUpgrader() + if err := initUpgrader(); err != nil { + return err + } + + warnOnIncorrectParentPath() + + return nil } // TriggerUpdate queues the update task to execute ASAP. diff --git a/updates/upgrader.go b/updates/upgrader.go index 281efb92..50aacfa5 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -148,13 +148,15 @@ func upgradePortmasterStart() error { } log.Infof("updates: upgraded %s", rootPmStartPath) - // TODO(ppacher): remove once we released a few more versions ... - warnOnIncorrectParentPath(filename) - return nil } -func warnOnIncorrectParentPath(expectedFileName string) { +func warnOnIncorrectParentPath() { + expectedFileName := "portmaster-start" + if onWindows { + expectedFileName += ".exe" + } + // upgrade parent process, if it's portmaster-start parent, err := processInfo.NewProcess(int32(os.Getppid())) if err != nil { @@ -191,10 +193,10 @@ func warnOnIncorrectParentPath(expectedFileName string) { if !strings.HasPrefix(absPath, root) { log.Warningf("detected unexpected path %s for portmaster-start", absPath) - // TODO(ppacher): once we released a new installer and folks had time - // to update we should send a module warning/hint to the - // UI notifying the user that he's using portmaster-start - // from a wrong location. + notifications.NotifyWarn( + "updates:unsupported-parent", + fmt.Sprintf("The portmaster has been launched by an unexpected %s binary at %s. Please configure your system to use the binary at %s as this version will be kept up to date automatically.", expectedFileName, absPath, filepath.Join(root, expectedFileName)), + ) } } From 8754eabfe0d2157d50b62e2d885d482e7b759c1b Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 22 Jul 2020 16:12:26 +0200 Subject: [PATCH 14/15] Fix --short not working correctly --- cmds/portmaster-start/version.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/cmds/portmaster-start/version.go b/cmds/portmaster-start/version.go index d5badc30..8b19b673 100644 --- a/cmds/portmaster-start/version.go +++ b/cmds/portmaster-start/version.go @@ -33,6 +33,7 @@ var versionCmd = &cobra.Command{ if !showAllVersions { if showShortVersion { fmt.Println(info.Version()) + return nil } fmt.Println(info.FullVersion()) @@ -40,7 +41,7 @@ var versionCmd = &cobra.Command{ } fmt.Printf("portmaster-start %s\n\n", info.Version()) - fmt.Printf("Components:\n") + fmt.Printf("Assets:\n") all := registry.Export() keys := make([]string, 0, len(all)) @@ -72,7 +73,7 @@ func init() { flags := versionCmd.Flags() { flags.BoolVar(&showShortVersion, "short", false, "Print only the version number.") - flags.BoolVar(&showAllVersions, "all", false, "Dump versions for all components.") + flags.BoolVar(&showAllVersions, "all", false, "Dump versions for all assets.") } rootCmd.AddCommand(versionCmd) From 92c25b2ed433d6a8c4b3c8c3f8cd1f3c68f8a69a Mon Sep 17 00:00:00 2001 From: Patrick Pacher Date: Wed, 22 Jul 2020 16:17:11 +0200 Subject: [PATCH 15/15] Fix linter warnings --- cmds/portmaster-start/run.go | 2 +- updates/upgrader.go | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/cmds/portmaster-start/run.go b/cmds/portmaster-start/run.go index 0efd8fa6..028f3de7 100644 --- a/cmds/portmaster-start/run.go +++ b/cmds/portmaster-start/run.go @@ -188,7 +188,7 @@ func runAndRestart(opts *Options, args []string) error { // if we are constantly failing or a restart was requested // try to update the resources. log.Printf("updating registry index") - updateRegistryIndex(false) + _ = updateRegistryIndex(false) // will always return nil } } } diff --git a/updates/upgrader.go b/updates/upgrader.go index 50aacfa5..88ca22a7 100644 --- a/updates/upgrader.go +++ b/updates/upgrader.go @@ -25,6 +25,7 @@ import ( const ( upgradedSuffix = "-upgraded" + exeExt = ".exe" ) var ( @@ -68,7 +69,7 @@ func upgrader(_ context.Context, _ interface{}) error { func upgradeCoreNotify() error { identifier := "core/portmaster-core" // identifier, use forward slash! if onWindows { - identifier += ".exe" + identifier += exeExt } // check if we can upgrade @@ -125,7 +126,7 @@ func upgradeCoreNotifyActionHandler(n *notifications.Notification) { func upgradePortmasterStart() error { filename := "portmaster-start" if onWindows { - filename += ".exe" + filename += exeExt } // check if we can upgrade @@ -154,7 +155,7 @@ func upgradePortmasterStart() error { func warnOnIncorrectParentPath() { expectedFileName := "portmaster-start" if onWindows { - expectedFileName += ".exe" + expectedFileName += exeExt } // upgrade parent process, if it's portmaster-start