package config

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"

	"github.com/BurntSushi/toml"
	"github.com/containers/common/internal/attributedstring"
	"github.com/containers/common/libnetwork/types"
	"github.com/containers/common/pkg/capabilities"
	"github.com/containers/common/pkg/util"
	"github.com/containers/storage/pkg/ioutils"
	"github.com/containers/storage/pkg/unshare"
	units "github.com/docker/go-units"
	selinux "github.com/opencontainers/selinux/go-selinux"
	"github.com/sirupsen/logrus"
)

const (
	// _configPath is the path to the containers/containers.conf
	// inside a given config directory.
	_configPath = "containers/containers.conf"
	// UserOverrideContainersConfig holds the containers config path overridden by the rootless user
	UserOverrideContainersConfig = ".config/" + _configPath
	// Token prefix for looking for helper binary under $BINDIR
	bindirPrefix = "$BINDIR"
)

var validImageVolumeModes = []string{_typeBind, "tmpfs", "ignore"}

// ProxyEnv is a list of Proxy Environment variables
var ProxyEnv = []string{
	"http_proxy",
	"https_proxy",
	"ftp_proxy",
	"no_proxy",
	"HTTP_PROXY",
	"HTTPS_PROXY",
	"FTP_PROXY",
	"NO_PROXY",
}

// Config contains configuration options for container tools
type Config struct {
	// Containers specify settings that configure how containers will run ont the system
	Containers ContainersConfig `toml:"containers"`
	// Engine specifies how the container engine based on Engine will run
	Engine EngineConfig `toml:"engine"`
	// Machine specifies configurations of podman machine VMs
	Machine MachineConfig `toml:"machine"`
	// Network section defines the configuration of CNI Plugins
	Network NetworkConfig `toml:"network"`
	// Secret section defines configurations for the secret management
	Secrets SecretConfig `toml:"secrets"`
	// ConfigMap section defines configurations for the configmaps management
	ConfigMaps ConfigMapConfig `toml:"configmaps"`
	// Farms defines configurations for the buildfarm farms
	Farms FarmConfig `toml:"farms"`

	loadedModules []string // only used at runtime to store which modules were loaded
}

// ContainersConfig represents the "containers" TOML config table
// containers global options for containers tools
type ContainersConfig struct {
	// Devices to add to all containers
	Devices attributedstring.Slice `toml:"devices,omitempty"`

	// Volumes to add to all containers
	Volumes attributedstring.Slice `toml:"volumes,omitempty"`

	// ApparmorProfile is the apparmor profile name which is used as the
	// default for the runtime.
	ApparmorProfile string `toml:"apparmor_profile,omitempty"`

	// Annotation to add to all containers
	Annotations attributedstring.Slice `toml:"annotations,omitempty"`

	// BaseHostsFile is the path to a hosts file, the entries from this file
	// are added to the containers hosts file. As special value "image" is
	// allowed which uses the /etc/hosts file from within the image and "none"
	// which uses no base file at all. If it is empty we should default
	// to /etc/hosts.
	BaseHostsFile string `toml:"base_hosts_file,omitempty"`

	// Default way to create a cgroup namespace for the container
	CgroupNS string `toml:"cgroupns,omitempty"`

	// Default cgroup configuration
	Cgroups string `toml:"cgroups,omitempty"`

	// CgroupConf entries specifies a list of cgroup files to write to and their values. For example
	// "memory.high=1073741824" sets the memory.high limit to 1GB.
	CgroupConf attributedstring.Slice `toml:"cgroup_conf,omitempty"`

	// Capabilities to add to all containers.
	DefaultCapabilities attributedstring.Slice `toml:"default_capabilities,omitempty"`

	// Sysctls to add to all containers.
	DefaultSysctls attributedstring.Slice `toml:"default_sysctls,omitempty"`

	// DefaultUlimits specifies the default ulimits to apply to containers
	DefaultUlimits attributedstring.Slice `toml:"default_ulimits,omitempty"`

	// DefaultMountsFile is the path to the default mounts file for testing
	DefaultMountsFile string `toml:"-"`

	// DNSServers set default DNS servers.
	DNSServers attributedstring.Slice `toml:"dns_servers,omitempty"`

	// DNSOptions set default DNS options.
	DNSOptions attributedstring.Slice `toml:"dns_options,omitempty"`

	// DNSSearches set default DNS search domains.
	DNSSearches attributedstring.Slice `toml:"dns_searches,omitempty"`

	// EnableKeyring tells the container engines whether to create
	// a kernel keyring for use within the container
	EnableKeyring bool `toml:"keyring,omitempty"`

	// EnableLabeling tells the container engines whether to use MAC
	// Labeling to separate containers (SELinux)
	EnableLabeling bool `toml:"label,omitempty"`

	// EnableLabeledUsers indicates whether to enforce confined users with
	// containers on SELinux systems. This option causes containers to
	// maintain the current user and role field of the calling process.
	// Otherwise containers run with user system_u, and the role system_r.
	EnableLabeledUsers bool `toml:"label_users,omitempty"`

	// Env is the environment variable list for container process.
	Env attributedstring.Slice `toml:"env,omitempty"`

	// EnvHost Pass all host environment variables into the container.
	EnvHost bool `toml:"env_host,omitempty"`

	// HostContainersInternalIP is used to set a specific host.containers.internal ip.
	HostContainersInternalIP string `toml:"host_containers_internal_ip,omitempty"`

	// HTTPProxy is the proxy environment variable list to apply to container process
	HTTPProxy bool `toml:"http_proxy,omitempty"`

	// Init tells container runtimes whether to run init inside the
	// container that forwards signals and reaps processes.
	Init bool `toml:"init,omitempty"`

	// InitPath is the path for init to run if the Init bool is enabled
	//
	// Deprecated: Do not use this field directly use conf.FindInitBinary() instead.
	InitPath string `toml:"init_path,omitempty"`

	// IPCNS way to create a ipc namespace for the container
	IPCNS string `toml:"ipcns,omitempty"`

	// LogDriver  for the container.  For example: k8s-file and journald
	LogDriver string `toml:"log_driver,omitempty"`

	// LogSizeMax is the maximum number of bytes after which the log file
	// will be truncated. It can be expressed as a human-friendly string
	// that is parsed to bytes.
	// Negative values indicate that the log file won't be truncated.
	LogSizeMax int64 `toml:"log_size_max,omitempty,omitzero"`

	// Specifies default format tag for container log messages.
	// This is useful for creating a specific tag for container log messages.
	// Containers logs default to truncated container ID as a tag.
	LogTag string `toml:"log_tag,omitempty"`

	// Mount to add to all containers
	Mounts attributedstring.Slice `toml:"mounts,omitempty"`

	// NetNS indicates how to create a network namespace for the container
	NetNS string `toml:"netns,omitempty"`

	// NoHosts tells container engine whether to create its own /etc/hosts
	NoHosts bool `toml:"no_hosts,omitempty"`

	// OOMScoreAdj tunes the host's OOM preferences for containers
	// (accepts values from -1000 to 1000).
	OOMScoreAdj *int `toml:"oom_score_adj,omitempty"`

	// PidsLimit is the number of processes each container is restricted to
	// by the cgroup process number controller.
	PidsLimit int64 `toml:"pids_limit,omitempty,omitzero"`

	// PidNS indicates how to create a pid namespace for the container
	PidNS string `toml:"pidns,omitempty"`

	// Copy the content from the underlying image into the newly created
	// volume when the container is created instead of when it is started.
	// If false, the container engine will not copy the content until
	// the container is started. Setting it to true may have negative
	// performance implications.
	PrepareVolumeOnCreate bool `toml:"prepare_volume_on_create,omitempty"`

	// Give extended privileges to all containers. A privileged container
	// turns off the security features that isolate the container from the
	// host. Dropped Capabilities, limited devices, read-only mount points,
	// Apparmor/SELinux separation, and Seccomp filters are all disabled.
	// Due to the disabled security features the privileged field should
	// almost never be set as containers can easily break out of
	// confinment.
	//
	// Containers running in a user namespace (e.g., rootless containers)
	// cannot have more privileges than the user that launched them.
	Privileged bool `toml:"privileged,omitempty"`

	// ReadOnly causes engine to run all containers with root file system mounted read-only
	ReadOnly bool `toml:"read_only,omitempty"`

	// SeccompProfile is the seccomp.json profile path which is used as the
	// default for the runtime.
	SeccompProfile string `toml:"seccomp_profile,omitempty"`

	// ShmSize holds the size of /dev/shm.
	ShmSize string `toml:"shm_size,omitempty"`

	// TZ sets the timezone inside the container
	TZ string `toml:"tz,omitempty"`

	// Umask is the umask inside the container.
	Umask string `toml:"umask,omitempty"`

	// UTSNS indicates how to create a UTS namespace for the container
	UTSNS string `toml:"utsns,omitempty"`

	// UserNS indicates how to create a User namespace for the container
	UserNS string `toml:"userns,omitempty"`

	// UserNSSize how many UIDs to allocate for automatically created UserNS
	// Deprecated: no user of this field is known.
	UserNSSize int `toml:"userns_size,omitempty,omitzero"`
}

