Ctrl K
ComunidadGuías…

svcopts_escape_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
//go:build !windows

package main

import (
	"errors"
	"strings"
	"testing"
	"text/template"

	"github.com/kardianos/service"
)

// fakeSystemd stands in for kardianos's systemd backend so applyServiceOpts
// takes its systemd branch on any host. Only String matters here; New is never
// reached because the test renders the unit itself.
type fakeSystemd struct{}

func (fakeSystemd) String() string    { return "linux-systemd" }
func (fakeSystemd) Detect() bool      { return true }
func (fakeSystemd) Interactive() bool { return true }
func (fakeSystemd) New(service.Interface, *service.Config) (service.Service, error) {
	return nil, errors.New("fakeSystemd cannot build a service")
}

// kardianosFuncs mirrors the func map kardianos feeds the systemd template
// (service_linux.go): cmd double-quotes an argument and escapes only the
// double quote; cmdEscape rewrites spaces in the executable path.
var kardianosFuncs = template.FuncMap{
	"cmd":       func(s string) string { return `"` + strings.ReplaceAll(s, `"`, `\"`) + `"` },
	"cmdEscape": func(s string) string { return strings.ReplaceAll(s, " ", `\x20`) },
}

// renderUnit renders the self-install unit for the flags an operator passed to
// `pingularity install`, exactly as controlCmd + svcConfig hand them to
// kardianos, and returns its ExecStart line.
func renderUnit(t *testing.T, flags []string) string {
	t.Helper()
	sc := svcConfig(append([]string{"run"}, flags...))
	tmpl, err := template.New("").Funcs(kardianosFuncs).Parse(systemdScript)
	if err != nil {
		t.Fatalf("systemdScript failed to parse: %v", err)
	}
	data := struct {
		Description      string
		Path             string
		Name             string
		Dependencies     []string
		Arguments        []string
		WorkingDirectory string
		ReloadSignal     string
		Restart          string
	}{
		Description: sc.Description, Path: "/usr/local/bin/pingularity", Name: sc.Name,
		Dependencies: sc.Dependencies, Arguments: sc.Arguments,
		ReloadSignal: "HUP", Restart: "always",
	}
	var b strings.Builder
	if err := tmpl.Execute(&b, data); err != nil {
		t.Fatalf("systemdScript failed to render: %v", err)
	}
	for _, line := range strings.Split(b.String(), "\n") {
		if strings.HasPrefix(line, "ExecStart=") {
			return line
		}
	}
	t.Fatalf("no ExecStart line in rendered unit:\n%s", b.String())
	return ""
}

// systemd reads ExecStart with its own rules, double quotes or not: %x is a
// specifier (%t the runtime directory, %h the home), $VAR and ${VAR} are
// environment substitutions, and a backslash starts a C escape. kardianos's
// cmd escapes only the double quote, so a -metrics-token with a % in it reached
// the daemon as a different token and a trailing backslash left a unit systemd
// refuses to load. Each install argument must reach the unit escaped per
// systemd.unit(5)/systemd.service(5): %% for %, $$ for $, a doubled backslash -
// while the template's own $PINGULARITY_OPTS stays expandable.
func TestSystemdUnitKeepsFlagValuesVerbatim(t *testing.T) {
	orig := service.AvailableSystems()
	service.ChooseSystem(fakeSystemd{})
	t.Cleanup(func() { service.ChooseSystem(orig...) })
	if service.Platform() != "linux-systemd" {
		t.Fatalf("platform stand-in not in effect: %q", service.Platform())
	}

	line := renderUnit(t, []string{"-metrics-token", "tok%tX", "-db", `/var/lib/ping${HOME}y`, "-allow-host", `a\`, "-listen", ":9000"})

	for _, want := range []string{
		`"tok%%tX"`,                // %t is systemd's runtime directory; %% is a literal percent
		`"/var/lib/ping$${HOME}y"`, // ${HOME} substitutes even mid-word; $$ is a literal dollar
		`"a\\"`,                    // a lone trailing backslash escapes the closing quote
		`":9000"`,                  // an ordinary value is untouched
		" $PINGULARITY_OPTS",       // the template's own variable must still expand
	} {
		if !strings.Contains(line, want) {
			t.Errorf("ExecStart lacks %s:\n%s", want, line)
		}
	}
	for _, bad := range []string{`"tok%tX"`, `ping${HOME}y"`, `"a\"`} {
		if strings.Contains(line, bad) {
			t.Errorf("ExecStart still carries the raw value %s, which systemd rewrites:\n%s", bad, line)
		}
	}
}

// launchd's plist and the Windows SCM expand nothing, so on any other platform
// the arguments must reach the service definition exactly as typed.
func TestNonSystemdPlatformsKeepArgumentsAsTyped(t *testing.T) {
	if service.Platform() == "linux-systemd" {
		t.Skip("this host is systemd; the escaping is right here")
	}
	sc := svcConfig([]string{"run", "-metrics-token", "tok%tX", "-db", `C:\data\p.db`})
	if got, want := strings.Join(sc.Arguments, " "), `run -metrics-token tok%tX -db C:\data\p.db`; got != want {
		t.Errorf("arguments changed on %s: got %q, want %q", service.Platform(), got, want)
	}
}

// systemdArgs applies systemd's three escapes and nothing else: %% for a
// percent, $$ for a dollar, a doubled backslash - in that order, so the
// backslash doubling never touches what the other two produce - and leaves the
// double quote to kardianos's cmd.
func TestSystemdArgs(t *testing.T) {
	cases := []struct{ in, want string }{
		{"run", "run"},
		{":9000", ":9000"},
		{"tok%tX", "tok%%tX"},
		{"100%", "100%%"},
		{"$HOME", "$$HOME"},
		{"a${X}b", "a$${X}b"},
		{"$$", "$$$$"},
		{`a\`, `a\\`},
		{`C:\new\tab`, `C:\\new\\tab`}, // \n and \t would otherwise become a newline and a tab
		{`\%`, `\\%%`},                 // backslash first: the %% it precedes is not re-escaped
		{`say "hi"`, `say "hi"`},       // the quote is cmd's job
		{"", ""},
	}
	for _, c := range cases {
		if got := systemdArgs([]string{c.in})[0]; got != c.want {
			t.Errorf("systemdArgs(%q) = %q, want %q", c.in, got, c.want)
		}
	}
	if got := systemdArgs(nil); got != nil {
		t.Errorf("systemdArgs(nil) = %v, want nil (start/stop/status carry no arguments)", got)
	}
	in := []string{"run", "-db", "%h"}
	out := systemdArgs(in)
	if strings.Join(in, " ") != "run -db %h" {
		t.Errorf("systemdArgs rewrote its input in place: %q", in)
	}
	if strings.Join(out, " ") != "run -db %%h" {
		t.Errorf("systemdArgs(%q) = %q", in, out)
	}
}