From 196f98dd91ef70b4d38bad7d818e9f63e857e63c Mon Sep 17 00:00:00 2001 From: jagadam97 Date: Sun, 13 Jul 2025 05:01:43 +0530 Subject: [PATCH] feat: better error handling for sys calls --- cmd/cmd.go | 10 ++------ cmd/cmds_add.go | 3 ++- cmd/cmds_ls.go | 3 ++- cmd/cmds_rm.go | 3 ++- cmd/config_cat.go | 3 ++- cmd/config_export.go | 3 ++- cmd/config_import.go | 3 ++- cmd/config_init.go | 3 ++- cmd/config_set.go | 3 ++- cmd/root.go | 30 ++++++++++++++++++++---- cmd/rule_rm.go | 3 ++- cmd/rules_add.go | 3 ++- cmd/rules_ls.go | 3 ++- cmd/users_add.go | 3 ++- cmd/users_export.go | 3 ++- cmd/users_find.go | 7 +++--- cmd/users_import.go | 3 ++- cmd/users_rm.go | 3 ++- cmd/users_update.go | 3 ++- cmd/utils.go | 11 +++++---- errors/errors.go | 54 ++++++++++++++++++++++++++++++++++++++++++++ main.go | 7 +++++- 22 files changed, 130 insertions(+), 37 deletions(-) diff --git a/cmd/cmd.go b/cmd/cmd.go index 18f52337..2dc02107 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -1,12 +1,6 @@ package cmd -import ( - "log" -) - // Execute executes the commands. -func Execute() { - if err := rootCmd.Execute(); err != nil { - log.Fatal(err) - } +func Execute() error { + return rootCmd.Execute() } diff --git a/cmd/cmds_add.go b/cmd/cmds_add.go index 63571ba6..12a2b201 100644 --- a/cmd/cmds_add.go +++ b/cmd/cmds_add.go @@ -15,7 +15,7 @@ var cmdsAddCmd = &cobra.Command{ Short: "Add a command to run on a specific event", Long: `Add a command to run on a specific event.`, Args: cobra.MinimumNArgs(2), - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { s, err := d.store.Settings.Get() checkErr(err) command := strings.Join(args[1:], " ") @@ -23,5 +23,6 @@ var cmdsAddCmd = &cobra.Command{ err = d.store.Settings.Save(s) checkErr(err) printEvents(s.Commands) + return nil }, pythonConfig{}), } diff --git a/cmd/cmds_ls.go b/cmd/cmds_ls.go index 6d19c846..409a1bc0 100644 --- a/cmd/cmds_ls.go +++ b/cmd/cmds_ls.go @@ -14,7 +14,7 @@ var cmdsLsCmd = &cobra.Command{ Short: "List all commands for each event", Long: `List all commands for each event.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { s, err := d.store.Settings.Get() checkErr(err) evt := mustGetString(cmd.Flags(), "event") @@ -27,5 +27,6 @@ var cmdsLsCmd = &cobra.Command{ show["after_"+evt] = s.Commands["after_"+evt] printEvents(show) } + return nil }, pythonConfig{}), } diff --git a/cmd/cmds_rm.go b/cmd/cmds_rm.go index 7f187f7f..83972ee3 100644 --- a/cmd/cmds_rm.go +++ b/cmd/cmds_rm.go @@ -35,7 +35,7 @@ including 'index_end'.`, return nil }, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { s, err := d.store.Settings.Get() checkErr(err) evt := args[0] @@ -52,5 +52,6 @@ including 'index_end'.`, err = d.store.Settings.Save(s) checkErr(err) printEvents(s.Commands) + return nil }, pythonConfig{}), } diff --git a/cmd/config_cat.go b/cmd/config_cat.go index 8aaf05c3..507dfc6e 100644 --- a/cmd/config_cat.go +++ b/cmd/config_cat.go @@ -13,7 +13,7 @@ var configCatCmd = &cobra.Command{ Short: "Prints the configuration", Long: `Prints the configuration.`, Args: cobra.NoArgs, - Run: python(func(_ *cobra.Command, _ []string, d pythonData) { + RunE: python(func(_ *cobra.Command, _ []string, d *pythonData) error { set, err := d.store.Settings.Get() checkErr(err) ser, err := d.store.Settings.GetServer() @@ -21,5 +21,6 @@ var configCatCmd = &cobra.Command{ auther, err := d.store.Auth.Get(set.AuthMethod) checkErr(err) printSettings(ser, set, auther) + return nil }, pythonConfig{}), } diff --git a/cmd/config_export.go b/cmd/config_export.go index 6472bbe6..aafb1f17 100644 --- a/cmd/config_export.go +++ b/cmd/config_export.go @@ -15,7 +15,7 @@ var configExportCmd = &cobra.Command{ json or yaml file. This exported configuration can be changed, and imported again with 'config import' command.`, Args: jsonYamlArg, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { settings, err := d.store.Settings.Get() checkErr(err) @@ -33,5 +33,6 @@ and imported again with 'config import' command.`, err = marshal(args[0], data) checkErr(err) + return nil }, pythonConfig{}), } diff --git a/cmd/config_import.go b/cmd/config_import.go index 6c609481..baef7c29 100644 --- a/cmd/config_import.go +++ b/cmd/config_import.go @@ -34,7 +34,7 @@ database. The path must be for a json or yaml file.`, Args: jsonYamlArg, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { var key []byte if d.hadDB { settings, err := d.store.Settings.Get() @@ -80,6 +80,7 @@ The path must be for a json or yaml file.`, checkErr(err) printSettings(file.Server, file.Settings, auther) + return nil }, pythonConfig{allowNoDB: true}), } diff --git a/cmd/config_init.go b/cmd/config_init.go index d9710514..c57008ef 100644 --- a/cmd/config_init.go +++ b/cmd/config_init.go @@ -22,7 +22,7 @@ this options can be changed in the future with the command to the defaults when creating new users and you don't override the options.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { defaults := settings.UserDefaults{} flags := cmd.Flags() getUserDefaults(flags, &defaults, true) @@ -69,5 +69,6 @@ Now add your first user via 'filebrowser users add' and then you just need to call the main command to boot up the server. `) printSettings(ser, s, auther) + return nil }, pythonConfig{noDB: true}), } diff --git a/cmd/config_set.go b/cmd/config_set.go index 05816795..d7256d9f 100644 --- a/cmd/config_set.go +++ b/cmd/config_set.go @@ -16,7 +16,7 @@ var configSetCmd = &cobra.Command{ Long: `Updates the configuration. Set the flags for the options you want to change. Other options will remain unchanged.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { flags := cmd.Flags() set, err := d.store.Settings.Get() checkErr(err) @@ -84,5 +84,6 @@ you want to change. Other options will remain unchanged.`, err = d.store.Settings.SaveServer(ser) checkErr(err) printSettings(ser, set, auther) + return nil }, pythonConfig{}), } diff --git a/cmd/root.go b/cmd/root.go index 4b6819b7..f9c5fcb4 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -25,6 +25,7 @@ import ( "github.com/filebrowser/filebrowser/v2/auth" "github.com/filebrowser/filebrowser/v2/diskcache" + fbErrors "github.com/filebrowser/filebrowser/v2/errors" "github.com/filebrowser/filebrowser/v2/frontend" fbhttp "github.com/filebrowser/filebrowser/v2/http" "github.com/filebrowser/filebrowser/v2/img" @@ -39,6 +40,7 @@ var ( func init() { cobra.OnInitialize(initConfig) + rootCmd.SilenceUsage = true cobra.MousetrapHelpText = "" rootCmd.SetVersionTemplate("File Browser version {{printf \"%s\" .Version}}\n") @@ -112,11 +114,11 @@ set FB_DATABASE. Also, if the database path doesn't exist, File Browser will enter into the quick setup mode and a new database will be bootstrapped and a new user created with the credentials from options "username" and "password".`, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { log.Println(cfgFile) if !d.hadDB { - quickSetup(cmd.Flags(), d) + quickSetup(cmd.Flags(), *d) } // build img service @@ -194,8 +196,15 @@ user created with the credentials from options "username" and "password".`, }() sigc := make(chan os.Signal, 1) - signal.Notify(sigc, os.Interrupt, syscall.SIGTERM) - <-sigc + signal.Notify(sigc, + os.Interrupt, + syscall.SIGHUP, + syscall.SIGINT, + syscall.SIGTERM, + syscall.SIGQUIT, + ) + sig := <-sigc + log.Println("Got signal:", sig) shutdownCtx, shutdownRelease := context.WithTimeout(context.Background(), 10*time.Second) //nolint:mnd defer shutdownRelease() @@ -204,6 +213,19 @@ user created with the credentials from options "username" and "password".`, log.Fatalf("HTTP shutdown error: %v", err) } log.Println("Graceful shutdown complete.") + + switch sig { + case syscall.SIGHUP: + d.err = fbErrors.ErrSighup + case syscall.SIGINT: + d.err = fbErrors.ErrSigint + case syscall.SIGQUIT: + d.err = fbErrors.ErrSigquit + case syscall.SIGTERM: + d.err = fbErrors.ErrSigTerm + } + + return d.err }, pythonConfig{allowNoDB: true}), } diff --git a/cmd/rule_rm.go b/cmd/rule_rm.go index 4b7ba851..74175e4a 100644 --- a/cmd/rule_rm.go +++ b/cmd/rule_rm.go @@ -40,7 +40,7 @@ including 'index_end'.`, return nil }, - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { i, err := strconv.Atoi(args[0]) checkErr(err) f := i @@ -62,5 +62,6 @@ including 'index_end'.`, } runRules(d.store, cmd, user, global) + return nil }, pythonConfig{}), } diff --git a/cmd/rules_add.go b/cmd/rules_add.go index fcdc7fb4..5005ad66 100644 --- a/cmd/rules_add.go +++ b/cmd/rules_add.go @@ -21,7 +21,7 @@ var rulesAddCmd = &cobra.Command{ Short: "Add a global rule or user rule", Long: `Add a global rule or user rule.`, Args: cobra.ExactArgs(1), - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { allow := mustGetBool(cmd.Flags(), "allow") regex := mustGetBool(cmd.Flags(), "regex") exp := args[0] @@ -54,5 +54,6 @@ var rulesAddCmd = &cobra.Command{ } runRules(d.store, cmd, user, global) + return nil }, pythonConfig{}), } diff --git a/cmd/rules_ls.go b/cmd/rules_ls.go index 0a8ed721..fa017dc2 100644 --- a/cmd/rules_ls.go +++ b/cmd/rules_ls.go @@ -13,7 +13,8 @@ var rulesLsCommand = &cobra.Command{ Short: "List global rules or user specific rules", Long: `List global rules or user specific rules.`, Args: cobra.NoArgs, - Run: python(func(cmd *cobra.Command, _ []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, _ []string, d *pythonData) error { runRules(d.store, cmd, nil, nil) + return nil }, pythonConfig{}), } diff --git a/cmd/users_add.go b/cmd/users_add.go index c3b8af28..66a61088 100644 --- a/cmd/users_add.go +++ b/cmd/users_add.go @@ -16,7 +16,7 @@ var usersAddCmd = &cobra.Command{ Short: "Create a new user", Long: `Create a new user and add it to the database.`, Args: cobra.ExactArgs(2), - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { s, err := d.store.Settings.Get() checkErr(err) getUserDefaults(cmd.Flags(), &s.Defaults, false) @@ -47,5 +47,6 @@ var usersAddCmd = &cobra.Command{ err = d.store.Users.Save(user) checkErr(err) printUsers([]*users.User{user}) + return nil }, pythonConfig{}), } diff --git a/cmd/users_export.go b/cmd/users_export.go index 3b3798ad..65462727 100644 --- a/cmd/users_export.go +++ b/cmd/users_export.go @@ -14,11 +14,12 @@ var usersExportCmd = &cobra.Command{ Long: `Export all users to a json or yaml file. Please indicate the path to the file where you want to write the users.`, Args: jsonYamlArg, - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { list, err := d.store.Users.Gets("") checkErr(err) err = marshal(args[0], list) checkErr(err) + return nil }, pythonConfig{}), } diff --git a/cmd/users_find.go b/cmd/users_find.go index 1f6e40c0..9bdad2a1 100644 --- a/cmd/users_find.go +++ b/cmd/users_find.go @@ -16,17 +16,17 @@ var usersFindCmd = &cobra.Command{ Short: "Find a user by username or id", Long: `Find a user by username or id. If no flag is set, all users will be printed.`, Args: cobra.ExactArgs(1), - Run: findUsers, + RunE: findUsers, } var usersLsCmd = &cobra.Command{ Use: "ls", Short: "List all users.", Args: cobra.NoArgs, - Run: findUsers, + RunE: findUsers, } -var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) { +var findUsers = python(func(_ *cobra.Command, args []string, d *pythonData) error { var ( list []*users.User user *users.User @@ -48,4 +48,5 @@ var findUsers = python(func(_ *cobra.Command, args []string, d pythonData) { checkErr(err) printUsers(list) + return nil }, pythonConfig{}) diff --git a/cmd/users_import.go b/cmd/users_import.go index dee9d759..8a40c0bd 100644 --- a/cmd/users_import.go +++ b/cmd/users_import.go @@ -25,7 +25,7 @@ file. You can use this command to import new users to your installation. For that, just don't place their ID on the files list or set it to 0.`, Args: jsonYamlArg, - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { fd, err := os.Open(args[0]) checkErr(err) defer fd.Close() @@ -80,6 +80,7 @@ list or set it to 0.`, err = d.store.Users.Save(user) checkErr(err) } + return nil }, pythonConfig{}), } diff --git a/cmd/users_rm.go b/cmd/users_rm.go index 9041aa1b..fa5a216c 100644 --- a/cmd/users_rm.go +++ b/cmd/users_rm.go @@ -15,7 +15,7 @@ var usersRmCmd = &cobra.Command{ Short: "Delete a user by username or id", Long: `Delete a user by username or id`, Args: cobra.ExactArgs(1), - Run: python(func(_ *cobra.Command, args []string, d pythonData) { + RunE: python(func(_ *cobra.Command, args []string, d *pythonData) error { username, id := parseUsernameOrID(args[0]) var err error @@ -27,5 +27,6 @@ var usersRmCmd = &cobra.Command{ checkErr(err) fmt.Println("user deleted successfully") + return nil }, pythonConfig{}), } diff --git a/cmd/users_update.go b/cmd/users_update.go index 2c58c4af..3ed440ff 100644 --- a/cmd/users_update.go +++ b/cmd/users_update.go @@ -21,7 +21,7 @@ var usersUpdateCmd = &cobra.Command{ Long: `Updates an existing user. Set the flags for the options you want to change.`, Args: cobra.ExactArgs(1), - Run: python(func(cmd *cobra.Command, args []string, d pythonData) { + RunE: python(func(cmd *cobra.Command, args []string, d *pythonData) error { username, id := parseUsernameOrID(args[0]) flags := cmd.Flags() password := mustGetString(flags, "password") @@ -73,5 +73,6 @@ options you want to change.`, err = d.store.Users.Update(user) checkErr(err) printUsers([]*users.User{user}) + return nil }, pythonConfig{}), } diff --git a/cmd/utils.go b/cmd/utils.go index 909a1558..e1e3c32c 100644 --- a/cmd/utils.go +++ b/cmd/utils.go @@ -50,8 +50,8 @@ func generateKey() []byte { return k } -type cobraFunc func(cmd *cobra.Command, args []string) -type pythonFunc func(cmd *cobra.Command, args []string, data pythonData) +type cobraFunc func(cmd *cobra.Command, args []string) error +type pythonFunc func(cmd *cobra.Command, args []string, data *pythonData) error type pythonConfig struct { noDB bool @@ -61,6 +61,7 @@ type pythonConfig struct { type pythonData struct { hadDB bool store *storage.Storage + err error } func dbExists(path string) (bool, error) { @@ -84,8 +85,8 @@ func dbExists(path string) (bool, error) { } func python(fn pythonFunc, cfg pythonConfig) cobraFunc { - return func(cmd *cobra.Command, args []string) { - data := pythonData{hadDB: true} + return func(cmd *cobra.Command, args []string) error { + data := &pythonData{hadDB: true} path := getStringParam(cmd.Flags(), "database") absPath, err := filepath.Abs(path) @@ -111,7 +112,7 @@ func python(fn pythonFunc, cfg pythonConfig) cobraFunc { defer db.Close() data.store, err = bolt.NewStorage(db) checkErr(err) - fn(cmd, args, data) + return fn(cmd, args, data) } } diff --git a/errors/errors.go b/errors/errors.go index 5fd760c2..f8abee59 100644 --- a/errors/errors.go +++ b/errors/errors.go @@ -3,6 +3,15 @@ package errors import ( "errors" "fmt" + "os" + "syscall" +) + +const ( + ExitCodeSigTerm = 128 + int(syscall.SIGTERM) + ExitCodeSighup = 128 + int(syscall.SIGHUP) + ExitCodeSigint = 128 + int(syscall.SIGINT) + ExitCodeSigquit = 128 + int(syscall.SIGQUIT) ) var ( @@ -22,6 +31,10 @@ var ( ErrInvalidRequestParams = errors.New("invalid request params") ErrSourceIsParent = errors.New("source is parent") ErrRootUserDeletion = errors.New("user with id 1 can't be deleted") + ErrSigTerm = errors.New("exit on signal: sigterm") + ErrSighup = errors.New("exit on signal: sighup") + ErrSigint = errors.New("exit on signal: sigint") + ErrSigquit = errors.New("exit on signal: sigquit") ) type ErrShortPassword struct { @@ -31,3 +44,44 @@ type ErrShortPassword struct { func (e ErrShortPassword) Error() string { return fmt.Sprintf("password is too short, minimum length is %d", e.MinimumLength) } + +// GetExitCode returns the exit code for a given error. +func GetExitCode(err error) int { + if err == nil { + return 0 + } + + exitCodeMap := map[error]int{ + ErrSigTerm: ExitCodeSigTerm, + ErrSighup: ExitCodeSighup, + ErrSigint: ExitCodeSigint, + ErrSigquit: ExitCodeSigquit, + } + + for e, code := range exitCodeMap { + if errors.Is(err, e) { + return code + } + } + + if exitErr, ok := err.(interface{ ExitCode() int }); ok { + return exitErr.ExitCode() + } + + var pathErr *os.PathError + if errors.As(err, &pathErr) { + return 1 + } + + var syscallErr *os.SyscallError + if errors.As(err, &syscallErr) { + return 1 + } + + var errno syscall.Errno + if errors.As(err, &errno) { + return 1 + } + + return 1 +} diff --git a/main.go b/main.go index ab22ef45..d17550c9 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,14 @@ package main import ( + "os" + "github.com/filebrowser/filebrowser/v2/cmd" + "github.com/filebrowser/filebrowser/v2/errors" ) func main() { - cmd.Execute() + if err := cmd.Execute(); err != nil { + os.Exit(errors.GetExitCode(err)) + } }