// EngineConfig contains configuration options used to set up a engine runtime
type EngineConfig struct {
	// CgroupCheck indicates the configuration has been rewritten after an
	// upgrade to Fedora 31 to change the default OCI runtime for cgroupv2v2.
	CgroupCheck bool `toml:"cgroup_check,omitempty"`

	// CGroupManager is the CGroup Manager to use Valid values are "cgroupfs"
	// and "systemd".
	CgroupManager string `toml:"cgroup_manager,omitempty"`

	// NOTE: when changing this struct, make sure to update (*Config).Merge().

	// ConmonEnvVars are environment variables to pass to the Conmon binary
	// when it is launched.
	ConmonEnvVars attributedstring.Slice `toml:"conmon_env_vars,omitempty"`

	// ConmonPath is the path to the Conmon binary used for managing containers.
	// The first path pointing to a valid file will be used.
	ConmonPath attributedstring.Slice `toml:"conmon_path,omitempty"`

	// ConmonRsPath is the path to the Conmon-rs binary used for managing containers.
	// The first path pointing to a valid file will be used.
	ConmonRsPath attributedstring.Slice `toml:"conmonrs_path,omitempty"`

	// CompatAPIEnforceDockerHub enforces using docker.io for completing
	// short names in Podman's compatibility REST API.  Note that this will
	// ignore unqualified-search-registries and short-name aliases defined
	// in containers-registries.conf(5).
	CompatAPIEnforceDockerHub bool `toml:"compat_api_enforce_docker_hub,omitempty"`

	// ComposeProviders specifies one or more external providers for the
	// compose command.  The first found provider is used for execution.
	// Can be an absolute and relative path or a (file) name.  Make sure to
	// expand the return items via `os.ExpandEnv`.
	ComposeProviders attributedstring.Slice `toml:"compose_providers,omitempty"`

	// ComposeWarningLogs emits logs on each invocation of the compose
	// command indicating that an external compose provider is being
	// executed.
	ComposeWarningLogs bool `toml:"compose_warning_logs,omitempty"`

	// DBBackend is the database backend to be used by Podman.
	DBBackend string `toml:"database_backend,omitempty"`

	// DetachKeys is the sequence of keys used to detach a container.
	DetachKeys string `toml:"detach_keys,omitempty"`

	// EnablePortReservation determines whether engine will reserve ports on the
	// host when they are forwarded to containers. When enabled, when ports are
	// forwarded to containers, they are held open by conmon as long as the
	// container is running, ensuring that they cannot be reused by other
	// programs on the host. However, this can cause significant memory usage if
	// a container has many ports forwarded to it. Disabling this can save
	// memory.
	EnablePortReservation bool `toml:"enable_port_reservation,omitempty"`

	// Environment variables to be used when running the container engine (e.g., Podman, Buildah). For example "http_proxy=internal.proxy.company.com"
	Env attributedstring.Slice `toml:"env,omitempty"`

	// EventsLogFilePath is where the events log is stored.
	EventsLogFilePath string `toml:"events_logfile_path,omitempty"`

	// EventsLogFileMaxSize sets the maximum size for the events log. When the limit is exceeded,
	// the logfile is rotated and the old one is deleted.
	EventsLogFileMaxSize eventsLogMaxSize `toml:"events_logfile_max_size,omitzero"`

	// EventsLogger determines where events should be logged.
	EventsLogger string `toml:"events_logger,omitempty"`

	// EventsContainerCreateInspectData creates a more verbose
	// container-create event which includes a JSON payload with detailed
	// information about the container.
	EventsContainerCreateInspectData bool `toml:"events_container_create_inspect_data,omitempty"`

	// graphRoot internal stores the location of the graphroot
	graphRoot string

	// HelperBinariesDir is a list of directories which are used to search for
	// helper binaries.
	HelperBinariesDir attributedstring.Slice `toml:"helper_binaries_dir,omitempty"`

	// configuration files. When the same filename is present in
	// multiple directories, the file in the directory listed last in
	// this slice takes precedence.
	HooksDir attributedstring.Slice `toml:"hooks_dir,omitempty"`

	// ImageBuildFormat (DEPRECATED) indicates the default image format to
	// building container images. Should use ImageDefaultFormat
	ImageBuildFormat string `toml:"image_build_format,omitempty"`

	// ImageDefaultTransport is the default transport method used to fetch
	// images.
	ImageDefaultTransport string `toml:"image_default_transport,omitempty"`

	// ImageParallelCopies indicates the maximum number of image layers
	// to be copied simultaneously. If this is zero, container engines
	// will fall back to containers/image defaults.
	ImageParallelCopies uint `toml:"image_parallel_copies,omitempty,omitzero"`

	// ImageDefaultFormat specified the manifest Type (oci, v2s2, or v2s1)
	// to use when pulling, pushing, building container images. By default
	// image pulled and pushed match the format of the source image.
	// Building/committing defaults to OCI.
	ImageDefaultFormat string `toml:"image_default_format,omitempty"`

	// ImageVolumeMode Tells container engines how to handle the built-in
	// image volumes.  Acceptable values are "bind", "tmpfs", and "ignore".
	ImageVolumeMode string `toml:"image_volume_mode,omitempty"`

	// InfraCommand is the command run to start up a pod infra container.
	InfraCommand string `toml:"infra_command,omitempty"`

	// InfraImage is the image a pod infra container will use to manage
	// namespaces.
	InfraImage string `toml:"infra_image,omitempty"`

	// InitPath is the path to the container-init binary.
	//
	// Deprecated: Do not use this field directly use conf.FindInitBinary() instead.
	InitPath string `toml:"init_path,omitempty"`

	// KubeGenerateType sets the Kubernetes kind/specification to generate by default
	// with the podman kube generate command
	KubeGenerateType string `toml:"kube_generate_type,omitempty"`

	// LockType is the type of locking to use.
	LockType string `toml:"lock_type,omitempty"`

	// MachineEnabled indicates if Podman is running in a podman-machine VM
	//
	// This method is soft deprecated, use machine.IsPodmanMachine instead
	MachineEnabled bool `toml:"machine_enabled,omitempty"`

	// MultiImageArchive - if true, the container engine allows for storing
	// archives (e.g., of the docker-archive transport) with multiple
	// images.  By default, Podman creates single-image archives.
	MultiImageArchive bool `toml:"multi_image_archive,omitempty"`

	// Namespace is the engine namespace to use. Namespaces are used to create
	// scopes to separate containers and pods in the state. When namespace is
	// set, engine will only view containers and pods in the same namespace. All
	// containers and pods created will default to the namespace set here. A
	// namespace of "", the empty string, is equivalent to no namespace, and all
	// containers and pods will be visible. The default namespace is "".
	Namespace string `toml:"namespace,omitempty"`

	// NetworkCmdPath is the path to the slirp4netns binary.
	NetworkCmdPath string `toml:"network_cmd_path,omitempty"`

	// NetworkCmdOptions is the default options to pass to the slirp4netns binary.
	// For example "allow_host_loopback=true"
	NetworkCmdOptions attributedstring.Slice `toml:"network_cmd_options,omitempty"`

	// NoPivotRoot sets whether to set no-pivot-root in the OCI runtime.
	NoPivotRoot bool `toml:"no_pivot_root,omitempty"`

	// NumLocks is the number of locks to make available for containers and
	// pods.
	NumLocks uint32 `toml:"num_locks,omitempty,omitzero"`

	// OCIRuntime is the OCI runtime to use.
	OCIRuntime string `toml:"runtime,omitempty"`

	// OCIRuntimes are the set of configured OCI runtimes (default is runc).
	OCIRuntimes map[string][]string `toml:"runtimes,omitempty"`

	// PlatformToOCIRuntime requests specific OCI runtime for a specified platform of image.
	PlatformToOCIRuntime map[string]string `toml:"platform_to_oci_runtime,omitempty"`

	// PodExitPolicy determines the behaviour when the last container of a pod exits.
	PodExitPolicy PodExitPolicy `toml:"pod_exit_policy,omitempty"`

	// PullPolicy determines whether to pull image before creating or running a container
	// default is "missing"
	PullPolicy string `toml:"pull_policy,omitempty"`

	// Indicates whether the application should be running in Remote mode
	Remote bool `toml:"remote,omitempty"`

	// RemoteURI is deprecated, see ActiveService
	// RemoteURI containers connection information used to connect to remote system.
	RemoteURI string `toml:"remote_uri,omitempty"`

	// RemoteIdentity is deprecated, ServiceDestinations
	// RemoteIdentity key file for RemoteURI
	RemoteIdentity string `toml:"remote_identity,omitempty"`

	// ActiveService index to Destinations added v2.0.3
	ActiveService string `toml:"active_service,omitempty"`

	// Add existing instances with requested compression algorithms to manifest list
	AddCompression attributedstring.Slice `toml:"add_compression,omitempty"`

	// ServiceDestinations mapped by service Names
	ServiceDestinations map[string]Destination `toml:"service_destinations,omitempty"`

	// SSHConfig contains the ssh config file path if not the default
	SSHConfig string `toml:"ssh_config,omitempty"`

	// RuntimePath is the path to OCI runtime binary for launching containers.
	// The first path pointing to a valid file will be used This is used only
	// when there are no OCIRuntime/OCIRuntimes defined.  It is used only to be
	// backward compatible with older versions of Podman.
	RuntimePath attributedstring.Slice `toml:"runtime_path,omitempty"`

	// RuntimeSupportsJSON is the list of the OCI runtimes that support
	// --format=json.
	RuntimeSupportsJSON attributedstring.Slice `toml:"runtime_supports_json,omitempty"`

	// RuntimeSupportsNoCgroups is a list of OCI runtimes that support
	// running containers without CGroups.
	RuntimeSupportsNoCgroups attributedstring.Slice `toml:"runtime_supports_nocgroup,omitempty"`

	// RuntimeSupportsKVM is a list of OCI runtimes that support
	// KVM separation for containers.
	RuntimeSupportsKVM attributedstring.Slice `toml:"runtime_supports_kvm,omitempty"`

	// SetOptions contains a subset of config options. It's used to indicate if
	// a given option has either been set by the user or by the parsed
	// configuration file. If not, the corresponding option might be
	// overwritten by values from the database. This behavior guarantees
	// backwards compat with older version of libpod and Podman.
	SetOptions

	// SignaturePolicyPath is the path to a signature policy to use for
	// validating images. If left empty, the containers/image default signature
	// policy will be used.
	SignaturePolicyPath string `toml:"-"`

	// SDNotify tells container engine to allow containers to notify the host systemd of
	// readiness using the SD_NOTIFY mechanism.
	SDNotify bool `toml:"-"`

	// ServiceTimeout is the number of seconds to wait without a connection
	// before the `podman system service` times out and exits
	ServiceTimeout uint `toml:"service_timeout,omitempty,omitzero"`

	// StaticDir is the path to a persistent directory to store container
	// files.
	StaticDir string `toml:"static_dir,omitempty"`

	// StopTimeout is the number of seconds to wait for container to exit
	// before sending kill signal.
	StopTimeout uint `toml:"stop_timeout,omitempty,omitzero"`

	// ExitCommandDelay is the number of seconds to wait for the exit
	// command to be send to the API process on the server.
	ExitCommandDelay uint `toml:"exit_command_delay,omitempty,omitzero"`

	// ImageCopyTmpDir is the default location for storing temporary
	// container image content,  Can be overridden with the TMPDIR
	// environment variable.  If you specify "storage", then the
	// location of the container/storage tmp directory will be used.
	ImageCopyTmpDir string `toml:"image_copy_tmp_dir,omitempty"`

	// TmpDir is the path to a temporary directory to store per-boot container
	// files. Must be stored in a tmpfs.
	TmpDir string `toml:"tmp_dir,omitempty"`

	// VolumePath is the default location that named volumes will be created
	// under. This convention is followed by the default volume driver, but
	// may not be by other drivers.
	VolumePath string `toml:"volume_path,omitempty"`

	// VolumePluginTimeout sets the default timeout, in seconds, for
	// operations that must contact a volume plugin. Plugins are external
	// programs accessed via REST API; this sets a timeout for requests to
	// that API.
	// A value of 0 is treated as no timeout.
	VolumePluginTimeout uint `toml:"volume_plugin_timeout,omitempty,omitzero"`

	// VolumePlugins is a set of plugins that can be used as the backend for
	// Podman named volumes. Each volume is specified as a name (what Podman
	// will refer to the plugin as) mapped to a path, which must point to a
	// Unix socket that conforms to the Volume Plugin specification.
	VolumePlugins map[string]string `toml:"volume_plugins,omitempty"`

	// ChownCopiedFiles tells the container engine whether to chown files copied
	// into a container to the container's primary uid/gid.
	ChownCopiedFiles bool `toml:"chown_copied_files,omitempty"`

	// CompressionFormat is the compression format used to compress image layers.
	CompressionFormat string `toml:"compression_format,omitempty"`

	// CompressionLevel is the compression level used to compress image layers.
	CompressionLevel *int `toml:"compression_level,omitempty"`

	// PodmanshTimeout is the number of seconds to wait for podmansh logins.
	// In other words, the timeout for the `podmansh` container to be in running
	// state.
	PodmanshTimeout uint `toml:"podmansh_timeout,omitempty,omitzero"`
}

