mirror of
https://github.com/containers/podman.git
synced 2026-04-04 23:13:34 -04:00
Add a per-volume 'nocreate' option that prevents automatic creation of
named volumes when they don't exist. When specified, Podman will fail
if the volume is not found instead of creating it automatically.
Usage: -v myvolume:/data:nocreate
--mount type=volume,src=myvolume,dst=/data,nocreate
See: #27862
Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
242 lines
7.8 KiB
Go
242 lines
7.8 KiB
Go
package util
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/containers/podman/v6/libpod/define"
|
|
"github.com/containers/podman/v6/pkg/rootless"
|
|
)
|
|
|
|
var (
|
|
// ErrBadMntOption indicates that an invalid mount option was passed.
|
|
ErrBadMntOption = errors.New("invalid mount option")
|
|
// ErrDupeMntOption indicates that a duplicate mount option was passed.
|
|
ErrDupeMntOption = errors.New("duplicate mount option passed")
|
|
)
|
|
|
|
type defaultMountOptions struct {
|
|
noexec bool
|
|
nosuid bool
|
|
nodev bool
|
|
}
|
|
|
|
type getDefaultMountOptionsFn func(path string) (defaultMountOptions, error)
|
|
|
|
// ProcessOptions parses the options for a bind or tmpfs mount and ensures that
|
|
// they are sensible and follow convention. The isTmpfs variable controls
|
|
// whether extra, tmpfs-specific options will be allowed.
|
|
// The sourcePath variable, if not empty, contains a bind mount source.
|
|
// Returns the processed options, a boolean indicating if nocreate was specified, and an error.
|
|
func ProcessOptions(options []string, isTmpfs bool, sourcePath string) ([]string, bool, error) {
|
|
return processOptionsInternal(options, isTmpfs, sourcePath, getDefaultMountOptions)
|
|
}
|
|
|
|
func processOptionsInternal(options []string, isTmpfs bool, sourcePath string, getDefaultMountOptions getDefaultMountOptionsFn) ([]string, bool, error) {
|
|
var foundWrite, foundSize, foundProp, foundMode, foundExec, foundSuid, foundDev, foundCopyUp, foundBind, foundZ, foundU, foundOverlay, foundIdmap, foundCopy, foundNoSwap, foundNoDereference, foundNoCreate bool
|
|
|
|
recursiveBind := true
|
|
|
|
newOptions := make([]string, 0, len(options))
|
|
for _, opt := range options {
|
|
// Some options have parameters - size, mode
|
|
key, _, _ := strings.Cut(opt, "=")
|
|
|
|
// add advanced options such as upperdir=/path and workdir=/path, when overlay is specified
|
|
if foundOverlay {
|
|
if strings.Contains(opt, "upperdir") {
|
|
newOptions = append(newOptions, opt)
|
|
continue
|
|
}
|
|
if strings.Contains(opt, "workdir") {
|
|
newOptions = append(newOptions, opt)
|
|
continue
|
|
}
|
|
}
|
|
if strings.HasPrefix(key, "subpath") {
|
|
newOptions = append(newOptions, opt)
|
|
continue
|
|
}
|
|
if strings.HasPrefix(key, "idmap") {
|
|
if foundIdmap {
|
|
return nil, false, fmt.Errorf("the 'idmap' option can only be set once: %w", ErrDupeMntOption)
|
|
}
|
|
foundIdmap = true
|
|
newOptions = append(newOptions, opt)
|
|
continue
|
|
}
|
|
|
|
switch key {
|
|
case "copy", "nocopy":
|
|
if foundCopy {
|
|
return nil, false, fmt.Errorf("only one of 'nocopy' and 'copy' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundCopy = true
|
|
case "O":
|
|
foundOverlay = true
|
|
case "volume-opt":
|
|
// Volume-opt should be relayed and processed by driver.
|
|
newOptions = append(newOptions, opt)
|
|
case "exec", "noexec":
|
|
if foundExec {
|
|
return nil, false, fmt.Errorf("only one of 'noexec' and 'exec' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundExec = true
|
|
case "suid", "nosuid":
|
|
if foundSuid {
|
|
return nil, false, fmt.Errorf("only one of 'nosuid' and 'suid' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundSuid = true
|
|
case "nodev", "dev":
|
|
if foundDev {
|
|
return nil, false, fmt.Errorf("only one of 'nodev' and 'dev' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundDev = true
|
|
case "rw", "ro":
|
|
if foundWrite {
|
|
return nil, false, fmt.Errorf("only one of 'rw' and 'ro' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundWrite = true
|
|
case "private", "rprivate", "slave", "rslave", "shared", "rshared", "unbindable", "runbindable":
|
|
if foundProp {
|
|
return nil, false, fmt.Errorf("only one root propagation mode can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundProp = true
|
|
case "size":
|
|
if !isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'size' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundSize {
|
|
return nil, false, fmt.Errorf("only one tmpfs size can be specified: %w", ErrDupeMntOption)
|
|
}
|
|
foundSize = true
|
|
case "mode":
|
|
if !isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'mode' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundMode {
|
|
return nil, false, fmt.Errorf("only one tmpfs mode can be specified: %w", ErrDupeMntOption)
|
|
}
|
|
foundMode = true
|
|
case "tmpcopyup":
|
|
if !isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'tmpcopyup' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundCopyUp {
|
|
return nil, false, fmt.Errorf("the 'tmpcopyup' or 'notmpcopyup' option can only be set once: %w", ErrDupeMntOption)
|
|
}
|
|
foundCopyUp = true
|
|
case "consistency":
|
|
// Often used on MACs and mistakenly on Linux platforms.
|
|
// Since Docker ignores this option so shall we.
|
|
continue
|
|
case "notmpcopyup":
|
|
if !isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'notmpcopyup' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundCopyUp {
|
|
return nil, false, fmt.Errorf("the 'tmpcopyup' or 'notmpcopyup' option can only be set once: %w", ErrDupeMntOption)
|
|
}
|
|
foundCopyUp = true
|
|
// do not propagate notmpcopyup to the OCI runtime
|
|
continue
|
|
case "noswap":
|
|
|
|
if !isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'noswap' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if rootless.IsRootless() {
|
|
return nil, false, fmt.Errorf("the 'noswap' option is only allowed with rootful tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundNoSwap {
|
|
return nil, false, fmt.Errorf("the 'tmpswap' option can only be set once: %w", ErrDupeMntOption)
|
|
}
|
|
foundNoSwap = true
|
|
newOptions = append(newOptions, opt)
|
|
continue
|
|
case "no-dereference":
|
|
if foundNoDereference {
|
|
return nil, false, fmt.Errorf("the 'no-dereference' option can only be set once: %w", ErrDupeMntOption)
|
|
}
|
|
foundNoDereference = true
|
|
case define.TypeBind:
|
|
recursiveBind = false
|
|
fallthrough
|
|
case "rbind":
|
|
if isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'bind' and 'rbind' options are not allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundBind {
|
|
return nil, false, fmt.Errorf("only one of 'rbind' and 'bind' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundBind = true
|
|
case "z", "Z":
|
|
if isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'z' and 'Z' options are not allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
if foundZ {
|
|
return nil, false, fmt.Errorf("only one of 'z' and 'Z' can be used: %w", ErrDupeMntOption)
|
|
}
|
|
foundZ = true
|
|
case "U":
|
|
if foundU {
|
|
return nil, false, fmt.Errorf("the 'U' option can only be set once: %w", ErrDupeMntOption)
|
|
}
|
|
foundU = true
|
|
case "noatime":
|
|
if !isTmpfs {
|
|
return nil, false, fmt.Errorf("the 'noatime' option is only allowed with tmpfs mounts: %w", ErrBadMntOption)
|
|
}
|
|
case "nocreate":
|
|
// nocreate is handled separately and not passed to the runtime
|
|
foundNoCreate = true
|
|
continue
|
|
default:
|
|
return nil, false, fmt.Errorf("unknown mount option %q: %w", opt, ErrBadMntOption)
|
|
}
|
|
newOptions = append(newOptions, opt)
|
|
}
|
|
|
|
if !foundProp {
|
|
if recursiveBind {
|
|
newOptions = append(newOptions, "rprivate")
|
|
} else {
|
|
newOptions = append(newOptions, "private")
|
|
}
|
|
}
|
|
defaults, err := getDefaultMountOptions(sourcePath)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !foundExec && defaults.noexec {
|
|
newOptions = append(newOptions, "noexec")
|
|
}
|
|
if !foundSuid && defaults.nosuid {
|
|
newOptions = append(newOptions, "nosuid")
|
|
}
|
|
if !foundDev && defaults.nodev {
|
|
newOptions = append(newOptions, "nodev")
|
|
}
|
|
if isTmpfs && !foundCopyUp {
|
|
newOptions = append(newOptions, "tmpcopyup")
|
|
}
|
|
if !isTmpfs && !foundBind {
|
|
newOptions = append(newOptions, "rbind")
|
|
}
|
|
|
|
return newOptions, foundNoCreate, nil
|
|
}
|
|
|
|
func ParseDriverOpts(option string) (string, string, error) {
|
|
_, val, hasVal := strings.Cut(option, "=")
|
|
if !hasVal {
|
|
return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption)
|
|
}
|
|
optKey, optVal, hasOptVal := strings.Cut(val, "=")
|
|
if !hasOptVal {
|
|
return "", "", fmt.Errorf("cannot parse driver opts: %w", ErrBadMntOption)
|
|
}
|
|
return optKey, optVal, nil
|
|
}
|