diff --git a/cmd/containers-storage/copy.go b/cmd/containers-storage/copy.go index 558bfd86e..14a96c910 100644 --- a/cmd/containers-storage/copy.go +++ b/cmd/containers-storage/copy.go @@ -104,7 +104,7 @@ func init() { maxArgs: -1, action: copyContent, addFlags: func(flags *mflag.FlagSet, cmd *command) { - flags.StringVar(&chownOptions, []string{"-chown", ""}, chownOptions, "Set owner on new copies") + flags.StringVar(&chownOptions, []string{"-chown", "o"}, chownOptions, "Set owner on new copies") flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") }, }) diff --git a/cmd/containers-storage/create.go b/cmd/containers-storage/create.go index 3f5ddb82b..682d57946 100644 --- a/cmd/containers-storage/create.go +++ b/cmd/containers-storage/create.go @@ -1,14 +1,17 @@ package main import ( + "encoding/json" "fmt" "io" "os" "github.com/containers/storage" + graphdriver "github.com/containers/storage/drivers" "github.com/containers/storage/internal/opts" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/mflag" + "github.com/containers/storage/pkg/stringid" "github.com/containers/storage/types" digest "github.com/opencontainers/go-digest" ) @@ -71,6 +74,45 @@ func paramIDMapping() (*types.IDMappingOptions, error) { return &options, nil } +func createStorageLayer(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) { + parent := "" + if len(args) > 0 { + parent = args[0] + } + mappings, err := paramIDMapping() + if err != nil { + return 1, err + } + driver, err := m.GraphDriver() + if err != nil { + return 1, err + } + opts := graphdriver.CreateOpts{ + MountLabel: paramMountLabel, + IDMappings: idtools.NewIDMappingsFromMaps(mappings.UIDMap, mappings.GIDMap), + } + if paramID == "" { + paramID = stringid.GenerateNonCryptoID() + } + if paramCreateRO { + if err := driver.Create(paramID, parent, &opts); err != nil { + return 1, err + } + } else { + if err := driver.CreateReadWrite(paramID, parent, &opts); err != nil { + return 1, err + } + } + if jsonOutput { + if err := json.NewEncoder(os.Stdout).Encode(paramID); err != nil { + return 1, err + } + } else { + fmt.Printf("%s\n", paramID) + } + return 0, nil +} + func createLayer(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) { parent := "" if len(args) > 0 { @@ -194,6 +236,26 @@ func createContainer(flags *mflag.FlagSet, action string, m storage.Store, args } func init() { + commands = append(commands, command{ + names: []string{"create-storage-layer"}, + optionsHelp: "[options [...]] [parentLayerNameOrID]", + usage: "Create a new layer only in the storage driver", + minArgs: 0, + maxArgs: 1, + action: createStorageLayer, + addFlags: func(flags *mflag.FlagSet, cmd *command) { + flags.StringVar(¶mMountLabel, []string{"-label", "l"}, "", "Mount Label") + flags.StringVar(¶mID, []string{"-id", "i"}, "", "Layer ID") + flags.BoolVar(¶mCreateRO, []string{"-readonly", "r"}, false, "Mark as read-only") + flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") + flags.BoolVar(¶mHostUIDMap, []string{"-hostuidmap"}, paramHostUIDMap, "Force host UID map") + flags.BoolVar(¶mHostGIDMap, []string{"-hostgidmap"}, paramHostGIDMap, "Force host GID map") + flags.StringVar(¶mUIDMap, []string{"-uidmap"}, "", "UID map") + flags.StringVar(¶mGIDMap, []string{"-gidmap"}, "", "GID map") + flags.StringVar(¶mSubUIDMap, []string{"-subuidmap"}, "", "subuid UID map for a user") + flags.StringVar(¶mSubGIDMap, []string{"-subgidmap"}, "", "subgid GID map for a group") + }, + }) commands = append(commands, command{ names: []string{"create-layer", "createlayer"}, optionsHelp: "[options [...]] [parentLayerNameOrID]", diff --git a/cmd/containers-storage/layers.go b/cmd/containers-storage/layers.go index 673cd6ccb..11e4cbfc5 100644 --- a/cmd/containers-storage/layers.go +++ b/cmd/containers-storage/layers.go @@ -1,13 +1,39 @@ package main import ( + "encoding/json" "fmt" + "os" "github.com/containers/storage" "github.com/containers/storage/pkg/mflag" ) -var listLayersTree = false +var ( + listLayersTree = false + listLayersQuick = false +) + +func storageLayers(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) { + driver, err := m.GraphDriver() + if err != nil { + return 1, err + } + layers, err := driver.ListLayers() + if err != nil { + return 1, err + } + if jsonOutput { + if err := json.NewEncoder(os.Stdout).Encode(layers); err != nil { + return 1, err + } + return 0, nil + } + for _, layer := range layers { + fmt.Printf("%s\n", layer) + } + return 0, nil +} func layers(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) { layers, err := m.Layers() @@ -71,6 +97,9 @@ func layers(flags *mflag.FlagSet, action string, m storage.Store, args []string) nodes = append(nodes, node) } else { fmt.Printf("%s\n", layer.ID) + if listLayersQuick { + continue + } for _, name := range layer.Names { fmt.Printf("\tname: %s\n", name) } @@ -106,6 +135,18 @@ func init() { maxArgs: 0, addFlags: func(flags *mflag.FlagSet, cmd *command) { flags.BoolVar(&listLayersTree, []string{"-tree", "t"}, listLayersTree, "Use a tree") + flags.BoolVar(&listLayersQuick, []string{"-quick", "q"}, listLayersTree, "Just the IDs") + flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") + }, + }) + commands = append(commands, command{ + names: []string{"storage-layers"}, + optionsHelp: "[options [...]]", + usage: "List storage layers", + action: storageLayers, + minArgs: 0, + maxArgs: 0, + addFlags: func(flags *mflag.FlagSet, cmd *command) { flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") }, }) diff --git a/cmd/containers-storage/main.go b/cmd/containers-storage/main.go index 804ca725e..8860b9e95 100644 --- a/cmd/containers-storage/main.go +++ b/cmd/containers-storage/main.go @@ -9,6 +9,7 @@ import ( "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" ) @@ -36,6 +37,7 @@ func main() { options := types.StoreOptions{} debug := false + doUnshare := false makeFlags := func(command string, eh mflag.ErrorHandling) *mflag.FlagSet { flags := mflag.NewFlagSet(command, eh) @@ -45,6 +47,7 @@ func main() { 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 } @@ -68,7 +71,6 @@ func main() { flags.Usage() os.Exit(1) } - if options.GraphRoot == "" && options.RunRoot == "" && options.GraphDriverName == "" && len(options.GraphDriverOptions) == 0 { options, _ = types.DefaultStoreOptionsAutoDetectUID() } @@ -112,6 +114,9 @@ func main() { flags.Usage() os.Exit(1) } + if doUnshare { + unshare.MaybeReexecUsingUserNamespace(true) + } if debug { logrus.SetLevel(logrus.DebugLevel) logrus.Debugf("Root: %s", options.GraphRoot) diff --git a/cmd/containers-storage/mount.go b/cmd/containers-storage/mount.go index 3f4c1e7f8..3ba8034a0 100644 --- a/cmd/containers-storage/mount.go +++ b/cmd/containers-storage/mount.go @@ -138,7 +138,7 @@ func init() { addFlags: func(flags *mflag.FlagSet, cmd *command) { flags.StringVar(¶mMountOptions, []string{"-opt", "o"}, "", "Mount Options") flags.StringVar(¶mMountLabel, []string{"-label", "l"}, "", "Mount Label") - flags.BoolVar(¶mReadOnly, []string{"-ro", "r"}, paramReadOnly, "Mount image readonly") + flags.BoolVar(¶mReadOnly, []string{"-read-only", "-ro", "r"}, paramReadOnly, "Mount image readonly") flags.BoolVar(&jsonOutput, []string{"-json", "j"}, jsonOutput, "Prefer JSON output") }, }) diff --git a/cmd/containers-storage/unshare.go b/cmd/containers-storage/unshare.go new file mode 100644 index 000000000..56273f838 --- /dev/null +++ b/cmd/containers-storage/unshare.go @@ -0,0 +1,38 @@ +package main + +import ( + "os" + "os/exec" + + "github.com/containers/storage" + "github.com/containers/storage/pkg/mflag" + "github.com/containers/storage/pkg/unshare" +) + +func unshareFn(flags *mflag.FlagSet, action string, m storage.Store, args []string) (int, error) { + unshare.MaybeReexecUsingUserNamespace(true) + if len(args) == 0 { + shell := os.Getenv("SHELL") + if shell == "" { + shell = "/bin/sh" + } + args = []string{shell} + } + cmd := exec.Command(args[0], args[1:]...) + cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr + if err := cmd.Run(); err != nil { + return 1, err + } + return 0, nil +} + +func init() { + commands = append(commands, command{ + names: []string{"unshare"}, + usage: "Run a command in a user namespace", + optionsHelp: "[command [...]]", + minArgs: 0, + maxArgs: -1, + action: unshareFn, + }) +} diff --git a/docs/containers-storage-create-layer.md b/docs/containers-storage-create-layer.md index 851b7cf18..4f97dcff9 100644 --- a/docs/containers-storage-create-layer.md +++ b/docs/containers-storage-create-layer.md @@ -20,14 +20,6 @@ returned. Sets the ID for the layer. If none is specified, one is generated. -**-m | --metadata** *metadata-value* - -Sets the metadata for the layer to the specified value. - -**-f | --metadata-file** *metadata-file* - -Sets the metadata for the layer to the contents of the specified file. - **-l | --label** *mount-label* Sets the label which should be assigned as an SELinux context when mounting the diff --git a/docs/containers-storage-create-storage-layer.md b/docs/containers-storage-create-storage-layer.md new file mode 100644 index 000000000..ed29af2be --- /dev/null +++ b/docs/containers-storage-create-storage-layer.md @@ -0,0 +1,29 @@ +## containers-storage-create-storage-layer 1 "September 2022" + +## NAME +containers-storage create-storage-layer - Create a layer in a lower-level storage driver + +## SYNOPSIS +**containers-storage** **create-storage-layer** [*options* [...]] [*parentLayerNameOrID*] + +## DESCRIPTION +Creates a new layer using the lower-level storage driver which either has a +specified layer as its parent, or if no parent is specified, is empty. + +## OPTIONS +**-i | --id** *ID* + +Sets the ID for the layer. If none is specified, one is generated. + +**-l | --label** *mount-label* + +Sets the label which should be assigned as an SELinux context when mounting the +layer. + +## EXAMPLE +**containers-storage create-storage-layer somelayer** + +## SEE ALSO +containers-storage-create-container(1) +containers-storage-create-image(1) +containers-storage-delete-layer(1) diff --git a/docs/containers-storage-unshare.md b/docs/containers-storage-unshare.md new file mode 100644 index 000000000..97b9fe4e0 --- /dev/null +++ b/docs/containers-storage-unshare.md @@ -0,0 +1,17 @@ +## containers-storage-unshare 1 "September 2022" + +## NAME +containers-storage unshare - Run a command in a user namespace + +## SYNOPSIS +**containers-storage** **unshare** [command [...]] + +## DESCRIPTION +Sets up a user namespace using mappings configured for the current user and runs +either a specified command or the current user's shell. + +## EXAMPLE +**containers-storage unshare id** + +## SEE ALSO +containers-storage-status(1) diff --git a/docs/containers-storage.md b/docs/containers-storage.md index 96d281008..a7682c94d 100644 --- a/docs/containers-storage.md +++ b/docs/containers-storage.md @@ -68,6 +68,8 @@ The *containers-storage* command's features are broken down into several subcomm **containers-storage create-layer(1)** Create a new layer + **containers-storage create-storage-layer(1)** Create a new layer in the lower-level storage driver + **containers-storage delete(1)** Delete a layer or image or container, with no safety checks **containers-storage delete-container(1)** Delete a container, with safety checks @@ -116,6 +118,8 @@ The *containers-storage* command's features are broken down into several subcomm **containers-storage unmount(1)** Unmount a layer or container + **containers-storage unshare(1)** Run a command in a user namespace + **containers-storage version(1)** Return containers-storage version information **containers-storage wipe(1)** Wipe all layers, images, and containers @@ -155,6 +159,11 @@ Set options which will be passed to the storage driver. If not set, but comma-separated list and used instead. If the storage tree has previously been initialized, these need not be provided. +**--unshare, -U** + +When started by a non-root user, run inside of a new user namespace configured +using the system's default ID mappings for the non-root user. + ## ENVIRONMENT OVERRIDES **CONTAINERS_STORAGE_CONF**