package main import ( "encoding/json" "fmt" "os" "slices" "github.com/containers/storage" "github.com/containers/storage/internal/opts" "github.com/containers/storage/pkg/mflag" "github.com/containers/storage/pkg/reexec" "github.com/containers/storage/pkg/unshare" "github.com/containers/storage/types" "github.com/sirupsen/logrus" ) type command struct { names []string optionsHelp string minArgs int maxArgs int usage string addFlags func(*mflag.FlagSet, *command) action func(*mflag.FlagSet, string, storage.Store, []string) (int, error) } var ( commands = []command{} jsonOutput = false force = false ) func main() { if reexec.Init() { return } options := types.StoreOptions{} debug := false doUnshare := false makeFlags := func(command string, eh mflag.ErrorHandling) *mflag.FlagSet { flags := mflag.NewFlagSet(command, eh) flags.StringVar(&options.RunRoot, []string{"-run", "R"}, options.RunRoot, "Root of the runtime state tree") flags.StringVar(&options.GraphRoot, []string{"-graph", "g"}, options.GraphRoot, "Root of the storage tree") flags.StringVar(&options.ImageStore, []string{"-image-store"}, options.ImageStore, "Root of the separate image store") flags.BoolVar(&options.TransientStore, []string{"-transient-store"}, options.TransientStore, "Transient store") flags.StringVar(&options.GraphDriverName, []string{"-storage-driver", "s"}, options.GraphDriverName, "Storage driver to use ($STORAGE_DRIVER)") flags.Var(opts.NewListOptsRef(&options.GraphDriverOptions, nil), []string{"-storage-opt"}, "Set storage driver options ($STORAGE_OPTS)") flags.BoolVar(&debug, []string{"-debug", "D"}, debug, "Print debugging information") flags.BoolVar(&doUnshare, []string{"-unshare", "U"}, unshare.IsRootless(), fmt.Sprintf("Run in a user namespace (default %t)", unshare.IsRootless())) return flags } flags := makeFlags("containers-storage", mflag.ContinueOnError) flags.Usage = func() { fmt.Printf("Usage: containers-storage command [options [...]]\n\n") fmt.Printf("Commands:\n\n") for _, command := range commands { fmt.Printf(" %-30s%s\n", command.names[0], command.usage) } fmt.Printf("\nOptions:\n") flags.PrintDefaults() } if len(os.Args) < 2 { flags.Usage() os.Exit(1) } if err := flags.ParseFlags(os.Args[1:], true); err != nil { fmt.Printf("%v while parsing arguments (1)\n", err) flags.Usage() os.Exit(1) } if options.GraphRoot == "" && options.RunRoot == "" && options.GraphDriverName == "" && len(options.GraphDriverOptions) == 0 { options, _ = types.DefaultStoreOptions() } args := flags.Args() if len(args) < 1 { flags.Usage() os.Exit(1) return } name := args[0] command := func() *command { for i := range commands { if slices.Contains(commands[i].names, name) { return &commands[i] } } fmt.Printf("%s: unrecognized command.\n", name) os.Exit(1) return nil // To satisfy linters. }() flags = makeFlags(name, mflag.ExitOnError) if command.addFlags != nil { command.addFlags(flags, command) } flags.Usage = func() { fmt.Printf("Usage: containers-storage %s %s\n\n", name, command.optionsHelp) fmt.Printf("%s\n", command.usage) fmt.Printf("\nOptions:\n") flags.PrintDefaults() } if err := flags.ParseFlags(args[1:], false); err != nil { fmt.Printf("%v while parsing arguments (3)", err) flags.Usage() os.Exit(1) } args = flags.Args() if command.minArgs != 0 && len(args) < command.minArgs { fmt.Printf("%s: more arguments required.\n", name) flags.Usage() os.Exit(1) } if command.maxArgs >= 0 && command.maxArgs < command.minArgs { panic(fmt.Sprintf("command %v requires more args (%d) than it allows (%d)", command.names, command.minArgs, command.maxArgs)) } if command.maxArgs >= 0 && len(args) > command.maxArgs { fmt.Printf("%s: too many arguments (%s).\n", name, args) flags.Usage() os.Exit(1) } if doUnshare { unshare.MaybeReexecUsingUserNamespace(true) } if debug { logrus.SetLevel(logrus.DebugLevel) logrus.Debugf("Root: %s", options.GraphRoot) logrus.Debugf("Run Root: %s", options.RunRoot) logrus.Debugf("Driver Name: %s", options.GraphDriverName) logrus.Debugf("Driver Options: %s", options.GraphDriverOptions) } else { logrus.SetLevel(logrus.ErrorLevel) } store, err := storage.GetStore(options) if err != nil { fmt.Printf("error initializing: %+v\n", err) os.Exit(1) } store.Free() res, err := command.action(flags, name, store, args) if err != nil { fmt.Fprintf(os.Stderr, "%+v\n", err) } os.Exit(res) } // outputJSON formats its input as JSON to stdout, and returns values suitable // for directly returning from command.action func outputJSON(data any) (int, error) { if err := json.NewEncoder(os.Stdout).Encode(data); err != nil { return 1, err } return 0, nil }