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
15 changes: 3 additions & 12 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ env:
- GO_VERSION="1.11"
DISTRO="ubuntu"

- GO_VERSION="1.10"
DISTRO="ubuntu"

- GO_VERSION="1.9"
- GO_VERSION="1.12"
DISTRO="ubuntu"

# Fedora
Expand All @@ -31,10 +28,7 @@ env:
- GO_VERSION="1.11"
DISTRO="fedora"

- GO_VERSION="1.10"
DISTRO="fedora"

- GO_VERSION="1.9"
- GO_VERSION="1.12"
DISTRO="fedora"

# CentOS
Expand All @@ -44,10 +38,7 @@ env:
- GO_VERSION="1.11"
DISTRO="centos"

- GO_VERSION="1.10"
DISTRO="centos"

- GO_VERSION="1.9"
- GO_VERSION="1.12"
DISTRO="centos"

# GO_VERSION="stable" builds successfully, but tests fail on all platforms.
Expand Down
2 changes: 1 addition & 1 deletion cmd/containers-storage/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func main() {
}

if options.GraphRoot == "" && options.RunRoot == "" && options.GraphDriverName == "" && len(options.GraphDriverOptions) == 0 {
options = storage.DefaultStoreOptions
options, _ = storage.DefaultStoreOptions(false, 0)
}
args := flags.Args()
if len(args) < 1 {
Expand Down
32 changes: 22 additions & 10 deletions store.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import (

var (
// DefaultStoreOptions is a reasonable default set of options.
DefaultStoreOptions StoreOptions
defaultStoreOptions StoreOptions
stores []*store
storesLock sync.Mutex
)
Expand Down Expand Up @@ -550,7 +550,7 @@ type store struct {
// }
func GetStore(options StoreOptions) (Store, error) {
if options.RunRoot == "" && options.GraphRoot == "" && options.GraphDriverName == "" && len(options.GraphDriverOptions) == 0 {
options = DefaultStoreOptions
options = defaultStoreOptions
}

if options.GraphRoot != "" {
Expand Down Expand Up @@ -3217,8 +3217,20 @@ func copyStringInterfaceMap(m map[string]interface{}) map[string]interface{} {
return ret
}

// DefaultConfigFile path to the system wide storage.conf file
const DefaultConfigFile = "/etc/containers/storage.conf"
// defaultConfigFile path to the system wide storage.conf file
const defaultConfigFile = "/etc/containers/storage.conf"

// DefaultConfigFile returns the path to the storage config file used
func DefaultConfigFile(rootless bool) (string, error) {
if rootless {
home, err := homeDir()
if err != nil {
return "", errors.Wrapf(err, "cannot determine users homedir")
}
return filepath.Join(home, ".config/containers/storage.conf"), nil
}
return defaultConfigFile, nil
}

// TOML-friendly explicit tables used for conversions.
type tomlConfig struct {
Expand Down Expand Up @@ -3358,19 +3370,19 @@ func ReloadConfigurationFile(configFile string, storeOptions *StoreOptions) {
}

func init() {
DefaultStoreOptions.RunRoot = "/var/run/containers/storage"
DefaultStoreOptions.GraphRoot = "/var/lib/containers/storage"
DefaultStoreOptions.GraphDriverName = ""
defaultStoreOptions.RunRoot = "/var/run/containers/storage"
defaultStoreOptions.GraphRoot = "/var/lib/containers/storage"
defaultStoreOptions.GraphDriverName = ""

ReloadConfigurationFile(DefaultConfigFile, &DefaultStoreOptions)
ReloadConfigurationFile(defaultConfigFile, &defaultStoreOptions)
}

func GetDefaultMountOptions() ([]string, error) {
mountOpts := []string{
".mountopt",
fmt.Sprintf("%s.mountopt", DefaultStoreOptions.GraphDriverName),
fmt.Sprintf("%s.mountopt", defaultStoreOptions.GraphDriverName),
}
for _, option := range DefaultStoreOptions.GraphDriverOptions {
for _, option := range defaultStoreOptions.GraphDriverOptions {
key, val, err := parsers.ParseKeyValueOpt(option)
if err != nil {
return nil, err
Expand Down
237 changes: 237 additions & 0 deletions utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,237 @@
package storage

import (
"fmt"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
"syscall"

"github.com/BurntSushi/toml"
"github.com/containers/storage/pkg/idtools"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)

// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping
func ParseIDMapping(UIDMapSlice, GIDMapSlice []string, subUIDMap, subGIDMap string) (*IDMappingOptions, error) {
options := IDMappingOptions{
HostUIDMapping: true,
HostGIDMapping: true,
}
if subGIDMap == "" && subUIDMap != "" {
subGIDMap = subUIDMap
}
if subUIDMap == "" && subGIDMap != "" {
subUIDMap = subGIDMap
}
if len(GIDMapSlice) == 0 && len(UIDMapSlice) != 0 {
GIDMapSlice = UIDMapSlice
}
if len(UIDMapSlice) == 0 && len(GIDMapSlice) != 0 {
UIDMapSlice = GIDMapSlice
}
if len(UIDMapSlice) == 0 && subUIDMap == "" && os.Getuid() != 0 {
UIDMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getuid())}
}
if len(GIDMapSlice) == 0 && subGIDMap == "" && os.Getuid() != 0 {
GIDMapSlice = []string{fmt.Sprintf("0:%d:1", os.Getgid())}
}

if subUIDMap != "" && subGIDMap != "" {
mappings, err := idtools.NewIDMappings(subUIDMap, subGIDMap)
if err != nil {
return nil, errors.Wrapf(err, "failed to create NewIDMappings for uidmap=%s gidmap=%s", subUIDMap, subGIDMap)
}
options.UIDMap = mappings.UIDs()
options.GIDMap = mappings.GIDs()
}
parsedUIDMap, err := idtools.ParseIDMap(UIDMapSlice, "UID")
if err != nil {
return nil, errors.Wrapf(err, "failed to create ParseUIDMap UID=%s", UIDMapSlice)
}
parsedGIDMap, err := idtools.ParseIDMap(GIDMapSlice, "GID")
if err != nil {
return nil, errors.Wrapf(err, "failed to create ParseGIDMap GID=%s", UIDMapSlice)
}
options.UIDMap = append(options.UIDMap, parsedUIDMap...)
options.GIDMap = append(options.GIDMap, parsedGIDMap...)
if len(options.UIDMap) > 0 {
options.HostUIDMapping = false
}
if len(options.GIDMap) > 0 {
options.HostGIDMapping = false
}
return &options, nil
}

// GetRootlessRuntimeDir returns the runtime directory when running as non root
func GetRootlessRuntimeDir(rootlessUid int) (string, error) {
runtimeDir := os.Getenv("XDG_RUNTIME_DIR")
if runtimeDir == "" {
tmpDir := fmt.Sprintf("/run/user/%d", rootlessUid)
st, err := os.Stat(tmpDir)
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 {
return tmpDir, nil
}
}
tmpDir := fmt.Sprintf("%s/%d", os.TempDir(), rootlessUid)
Copy link
Member

Choose a reason for hiding this comment

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

filepath.Join?

Copy link
Member Author

Choose a reason for hiding this comment

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

That fails to build because rootlessUid is an int.

if err := os.MkdirAll(tmpDir, 0700); err != nil {
logrus.Errorf("failed to create %s: %v", tmpDir, err)
} else {
st, err := os.Stat(tmpDir)
if err == nil && int(st.Sys().(*syscall.Stat_t).Uid) == os.Getuid() && st.Mode().Perm() == 0700 {
return tmpDir, nil
}
}
home, err := homeDir()
if err != nil {
return "", errors.Wrapf(err, "neither XDG_RUNTIME_DIR nor HOME was set non-empty")
}
resolvedHome, err := filepath.EvalSymlinks(home)
if err != nil {
return "", errors.Wrapf(err, "cannot resolve %s", home)
}
return filepath.Join(resolvedHome, "rundir"), nil
}

// getRootlessDirInfo returns the parent path of where the storage for containers and
// volumes will be in rootless mode
func getRootlessDirInfo(rootlessUid int) (string, string, error) {
rootlessRuntime, err := GetRootlessRuntimeDir(rootlessUid)
if err != nil {
return "", "", err
}

dataDir := os.Getenv("XDG_DATA_HOME")
if dataDir == "" {
home, err := homeDir()
if err != nil {
return "", "", errors.Wrapf(err, "neither XDG_DATA_HOME nor HOME was set non-empty")
}
// runc doesn't like symlinks in the rootfs path, and at least
// on CoreOS /home is a symlink to /var/home, so resolve any symlink.
resolvedHome, err := filepath.EvalSymlinks(home)
if err != nil {
return "", "", errors.Wrapf(err, "cannot resolve %s", home)
}
dataDir = filepath.Join(resolvedHome, ".local", "share")
}
return dataDir, rootlessRuntime, nil
}

// getRootlessStorageOpts returns the storage opts for containers running as non root
func getRootlessStorageOpts(rootlessUid int) (StoreOptions, error) {
var opts StoreOptions

dataDir, rootlessRuntime, err := getRootlessDirInfo(rootlessUid)
if err != nil {
return opts, err
}
opts.RunRoot = rootlessRuntime
opts.GraphRoot = filepath.Join(dataDir, "containers", "storage")
if path, err := exec.LookPath("fuse-overlayfs"); err == nil {
opts.GraphDriverName = "overlay"
opts.GraphDriverOptions = []string{fmt.Sprintf("overlay.mount_program=%s", path)}
} else {
opts.GraphDriverName = "vfs"
}
return opts, nil
}

type tomlOptionsConfig struct {
MountProgram string `toml:"mount_program"`
}

func getTomlStorage(storeOptions *StoreOptions) *tomlConfig {
config := new(tomlConfig)

config.Storage.Driver = storeOptions.GraphDriverName
config.Storage.RunRoot = storeOptions.RunRoot
config.Storage.GraphRoot = storeOptions.GraphRoot
for _, i := range storeOptions.GraphDriverOptions {
s := strings.Split(i, "=")
if s[0] == "overlay.mount_program" {
config.Storage.Options.MountProgram = s[1]
}
}

return config
}

// DefaultStoreOptions returns the default storage ops for containers
func DefaultStoreOptions(rootless bool, rootlessUid int) (StoreOptions, error) {
var (
defaultRootlessRunRoot string
defaultRootlessGraphRoot string
err error
)
storageOpts := defaultStoreOptions
if rootless {
storageOpts, err = getRootlessStorageOpts(rootlessUid)
if err != nil {
return storageOpts, err
}
}

storageConf, err := DefaultConfigFile(rootless)
if err != nil {
return storageOpts, err
}
if _, err = os.Stat(storageConf); err == nil {
defaultRootlessRunRoot = storageOpts.RunRoot
defaultRootlessGraphRoot = storageOpts.GraphRoot
storageOpts = StoreOptions{}
ReloadConfigurationFile(storageConf, &storageOpts)
}

if !os.IsNotExist(err) {
return storageOpts, errors.Wrapf(err, "cannot stat %s", storageConf)
}

if rootless {
if err == nil {
// If the file did not specify a graphroot or runroot,
// set sane defaults so we don't try and use root-owned
// directories
if storageOpts.RunRoot == "" {
storageOpts.RunRoot = defaultRootlessRunRoot
}
if storageOpts.GraphRoot == "" {
storageOpts.GraphRoot = defaultRootlessGraphRoot
}
} else {
if err := os.MkdirAll(filepath.Dir(storageConf), 0755); err != nil {
return storageOpts, errors.Wrapf(err, "cannot make directory %s", filepath.Dir(storageConf))
}
file, err := os.OpenFile(storageConf, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0666)
if err != nil {
return storageOpts, errors.Wrapf(err, "cannot open %s", storageConf)
}

tomlConfiguration := getTomlStorage(&storageOpts)
defer file.Close()
enc := toml.NewEncoder(file)
if err := enc.Encode(tomlConfiguration); err != nil {
os.Remove(storageConf)

return storageOpts, errors.Wrapf(err, "failed to encode %s", storageConf)
}
}
Copy link
Member

Choose a reason for hiding this comment

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

We should do something about non-nil errors that aren't IsNotExist errors.

Copy link
Member Author

Choose a reason for hiding this comment

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

Added handling for this.

}
return storageOpts, nil
}

func homeDir() (string, error) {
home := os.Getenv("HOME")
if home == "" {
usr, err := user.Current()
if err != nil {
return "", errors.Wrapf(err, "neither XDG_RUNTIME_DIR nor HOME was set non-empty")
}
home = usr.HomeDir
}
return home, nil
}