-
Notifications
You must be signed in to change notification settings - Fork 258
Consolidate XDG_RUNTIME code #1740
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,12 +7,16 @@ package homedir | |
// NOTE: this package has originally been copied from github.com/docker/docker. | ||
|
||
import ( | ||
"errors" | ||
"fmt" | ||
"os" | ||
"path/filepath" | ||
"strconv" | ||
"strings" | ||
"sync" | ||
"syscall" | ||
|
||
"github.com/containers/storage/pkg/unshare" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
// Key returns the env var name for the user's home dir based on | ||
|
@@ -40,18 +44,6 @@ func GetShortcutString() string { | |
return "~" | ||
} | ||
|
||
// GetRuntimeDir returns XDG_RUNTIME_DIR. | ||
// XDG_RUNTIME_DIR is typically configured via pam_systemd. | ||
// GetRuntimeDir returns non-nil error if XDG_RUNTIME_DIR is not set. | ||
// | ||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html | ||
func GetRuntimeDir() (string, error) { | ||
if xdgRuntimeDir := os.Getenv("XDG_RUNTIME_DIR"); xdgRuntimeDir != "" { | ||
return filepath.EvalSymlinks(xdgRuntimeDir) | ||
} | ||
return "", errors.New("could not get XDG_RUNTIME_DIR") | ||
} | ||
|
||
// StickRuntimeDirContents sets the sticky bit on files that are under | ||
// XDG_RUNTIME_DIR, so that the files won't be periodically removed by the system. | ||
// | ||
|
@@ -94,3 +86,95 @@ func stick(f string) error { | |
m |= os.ModeSticky | ||
return os.Chmod(f, m) | ||
} | ||
|
||
var ( | ||
rootlessConfigHomeDirError error | ||
rootlessConfigHomeDirOnce sync.Once | ||
rootlessConfigHomeDir string | ||
rootlessRuntimeDirOnce sync.Once | ||
rootlessRuntimeDir string | ||
) | ||
|
||
// isWriteableOnlyByOwner checks that the specified permission mask allows write | ||
// access only to the owner. | ||
func isWriteableOnlyByOwner(perm os.FileMode) bool { | ||
return (perm & 0o722) == 0o700 | ||
} | ||
|
||
// GetConfigHome returns XDG_CONFIG_HOME. | ||
// GetConfigHome returns $HOME/.config and nil error if XDG_CONFIG_HOME is not set. | ||
// | ||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html | ||
func GetConfigHome() (string, error) { | ||
rootlessConfigHomeDirOnce.Do(func() { | ||
cfgHomeDir := os.Getenv("XDG_CONFIG_HOME") | ||
if cfgHomeDir == "" { | ||
home := Get() | ||
resolvedHome, err := filepath.EvalSymlinks(home) | ||
if err != nil { | ||
rootlessConfigHomeDirError = fmt.Errorf("cannot resolve %s: %w", home, err) | ||
return | ||
} | ||
tmpDir := filepath.Join(resolvedHome, ".config") | ||
_ = os.MkdirAll(tmpDir, 0o700) | ||
st, err := os.Stat(tmpDir) | ||
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && isWriteableOnlyByOwner(st.Mode().Perm()) { | ||
cfgHomeDir = tmpDir | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else { | ||
rootlessConfigHomeDirError = fmt.Errorf("path %q exists and it is not writeable only by the current user", tmpDir) | ||
return | ||
} | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
rootlessConfigHomeDir = cfgHomeDir | ||
}) | ||
|
||
return rootlessConfigHomeDir, rootlessConfigHomeDirError | ||
} | ||
|
||
// GetRuntimeDir returns a directory suitable to store runtime files. | ||
// The function will try to use the XDG_RUNTIME_DIR env variable if it is set. | ||
// XDG_RUNTIME_DIR is typically configured via pam_systemd. | ||
// If XDG_RUNTIME_DIR is not set, GetRuntimeDir will try to find a suitable | ||
// directory for the current user. | ||
// | ||
// See also https://standards.freedesktop.org/basedir-spec/latest/ar01s03.html | ||
func GetRuntimeDir() (string, error) { | ||
var rootlessRuntimeDirError error | ||
|
||
rootlessRuntimeDirOnce.Do(func() { | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
runtimeDir := os.Getenv("XDG_RUNTIME_DIR") | ||
|
||
if runtimeDir != "" { | ||
rootlessRuntimeDir, rootlessRuntimeDirError = filepath.EvalSymlinks(runtimeDir) | ||
return | ||
} | ||
|
||
uid := strconv.Itoa(unshare.GetRootlessUID()) | ||
if runtimeDir == "" { | ||
tmpDir := filepath.Join("/run", "user", uid) | ||
if err := os.MkdirAll(tmpDir, 0o700); err != nil { | ||
logrus.Debug(err) | ||
} | ||
st, err := os.Lstat(tmpDir) | ||
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && isWriteableOnlyByOwner(st.Mode().Perm()) { | ||
runtimeDir = tmpDir | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
} | ||
if runtimeDir == "" { | ||
tmpDir := filepath.Join(os.TempDir(), fmt.Sprintf("storage-run-%s", uid)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This also changes the path from the podman one, not sure if this will cause problems for existing users. I know podman caches the paths in the db so I don't think it causes regressions but at the very least you need to update the podman tmpfile that uses the old path, see https://github.com/containers/podman/pull/8241/files |
||
if err := os.MkdirAll(tmpDir, 0o700); err != nil { | ||
logrus.Debug(err) | ||
} | ||
st, err := os.Lstat(tmpDir) | ||
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Geteuid() && isWriteableOnlyByOwner(st.Mode().Perm()) { | ||
runtimeDir = tmpDir | ||
} else { | ||
rootlessRuntimeDirError = fmt.Errorf("path %q exists and it is not writeable only by the current user", tmpDir) | ||
return | ||
} | ||
} | ||
rootlessRuntimeDir = runtimeDir | ||
}) | ||
|
||
return rootlessRuntimeDir, rootlessRuntimeDirError | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,7 +11,9 @@ import ( | |
|
||
"github.com/BurntSushi/toml" | ||
cfg "github.com/containers/storage/pkg/config" | ||
"github.com/containers/storage/pkg/homedir" | ||
"github.com/containers/storage/pkg/idtools" | ||
"github.com/containers/storage/pkg/unshare" | ||
"github.com/sirupsen/logrus" | ||
) | ||
|
||
|
@@ -87,7 +89,7 @@ func loadDefaultStoreOptions() { | |
|
||
_, err := os.Stat(defaultOverrideConfigFile) | ||
if err == nil { | ||
// The DefaultConfigFile(rootless) function returns the path | ||
// The DefaultConfigFile() function returns the path | ||
// of the used storage.conf file, by returning defaultConfigFile | ||
// If override exists containers/storage uses it by default. | ||
defaultConfigFile = defaultOverrideConfigFile | ||
|
@@ -109,21 +111,41 @@ func loadDefaultStoreOptions() { | |
setDefaults() | ||
} | ||
|
||
// defaultStoreOptionsIsolated is an internal implementation detail of DefaultStoreOptions to allow testing. | ||
// Everyone but the tests this is intended for should only call DefaultStoreOptions, never this function. | ||
func defaultStoreOptionsIsolated(rootless bool, rootlessUID int, storageConf string) (StoreOptions, error) { | ||
// loadStoreOptions returns the default storage ops for containers | ||
func loadStoreOptions() (StoreOptions, error) { | ||
storageConf, err := DefaultConfigFile() | ||
if err != nil { | ||
return defaultStoreOptions, err | ||
} | ||
return loadStoreOptionsFromConfFile(storageConf) | ||
} | ||
|
||
// usePerUserStorage returns whether the user private storage must be used. | ||
// We cannot simply use the unshare.IsRootless() condition, because | ||
// that checks only if the current process needs a user namespace to | ||
// work and it would break cases where the process is already created | ||
// in a user namespace (e.g. nested Podman/Buildah) and the desired | ||
// behavior is to use system paths instead of user private paths. | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
func usePerUserStorage() bool { | ||
return unshare.IsRootless() && unshare.GetRootlessUID() != 0 | ||
} | ||
|
||
// loadStoreOptionsFromConfFile is an internal implementation detail of DefaultStoreOptions to allow testing. | ||
// Everyone but the tests this is intended for should only call loadStoreOptions, never this function. | ||
func loadStoreOptionsFromConfFile(storageConf string) (StoreOptions, error) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (The idea of … OTOH now that |
||
var ( | ||
defaultRootlessRunRoot string | ||
defaultRootlessGraphRoot string | ||
err error | ||
) | ||
|
||
defaultStoreOptionsOnce.Do(loadDefaultStoreOptions) | ||
if loadDefaultStoreOptionsErr != nil { | ||
return StoreOptions{}, loadDefaultStoreOptionsErr | ||
} | ||
storageOpts := defaultStoreOptions | ||
if rootless && rootlessUID != 0 { | ||
storageOpts, err = getRootlessStorageOpts(rootlessUID, storageOpts) | ||
if usePerUserStorage() { | ||
storageOpts, err = getRootlessStorageOpts(storageOpts) | ||
if err != nil { | ||
return storageOpts, err | ||
} | ||
|
@@ -137,7 +159,7 @@ func defaultStoreOptionsIsolated(rootless bool, rootlessUID int, storageConf str | |
defaultRootlessGraphRoot = storageOpts.GraphRoot | ||
storageOpts = StoreOptions{} | ||
reloadConfigurationFileIfNeeded(storageConf, &storageOpts) | ||
if rootless && rootlessUID != 0 { | ||
if usePerUserStorage() { | ||
// If the file did not specify a graphroot or runroot, | ||
// set sane defaults so we don't try and use root-owned | ||
// directories | ||
|
@@ -156,6 +178,7 @@ func defaultStoreOptionsIsolated(rootless bool, rootlessUID int, storageConf str | |
if storageOpts.RunRoot == "" { | ||
return storageOpts, fmt.Errorf("runroot must be set") | ||
} | ||
rootlessUID := unshare.GetRootlessUID() | ||
runRoot, err := expandEnvPath(storageOpts.RunRoot, rootlessUID) | ||
if err != nil { | ||
return storageOpts, err | ||
|
@@ -186,26 +209,17 @@ func defaultStoreOptionsIsolated(rootless bool, rootlessUID int, storageConf str | |
return storageOpts, nil | ||
} | ||
|
||
// loadStoreOptions returns the default storage ops for containers | ||
func loadStoreOptions(rootless bool, rootlessUID int) (StoreOptions, error) { | ||
storageConf, err := DefaultConfigFile(rootless && rootlessUID != 0) | ||
if err != nil { | ||
return defaultStoreOptions, err | ||
} | ||
return defaultStoreOptionsIsolated(rootless, rootlessUID, storageConf) | ||
} | ||
|
||
// UpdateOptions should be called iff container engine received a SIGHUP, | ||
// otherwise use DefaultStoreOptions | ||
func UpdateStoreOptions(rootless bool, rootlessUID int) (StoreOptions, error) { | ||
storeOptions, storeError = loadStoreOptions(rootless, rootlessUID) | ||
func UpdateStoreOptions() (StoreOptions, error) { | ||
storeOptions, storeError = loadStoreOptions() | ||
return storeOptions, storeError | ||
} | ||
|
||
// DefaultStoreOptions returns the default storage ops for containers | ||
func DefaultStoreOptions(rootless bool, rootlessUID int) (StoreOptions, error) { | ||
func DefaultStoreOptions() (StoreOptions, error) { | ||
once.Do(func() { | ||
storeOptions, storeError = loadStoreOptions(rootless, rootlessUID) | ||
storeOptions, storeError = loadStoreOptions() | ||
}) | ||
return storeOptions, storeError | ||
} | ||
|
@@ -270,14 +284,26 @@ func isRootlessDriver(driver string) bool { | |
} | ||
|
||
// getRootlessStorageOpts returns the storage opts for containers running as non root | ||
func getRootlessStorageOpts(rootlessUID int, systemOpts StoreOptions) (StoreOptions, error) { | ||
func getRootlessStorageOpts(systemOpts StoreOptions) (StoreOptions, error) { | ||
var opts StoreOptions | ||
|
||
dataDir, rootlessRuntime, err := getRootlessDirInfo(rootlessUID) | ||
rootlessUID := unshare.GetRootlessUID() | ||
|
||
dataDir, err := homedir.GetDataHome() | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err != nil { | ||
return opts, err | ||
} | ||
|
||
rootlessRuntime, err := homedir.GetRuntimeDir() | ||
if err != nil { | ||
return opts, err | ||
} | ||
opts.RunRoot = rootlessRuntime | ||
|
||
opts.RunRoot = filepath.Join(rootlessRuntime, "containers") | ||
mtrmac marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if err := os.MkdirAll(opts.RunRoot, 0o700); err != nil { | ||
return opts, fmt.Errorf("unable to make rootless runtime: %w", err) | ||
} | ||
|
||
opts.PullOptions = systemOpts.PullOptions | ||
if systemOpts.RootlessStoragePath != "" { | ||
opts.GraphRoot, err = expandEnvPath(systemOpts.RootlessStoragePath, rootlessUID) | ||
|
@@ -343,12 +369,6 @@ func getRootlessStorageOpts(rootlessUID int, systemOpts StoreOptions) (StoreOpti | |
return opts, nil | ||
} | ||
|
||
// DefaultStoreOptionsAutoDetectUID returns the default storage ops for containers | ||
func DefaultStoreOptionsAutoDetectUID() (StoreOptions, error) { | ||
uid := getRootlessUID() | ||
return DefaultStoreOptions(uid != 0, uid) | ||
} | ||
|
||
var prevReloadConfig = struct { | ||
storeOptions *StoreOptions | ||
mod time.Time | ||
|
@@ -518,8 +538,8 @@ func Options() (StoreOptions, error) { | |
} | ||
|
||
// Save overwrites the tomlConfig in storage.conf with the given conf | ||
func Save(conf TomlConfig, rootless bool) error { | ||
configFile, err := DefaultConfigFile(rootless) | ||
func Save(conf TomlConfig) error { | ||
configFile, err := DefaultConfigFile() | ||
if err != nil { | ||
return err | ||
} | ||
|
@@ -537,10 +557,10 @@ func Save(conf TomlConfig, rootless bool) error { | |
} | ||
|
||
// StorageConfig is used to retrieve the storage.conf toml in order to overwrite it | ||
func StorageConfig(rootless bool) (*TomlConfig, error) { | ||
func StorageConfig() (*TomlConfig, error) { | ||
config := new(TomlConfig) | ||
|
||
configFile, err := DefaultConfigFile(rootless) | ||
configFile, err := DefaultConfigFile() | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.