// SetOptions contains a subset of options in a Config. It's used to indicate if
// a given option has either been set by the user or by a parsed engine
// configuration file. If not, the corresponding option might be overwritten by
// values from the database. This behavior guarantees backwards compat with
// older version of libpod and Podman.
type SetOptions struct {
	// StorageConfigRunRootSet indicates if the RunRoot has been explicitly set
	// by the config or by the user. It's required to guarantee backwards
	// compatibility with older versions of libpod for which we must query the
	// database configuration. Not included in the on-disk config.
	StorageConfigRunRootSet bool `toml:"-"`

	// StorageConfigGraphRootSet indicates if the RunRoot has been explicitly
	// set by the config or by the user. It's required to guarantee backwards
	// compatibility with older versions of libpod for which we must query the
	// database configuration. Not included in the on-disk config.
	StorageConfigGraphRootSet bool `toml:"-"`

	// StorageConfigGraphDriverNameSet indicates if the GraphDriverName has been
	// explicitly set by the config or by the user. It's required to guarantee
	// backwards compatibility with older versions of libpod for which we must
	// query the database configuration. Not included in the on-disk config.
	StorageConfigGraphDriverNameSet bool `toml:"-"`
}

// NetworkConfig represents the "network" TOML config table
type NetworkConfig struct {
	// NetworkBackend determines what backend should be used for Podman's
	// networking.
	NetworkBackend string `toml:"network_backend,omitempty"`

	// CNIPluginDirs is where CNI plugin binaries are stored.
	CNIPluginDirs attributedstring.Slice `toml:"cni_plugin_dirs,omitempty"`

	// NetavarkPluginDirs is a list of directories which contain netavark plugins.
	NetavarkPluginDirs attributedstring.Slice `toml:"netavark_plugin_dirs,omitempty"`

	// DefaultNetwork is the network name of the default network
	// to attach pods to.
	DefaultNetwork string `toml:"default_network,omitempty"`

	// DefaultSubnet is the subnet to be used for the default network.
	// If a network with the name given in DefaultNetwork is not present
	// then a new network using this subnet will be created.
	// Must be a valid IPv4 CIDR block.
	DefaultSubnet string `toml:"default_subnet,omitempty"`

	// DefaultSubnetPools is a list of subnets and size which are used to
	// allocate subnets automatically for podman network create.
	// It will iterate through the list and will pick the first free subnet
	// with the given size. This is only used for ipv4 subnets, ipv6 subnets
	// are always assigned randomly.
	DefaultSubnetPools []SubnetPool `toml:"default_subnet_pools,omitempty"`

	// DefaultRootlessNetworkCmd is used to set the default rootless network
	// program, either "slirp4nents" (default) or "pasta".
	DefaultRootlessNetworkCmd string `toml:"default_rootless_network_cmd,omitempty"`

	// NetworkConfigDir is where network configuration files are stored.
	NetworkConfigDir string `toml:"network_config_dir,omitempty"`

	// DNSBindPort is the port that should be used by dns forwarding daemon
	// for netavark rootful bridges with dns enabled. This can be necessary
	// when other dns forwarders run on the machine. 53 is used if unset.
	DNSBindPort uint16 `toml:"dns_bind_port,omitempty,omitzero"`

	// PastaOptions contains a default list of pasta(1) options that should
	// be used when running pasta.
	PastaOptions attributedstring.Slice `toml:"pasta_options,omitempty"`
}

