package generate import ( "fmt" "strconv" "strings" "github.com/containers/podman/v4/pkg/systemd/define" ) // minTimeoutStopSec is the minimal stop timeout for generated systemd units. // Once exceeded, processes of the services are killed and the cgroup(s) are // cleaned up. const minTimeoutStopSec = 60 // validateRestartPolicy checks that the user-provided policy is valid. func validateRestartPolicy(restart string) error { for _, i := range define.RestartPolicies { if i == restart { return nil } } return fmt.Errorf("%s is not a valid restart policy", restart) } const headerTemplate = `# {{{{.ServiceName}}}}{{{{- if (eq .IdentifySpecifier true) }}}}@{{{{- end}}}}.service {{{{- if (eq .GenerateNoHeader false) }}}} # autogenerated by Podman {{{{.PodmanVersion}}}} {{{{- if .TimeStamp}}}} # {{{{.TimeStamp}}}} {{{{- end}}}} {{{{- end}}}} [Unit] Description=Podman {{{{.ServiceName}}}}.service{{{{- if (eq .IdentifySpecifier true) }}}} for %I{{{{- end}}}} Documentation=man:podman-generate-systemd(1) Wants=network-online.target After=network-online.target RequiresMountsFor={{{{.RunRoot}}}} ` // filterPodFlags removes --pod, --pod-id-file and --infra-conmon-pidfile from the specified command. // argCount is the number of last arguments which should not be filtered, e.g. the container entrypoint. func filterPodFlags(command []string, argCount int) []string { processed := make([]string, 0, len(command)) for i := 0; i < len(command)-argCount; i++ { s := command[i] if s == "--pod" || s == "--pod-id-file" || s == "--infra-conmon-pidfile" { i++ continue } if strings.HasPrefix(s, "--pod=") || strings.HasPrefix(s, "--pod-id-file=") || strings.HasPrefix(s, "--infra-conmon-pidfile=") { continue } processed = append(processed, s) } processed = append(processed, command[len(command)-argCount:]...) return processed } // filterCommonContainerFlags removes --sdnotify, --rm and --cgroups from the specified command. // argCount is the number of last arguments which should not be filtered, e.g. the container entrypoint. func filterCommonContainerFlags(command []string, argCount int) []string { processed := make([]string, 0, len(command)) for i := 0; i < len(command)-argCount; i++ { s := command[i] switch { case s == "--rm": // Boolean flags support --flag and --flag={true,false}. continue case s == "--cgroups", s == "--cidfile", s == "--restart": i++ continue case strings.HasPrefix(s, "--rm="), strings.HasPrefix(s, "--cgroups="), strings.HasPrefix(s, "--cidfile="), strings.HasPrefix(s, "--restart="): continue } processed = append(processed, s) } processed = append(processed, command[len(command)-argCount:]...) return processed } // escapeSystemdArguments makes sure that all arguments with at least one whitespace // are quoted to make sure those are interpreted as one argument instead of // multiple ones. Also make sure to escape all characters which have a special // meaning to systemd -> $,% and \ // see: https://www.freedesktop.org/software/systemd/man/systemd.service.html#Command%20lines func escapeSystemdArguments(command []string) []string { for i := range command { command[i] = escapeSystemdArg(command[i]) } return command } func escapeSystemdArg(arg string) string { arg = strings.ReplaceAll(arg, "$", "$$") arg = strings.ReplaceAll(arg, "%", "%%") if strings.ContainsAny(arg, " \t\"") { arg = strconv.Quote(arg) } else if strings.Contains(arg, `\`) { // strconv.Quote also escapes backslashes so // we should replace only if strconv.Quote was not used arg = strings.ReplaceAll(arg, `\`, `\\`) } return arg } func removeSdNotifyArg(args []string, argCount int) []string { processed := make([]string, 0, len(args)) for i := 0; i < len(args)-argCount; i++ { s := args[i] switch { case s == "--sdnotify": i++ continue case strings.HasPrefix(s, "--sdnotify="): continue } processed = append(processed, s) } processed = append(processed, args[len(args)-argCount:]...) return processed } func removeDetachArg(args []string, argCount int) []string { // "--detach=false" could also be in the container entrypoint // split them off so we do not remove it there realArgs := args[len(args)-argCount:] flagArgs := removeArg("-d=false", args[:len(args)-argCount]) flagArgs = removeArg("--detach=false", flagArgs) return append(flagArgs, realArgs...) } func removeReplaceArg(args []string, argCount int) []string { // "--replace=false" could also be in the container entrypoint // split them off so we do not remove it there realArgs := args[len(args)-argCount:] flagArgs := removeArg("--replace=false", args[:len(args)-argCount]) return append(flagArgs, realArgs...) } func removeArg(arg string, args []string) []string { newArgs := []string{} for _, a := range args { if a != arg { newArgs = append(newArgs, a) } } return newArgs } // This function is used to get name of systemd service from prefix, separator, and // container/pod name. If prefix is empty, the service name does not include the // separator. This is to avoid a situation where service name starts with the separator // which is usually hyphen. func getServiceName(prefix string, separator string, name string) string { serviceName := name if len(prefix) > 0 { serviceName = prefix + separator + name } return serviceName }