Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions docs/containers-storage.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,19 @@ The `storage` table supports the following options:
container storage graph dir (default: "/var/lib/containers/storage")
Default directory to store all writable content created by container storage programs.

**rootless_storage_path**="$HOME/.local/share/containers/storage"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the RHS of the = supposed to be? In all other options it's simply an empty string "". Why is this one different? Especially why is it misleadingly different? The = here implies that it's a default, but right below it says otherwise.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the value of rootless_storage_path will not be used unless the user overwrites it in storage.conf. I think it can be described as "". But I'n not sure. I need ask @rhatdan

Storage path for rootless users. By default the graphroot for rootless users
is set to `$XDG_DATA_HOME/containers/storage`, if XDG_DATA_HOME is set.
Otherwise `$HOME/.local/share/containers/storage` is used. This field can
be used if administrators need to change the storage location for all users.

The rootless storage path supports three substations:
* `$HOME` => Replaced by the users home directory.
* `$UID` => Replaced by the users UID
* `$USER` => Replaced by the users name

A common use case for this field is to provide a local storage directory when user home directories are NFS-mounted (podman does not support container storage over NFS).

**runroot**=""
container storage run dir (default: "/var/run/containers/storage")
Default directory to store all temporary writable content created by container storage programs.
Expand Down
6 changes: 5 additions & 1 deletion storage.conf
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ runroot = "/var/run/containers/storage"
# Primary Read/Write location of container storage
graphroot = "/var/lib/containers/storage"

# Storage path for rootless users
#
# rootless_storage_path = "$HOME/.local/share/containers/storage"

[storage.options]
# Storage options to be passed to underlying storage drivers

Expand Down Expand Up @@ -115,7 +119,7 @@ mountopt = "nodev"
# size = ""

# use_deferred_removal marks devicemapper block device for deferred removal.
# If the thinpool is in use when the driver attempts to remove it, the driver
# If the thinpool is in use when the driver attempts to remove it, the driver
# tells the kernel to remove it as soon as possible. Note this does not free
# up the disk space, use deferred deletion to fully remove the thinpool.
# use_deferred_removal = "True"
Expand Down
15 changes: 11 additions & 4 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ type StoreOptions struct {
// GraphRoot is the filesystem path under which we will store the
// contents of layers, images, and containers.
GraphRoot string `json:"root,omitempty"`
// RootlessStoragePath is the storage path for rootless users
// default $HOME/.local/share/containers/storage
RootlessStoragePath string `toml:"rootless_storage_path"`
// GraphDriverName is the underlying storage driver that we'll be
// using. It only needs to be specified the first time a Store is
// initialized for a given RunRoot and GraphRoot.
Expand Down Expand Up @@ -3288,10 +3291,11 @@ func DefaultConfigFile(rootless bool) (string, error) {
// TOML-friendly explicit tables used for conversions.
type tomlConfig struct {
Storage struct {
Driver string `toml:"driver"`
RunRoot string `toml:"runroot"`
GraphRoot string `toml:"graphroot"`
Options cfg.OptionsConfig `toml:"options"`
Driver string `toml:"driver"`
RunRoot string `toml:"runroot"`
GraphRoot string `toml:"graphroot"`
RootlessStoragePath string `toml:"rootless_storage_path"`
Options cfg.OptionsConfig `toml:"options"`
} `toml:"storage"`
}

Expand Down Expand Up @@ -3321,6 +3325,9 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) {
if config.Storage.GraphRoot != "" {
storeOptions.GraphRoot = config.Storage.GraphRoot
}
if config.Storage.RootlessStoragePath != "" {
storeOptions.RootlessStoragePath = config.Storage.RootlessStoragePath
}
for _, s := range config.Storage.Options.AdditionalImageStores {
storeOptions.GraphDriverOptions = append(storeOptions.GraphDriverOptions, fmt.Sprintf("%s.imagestore=%s", config.Storage.Driver, s))
}
Expand Down
23 changes: 23 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"regexp"
"strconv"
"strings"

Expand Down Expand Up @@ -146,6 +148,7 @@ func getRootlessStorageOpts(rootlessUID int) (StoreOptions, error) {
}
opts.RunRoot = rootlessRuntime
opts.GraphRoot = filepath.Join(dataDir, "containers", "storage")
opts.RootlessStoragePath = opts.GraphRoot
if path, err := exec.LookPath("fuse-overlayfs"); err == nil {
opts.GraphDriverName = "overlay"
opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)}
Expand All @@ -161,6 +164,7 @@ func getTomlStorage(storeOptions *StoreOptions) *tomlConfig {
config.Storage.Driver = storeOptions.GraphDriverName
config.Storage.RunRoot = storeOptions.RunRoot
config.Storage.GraphRoot = storeOptions.GraphRoot
config.Storage.RootlessStoragePath = storeOptions.RootlessStoragePath
for _, i := range storeOptions.GraphDriverOptions {
s := strings.Split(i, "=")
if s[0] == "overlay.mount_program" {
Expand Down Expand Up @@ -226,6 +230,25 @@ func DefaultStoreOptions(rootless bool, rootlessUID int) (StoreOptions, error) {
}
if storageOpts.GraphRoot == "" {
storageOpts.GraphRoot = defaultRootlessGraphRoot
} else if storageOpts.RootlessStoragePath != "" {
splitPaths := strings.SplitAfter(storageOpts.RootlessStoragePath, "$")
validEnv := regexp.MustCompile(`^(HOME|USER|UID)[^a-zA-Z]`).MatchString
if len(splitPaths) > 1 {
for _, path := range splitPaths {
if !validEnv(path) {
return storageOpts, errors.Errorf("Unrecognized environment variable")
}
}
}

rootlessStoragePath := strings.Replace(storageOpts.RootlessStoragePath, "$HOME", homedir.Get(), -1)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, ouch. This sort of string replacement isn't as simple as that: for instance, what if someone writes $HOMEDIR or $USERNAME? What you probably want here is something closer to:

    look for dollar sign followed by any number of letters
    if those letters are HOME, UID, or USER, replace as needed
    otherwise, throw an error

rootlessStoragePath = strings.Replace(rootlessStoragePath, "$UID", strconv.Itoa(rootlessUID), -1)
usr, err := user.LookupId(strconv.Itoa(rootlessUID))
if err != nil {
return storageOpts, err
}
rootlessStoragePath = strings.Replace(rootlessStoragePath, "$USER", usr.Username, -1)
storageOpts.GraphRoot = rootlessStoragePath
}
} else {
if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil {
Expand Down