type SubnetPool struct {
	// Base is a bigger subnet which will be used to allocate a subnet with
	// the given size.
	Base *types.IPNet `toml:"base,omitempty"`
	// Size is the CIDR for the new subnet. It must be equal or small
	// than the CIDR from the base subnet.
	Size int `toml:"size,omitempty"`
}

// SecretConfig represents the "secret" TOML config table
type SecretConfig struct {
	// Driver specifies the secret driver to use.
	// Current valid value:
	//  * file
	//  * pass
	Driver string `toml:"driver,omitempty"`
	// Opts contains driver specific options
	Opts map[string]string `toml:"opts,omitempty"`
}

// ConfigMapConfig represents the "configmap" TOML config table
//
// revive does not like the name because the package is already called config
//
//nolint:revive
type ConfigMapConfig struct {
	// Driver specifies the configmap driver to use.
	// Current valid value:
	//  * file
	//  * pass
	Driver string `toml:"driver,omitempty"`
	// Opts contains driver specific options
	Opts map[string]string `toml:"opts,omitempty"`
}

// MachineConfig represents the "machine" TOML config table
type MachineConfig struct {
	// Number of CPU's a machine is created with.
	CPUs uint64 `toml:"cpus,omitempty,omitzero"`
	// DiskSize is the size of the disk in GB created when init-ing a podman-machine VM
	DiskSize uint64 `toml:"disk_size,omitempty,omitzero"`
	// Image is the image used when init-ing a podman-machine VM
	Image string `toml:"image,omitempty"`
	// Memory in MB a machine is created with.
	Memory uint64 `toml:"memory,omitempty,omitzero"`
	// User to use for rootless podman when init-ing a podman machine VM
	User string `toml:"user,omitempty"`
	// Volumes are host directories mounted into the VM by default.
	Volumes attributedstring.Slice `toml:"volumes,omitempty"`
	// Provider is the virtualization provider used to run podman-machine VM
	Provider string `toml:"provider,omitempty"`
}

// FarmConfig represents the "farm" TOML config tables
type FarmConfig struct {
	// Default is the default farm to be used when farming out builds
	Default string `toml:"default,omitempty"`
	// List is a map of farms created where key=farm-name and value=list of connections
	List map[string][]string `toml:"list,omitempty"`
}

// Destination represents destination for remote service
type Destination struct {
	// URI, required. Example: ssh://root@example.com:22/run/podman/podman.sock
	URI string `toml:"uri"`

	// Identity file with ssh key, optional
	Identity string `toml:"identity,omitempty"`

	// isMachine describes if the remote destination is a machine.
	IsMachine bool `toml:"is_machine,omitempty"`
}

// Consumes container image's os and arch and returns if any dedicated runtime was
// configured otherwise returns default runtime.
func (c *EngineConfig) ImagePlatformToRuntime(os string, arch string) string {
	platformString := os + "/" + arch
	if val, ok := c.PlatformToOCIRuntime[platformString]; ok {
		return val
	}
	return c.OCIRuntime
}

// CheckCgroupsAndAdjustConfig checks if we're running rootless with the systemd
// cgroup manager. In case the user session isn't available, we're switching the
// cgroup manager to cgroupfs.  Note, this only applies to rootless.
func (c *Config) CheckCgroupsAndAdjustConfig() {
	if !unshare.IsRootless() || c.Engine.CgroupManager != SystemdCgroupsManager {
		return
	}

	session := os.Getenv("DBUS_SESSION_BUS_ADDRESS")
	hasSession := session != ""
	if hasSession {
		for _, part := range strings.Split(session, ",") {
			if strings.HasPrefix(part, "unix:path=") {
				_, err := os.Stat(strings.TrimPrefix(part, "unix:path="))
				hasSession = err == nil
				break
			}
		}
	}

	if !hasSession && unshare.GetRootlessUID() != 0 {
		logrus.Warningf("The cgroupv2 manager is set to systemd but there is no systemd user session available")
		logrus.Warningf("For using systemd, you may need to log in using a user session")
		logrus.Warningf("Alternatively, you can enable lingering with: `loginctl enable-linger %d` (possibly as root)", unshare.GetRootlessUID())
		logrus.Warningf("Falling back to --cgroup-manager=cgroupfs")
		c.Engine.CgroupManager = CgroupfsCgroupsManager
	}
}

func (c *Config) addCAPPrefix() {
	caps := c.Containers.DefaultCapabilities.Get()
	newCaps := make([]string, 0, len(caps))
	for _, val := range caps {
		if !strings.HasPrefix(strings.ToLower(val), "cap_") {
			val = "CAP_" + strings.ToUpper(val)
		}
		newCaps = append(newCaps, val)
	}
	c.Containers.DefaultCapabilities.Set(newCaps)
}

// Validate is the main entry point for library configuration validation.
func (c *Config) Validate() error {
	if err := c.Containers.Validate(); err != nil {
		return fmt.Errorf("validating containers config: %w", err)
	}

	if !c.Containers.EnableLabeling {
		selinux.SetDisabled()
	}

	if err := c.Engine.Validate(); err != nil {
		return fmt.Errorf("validating engine configs: %w", err)
	}

	if err := c.Network.Validate(); err != nil {
		return fmt.Errorf("validating network configs %w", err)
	}

	return nil
}

// URI returns the URI Path to the machine image
func (m *MachineConfig) URI() string {
	uri := m.Image
	for _, val := range []string{"$ARCH", "$arch"} {
		uri = strings.Replace(uri, val, runtime.GOARCH, 1)
	}
	for _, val := range []string{"$OS", "$os"} {
		uri = strings.Replace(uri, val, runtime.GOOS, 1)
	}
	return uri
}

func (c *EngineConfig) findRuntime() string {
	// Search for crun first followed by runc, kata, runsc
	for _, name := range []string{"crun", "runc", "runj", "kata", "runsc", "ocijail"} {
		for _, v := range c.OCIRuntimes[name] {
			if _, err := os.Stat(v); err == nil {
				return name
			}
		}
		if path, err := exec.LookPath(name); err == nil {
			logrus.Debugf("Found default OCI runtime %s path via PATH environment variable", path)
			return name
		}
	}
	return ""
}

// Validate is the main entry point for Engine configuration validation
// It returns an `error` on validation failure, otherwise
// `nil`.
func (c *EngineConfig) Validate() error {
	if err := c.validatePaths(); err != nil {
		return err
	}

	if err := ValidateImageVolumeMode(c.ImageVolumeMode); err != nil {
		return err
	}
	// Check if the pullPolicy from containers.conf is valid
	// if it is invalid returns the error
	pullPolicy := strings.ToLower(c.PullPolicy)
	if _, err := ValidatePullPolicy(pullPolicy); err != nil {
		return fmt.Errorf("invalid pull type from containers.conf %q: %w", c.PullPolicy, err)
	}

	if _, err := ParseDBBackend(c.DBBackend); err != nil {
		return err
	}

	return nil
}

// Validate is the main entry point for containers configuration validation
// It returns an `error` on validation failure, otherwise
// `nil`.
func (c *ContainersConfig) Validate() error {
	if err := c.validateUlimits(); err != nil {
		return err
	}

	if err := c.validateDevices(); err != nil {
		return err
	}

	if err := c.validateTZ(); err != nil {
		return err
	}

	if err := c.validateUmask(); err != nil {
		return err
	}

	if c.LogSizeMax >= 0 && c.LogSizeMax < OCIBufSize {
		return fmt.Errorf("log size max should be negative or >= %d", OCIBufSize)
	}

	if _, err := units.FromHumanSize(c.ShmSize); err != nil {
		return fmt.Errorf("invalid --shm-size %s, %q", c.ShmSize, err)
	}

	return nil
}

// Validate is the main entry point for network configuration validation.
// The parameter `onExecution` specifies if the validation should include
// execution checks. It returns an `error` on validation failure, otherwise
// `nil`.
func (c *NetworkConfig) Validate() error {
	if &c.DefaultSubnetPools != &DefaultSubnetPools {
		for _, pool := range c.DefaultSubnetPools {
			if pool.Base.IP.To4() == nil {
				return fmt.Errorf("invalid subnet pool ip %q", pool.Base.IP)
			}
			ones, _ := pool.Base.IPNet.Mask.Size()
			if ones > pool.Size {
				return fmt.Errorf("invalid subnet pool, size is bigger than subnet %q", &pool.Base.IPNet)
			}
			if pool.Size > 32 {
				return errors.New("invalid subnet pool size, must be between 0-32")
			}
		}
	}

	return nil
}

// FindConmon iterates over (*Config).ConmonPath and returns the path
// to first (version) matching conmon binary. If non is found, we try
// to do a path lookup of "conmon".
func (c *Config) FindConmon() (string, error) {
	return findConmonPath(c.Engine.ConmonPath.Get(), "conmon")
}

func findConmonPath(paths []string, binaryName string) (string, error) {
	for _, path := range paths {
		stat, err := os.Stat(path)
		if err != nil {
			continue
		}
		if stat.IsDir() {
			continue
		}
		logrus.Debugf("Using conmon: %q", path)
		return path, nil
	}

	// Search the $PATH as last fallback
	if path, err := exec.LookPath(binaryName); err == nil {
		logrus.Debugf("Using conmon from $PATH: %q", path)
		return path, nil
	}

	return "", fmt.Errorf("could not find a working conmon binary (configured options: %v: %w)",
		paths, ErrInvalidArg)
}

// FindConmonRs iterates over (*Config).ConmonRsPath and returns the path
// to first (version) matching conmonrs binary. If non is found, we try
// to do a path lookup of "conmonrs".
func (c *Config) FindConmonRs() (string, error) {
	return findConmonPath(c.Engine.ConmonRsPath.Get(), "conmonrs")
}

// GetDefaultEnv returns the environment variables for the container.
// It will check the HTTPProxy and HostEnv booleans and add the appropriate
// environment variables to the container.
func (c *Config) GetDefaultEnv() []string {
	return c.GetDefaultEnvEx(c.Containers.EnvHost, c.Containers.HTTPProxy)
}

// GetDefaultEnvEx returns the environment variables for the container.
// It will check the HTTPProxy and HostEnv boolean parameters and return the appropriate
// environment variables for the container.
func (c *Config) GetDefaultEnvEx(envHost, httpProxy bool) []string {
	var env []string
	if envHost {
		env = append(env, os.Environ()...)
	} else if httpProxy {
		for _, p := range ProxyEnv {
			if val, ok := os.LookupEnv(p); ok {
				env = append(env, fmt.Sprintf("%s=%s", p, val))
			}
		}
	}
	return append(env, c.Containers.Env.Get()...)
}

// Capabilities returns the capabilities parses the Add and Drop capability
// list from the default capabiltiies for the container
func (c *Config) Capabilities(user string, addCapabilities, dropCapabilities []string) ([]string, error) {
	userNotRoot := func(user string) bool {
		if user == "" || user == "root" || user == "0" {
			return false
		}
		return true
	}

	defaultCapabilities := c.Containers.DefaultCapabilities.Get()
	if userNotRoot(user) {
		defaultCapabilities = []string{}
	}

	return capabilities.MergeCapabilities(defaultCapabilities, addCapabilities, dropCapabilities)
}

// Device parses device mapping string to a src, dest & permissions string
// Valid values for device looklike:
//
//	'/dev/sdc"
//	'/dev/sdc:/dev/xvdc"
//	'/dev/sdc:/dev/xvdc:rwm"
//	'/dev/sdc:rm"
func Device(device string) (src, dst, permissions string, err error) {
	permissions = "rwm"
	split := strings.Split(device, ":")
	switch len(split) {
	case 3:
		if !IsValidDeviceMode(split[2]) {
			return "", "", "", fmt.Errorf("invalid device mode: %s", split[2])
		}
		permissions = split[2]
		fallthrough
	case 2:
		if IsValidDeviceMode(split[1]) {
			permissions = split[1]
		} else {
			if split[1] == "" || split[1][0] != '/' {
				return "", "", "", fmt.Errorf("invalid device mode: %s", split[1])
			}
			dst = split[1]
		}
		fallthrough
	case 1:
		if !strings.HasPrefix(split[0], "/dev/") {
			return "", "", "", fmt.Errorf("invalid device mode: %s", split[0])
		}
		src = split[0]
	default:
		return "", "", "", fmt.Errorf("invalid device specification: %s", device)
	}

	if dst == "" {
		dst = src
	}
	return src, dst, permissions, nil
}

// IsValidDeviceMode checks if the mode for device is valid or not.
// IsValid mode is a composition of r (read), w (write), and m (mknod).
func IsValidDeviceMode(mode string) bool {
	legalDeviceMode := map[rune]bool{
		'r': true,
		'w': true,
		'm': true,
	}
	if mode == "" {
		return false
	}
	for _, c := range mode {
		if !legalDeviceMode[c] {
			return false
		}
		legalDeviceMode[c] = false
	}
	return true
}

func rootlessConfigPath() (string, error) {
	if configHome := os.Getenv("XDG_CONFIG_HOME"); configHome != "" {
		return filepath.Join(configHome, _configPath), nil
	}
	home, err := unshare.HomeDir()
	if err != nil {
		return "", err
	}

	return filepath.Join(home, UserOverrideContainersConfig), nil
}

func Path() string {
	if path := os.Getenv("CONTAINERS_CONF"); path != "" {
		return path
	}
	if unshare.IsRootless() {
		if rpath, err := rootlessConfigPath(); err == nil {
			return rpath
		}
		return "$HOME/" + UserOverrideContainersConfig
	}
	return OverrideContainersConfig
}

// ReadCustomConfig reads the custom config and only generates a config based on it
// If the custom config file does not exists, function will return an empty config
func ReadCustomConfig() (*Config, error) {
	path, err := customConfigFile()
	if err != nil {
		return nil, err
	}
	newConfig := &Config{}
	if _, err := os.Stat(path); err == nil {
		if err := readConfigFromFile(path, newConfig); err != nil {
			return nil, err
		}
	} else {
		if !errors.Is(err, os.ErrNotExist) {
			return nil, err
		}
	}
	// Let's always initialize the farm list so it is never nil
	if newConfig.Farms.List == nil {
		newConfig.Farms.List = make(map[string][]string)
	}
	return newConfig, nil
}

// Write writes the configuration to the default file
func (c *Config) Write() error {
	var err error
	path, err := customConfigFile()
	if err != nil {
		return err
	}
	if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
		return err
	}

	opts := &ioutils.AtomicFileWriterOptions{ExplicitCommit: true}
	configFile, err := ioutils.NewAtomicFileWriterWithOpts(path, 0o644, opts)
	if err != nil {
		return err
	}
	defer configFile.Close()

	enc := toml.NewEncoder(configFile)
	if err := enc.Encode(c); err != nil {
		return err
	}

	// If no errors commit the changes to the config file
	return configFile.Commit()
}

// Reload clean the cached config and reloads the configuration from containers.conf files
// This function is meant to be used for long-running processes that need to reload potential changes made to
// the cached containers.conf files.
func Reload() (*Config, error) {
	return New(&Options{SetDefault: true})
}

var (
	bindirFailed = false
	bindirCached = ""
)

func findBindir() string {
	if bindirCached != "" || bindirFailed {
		return bindirCached
	}
	execPath, err := os.Executable()
	if err == nil {
		// Resolve symbolic links to find the actual binary file path.
		execPath, err = filepath.EvalSymlinks(execPath)
	}
	if err != nil {
		// If failed to find executable (unlikely to happen), warn about it.
		// The bindirFailed flag will track this, so we only warn once.
		logrus.Warnf("Failed to find $BINDIR: %v", err)
		bindirFailed = true
		return ""
	}
	bindirCached = filepath.Dir(execPath)
	return bindirCached
}

// FindHelperBinary will search the given binary name in the configured directories.
// If searchPATH is set to true it will also search in $PATH.
func (c *Config) FindHelperBinary(name string, searchPATH bool) (string, error) {
	dirList := c.Engine.HelperBinariesDir.Get()
	bindirPath := ""
	bindirSearched := false

	// If set, search this directory first. This is used in testing.
	if dir, found := os.LookupEnv("CONTAINERS_HELPER_BINARY_DIR"); found {
		dirList = append([]string{dir}, dirList...)
	}

	for _, path := range dirList {
		if path == bindirPrefix || strings.HasPrefix(path, bindirPrefix+string(filepath.Separator)) {
			// Calculate the path to the executable first time we encounter a $BINDIR prefix.
			if !bindirSearched {
				bindirSearched = true
				bindirPath = findBindir()
			}
			// If there's an error, don't stop the search for the helper binary.
			// findBindir() will have warned once during the first failure.
			if bindirPath == "" {
				continue
			}
			// Replace the $BINDIR prefix with the path to the directory of the current binary.
			if path == bindirPrefix {
				path = bindirPath
			} else {
				path = filepath.Join(bindirPath, strings.TrimPrefix(path, bindirPrefix+string(filepath.Separator)))
			}
		}
		// Absolute path will force exec.LookPath to check for binary existence instead of lookup everywhere in PATH
		if abspath, err := filepath.Abs(filepath.Join(path, name)); err == nil {
			// exec.LookPath from absolute path on Unix is equal to os.Stat + IsNotDir + check for executable bits in FileMode
			// exec.LookPath from absolute path on Windows is equal to os.Stat + IsNotDir for `file.ext` or loops through extensions from PATHEXT for `file`
			if lp, err := exec.LookPath(abspath); err == nil {
				return lp, nil
			}
		}
	}
	if searchPATH {
		return exec.LookPath(name)
	}
	configHint := "To resolve this error, set the helper_binaries_dir key in the `[engine]` section of containers.conf to the directory containing your helper binaries."
	if len(c.Engine.HelperBinariesDir.Get()) == 0 {
		return "", fmt.Errorf("could not find %q because there are no helper binary directories configured.  %s", name, configHint)
	}
	return "", fmt.Errorf("could not find %q in one of %v.  %s", name, c.Engine.HelperBinariesDir, configHint)
}

// ImageCopyTmpDir default directory to store temporary image files during copy
func (c *Config) ImageCopyTmpDir() (string, error) {
	if path, found := os.LookupEnv("TMPDIR"); found {
		return path, nil
	}
	switch c.Engine.ImageCopyTmpDir {
	case "":
		return "", nil
	case "storage":
		return filepath.Join(c.Engine.graphRoot, "tmp"), nil
	default:
		if filepath.IsAbs(c.Engine.ImageCopyTmpDir) {
			return c.Engine.ImageCopyTmpDir, nil
		}
	}

	return "", fmt.Errorf("invalid image_copy_tmp_dir value %q (relative paths are not accepted)", c.Engine.ImageCopyTmpDir)
}

// setupEnv sets the environment variables for the engine
func (c *Config) setupEnv() error {
	for _, env := range c.Engine.Env.Get() {
		splitEnv := strings.SplitN(env, "=", 2)
		if len(splitEnv) != 2 {
			logrus.Warnf("invalid environment variable for engine %s, valid configuration is KEY=value pair", env)
			continue
		}
		// skip if the env is already defined
		if _, ok := os.LookupEnv(splitEnv[0]); ok {
			logrus.Debugf("environment variable %s is already defined, skip the settings from containers.conf", splitEnv[0])
			continue
		}
		if err := os.Setenv(splitEnv[0], splitEnv[1]); err != nil {
			return err
		}
	}
	return nil
}

// eventsLogMaxSize is the type used by EventsLogFileMaxSize
type eventsLogMaxSize uint64

// UnmarshalText parses the JSON encoding of eventsLogMaxSize and
// stores it in a value.
func (e *eventsLogMaxSize) UnmarshalText(text []byte) error {
	// REMOVE once writing works
	if string(text) == "" {
		return nil
	}
	val, err := units.FromHumanSize((string(text)))
	if err != nil {
		return err
	}
	if val < 0 {
		return fmt.Errorf("events log file max size cannot be negative: %s", string(text))
	}
	*e = eventsLogMaxSize(uint64(val))
	return nil
}

// MarshalText returns the JSON encoding of eventsLogMaxSize.
func (e eventsLogMaxSize) MarshalText() ([]byte, error) {
	if uint64(e) == DefaultEventsLogSizeMax || e == 0 {
		v := []byte{}
		return v, nil
	}
	return []byte(fmt.Sprintf("%d", e)), nil
}

func ValidateImageVolumeMode(mode string) error {
	if mode == "" {
		return nil
	}
	if util.StringInSlice(mode, validImageVolumeModes) {
		return nil
	}

	return fmt.Errorf("invalid image volume mode %q required value: %s", mode, strings.Join(validImageVolumeModes, ", "))
}

// FindInitBinary will return the path to the init binary (catatonit)
func (c *Config) FindInitBinary() (string, error) {
	// Sigh, for some reason we ended up with two InitPath field in containers.conf and
	// both are used in podman so we have to keep supporting both to prevent regressions.
	if c.Containers.InitPath != "" {
		return c.Containers.InitPath, nil
	}
	if c.Engine.InitPath != "" {
		return c.Engine.InitPath, nil
	}
	// keep old default working to guarantee backwards comapt
	if _, err := os.Stat(DefaultInitPath); err == nil {
		return DefaultInitPath, nil
	}
	return c.FindHelperBinary(defaultInitName, true)
}
