mirror of
https://github.com/containers/podman.git
synced 2025-10-12 08:45:37 +08:00
Merge pull request #26553 from l0rd/wsl-utf8
Enforce WSL UTF-8 encoded output
This commit is contained in:
@ -48,7 +48,7 @@ func installWslKernel() error {
|
||||
)
|
||||
backoff := 500 * time.Millisecond
|
||||
for i := 1; i < 6; i++ {
|
||||
err = wutil.SilentExec("wsl", "--update")
|
||||
err = wutil.SilentExec("--update")
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
|
2
go.mod
2
go.mod
@ -73,7 +73,6 @@ require (
|
||||
golang.org/x/sync v0.15.0
|
||||
golang.org/x/sys v0.33.0
|
||||
golang.org/x/term v0.32.0
|
||||
golang.org/x/text v0.26.0
|
||||
google.golang.org/protobuf v1.36.6
|
||||
gopkg.in/inf.v0 v0.9.1
|
||||
gopkg.in/yaml.v3 v3.0.1
|
||||
@ -185,6 +184,7 @@ require (
|
||||
go.yaml.in/yaml/v2 v2.4.2 // indirect
|
||||
golang.org/x/mod v0.25.0 // indirect
|
||||
golang.org/x/oauth2 v0.30.0 // indirect
|
||||
golang.org/x/text v0.26.0 // indirect
|
||||
golang.org/x/time v0.11.0 // indirect
|
||||
golang.org/x/tools v0.33.0 // indirect
|
||||
google.golang.org/genproto/googleapis/api v0.0.0-20250303144028-a0af3efb3deb // indirect
|
||||
|
@ -20,11 +20,10 @@ import (
|
||||
"github.com/containers/podman/v5/pkg/machine/env"
|
||||
"github.com/containers/podman/v5/pkg/machine/ignition"
|
||||
"github.com/containers/podman/v5/pkg/machine/vmconfigs"
|
||||
"github.com/containers/podman/v5/pkg/machine/wsl/wutil"
|
||||
"github.com/containers/podman/v5/utils"
|
||||
"github.com/containers/storage/pkg/homedir"
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -100,13 +99,9 @@ func provisionWSLDist(name string, imagePath string, prompt string) (string, err
|
||||
// 1. Wsl/Service/RegisterDistro/CreateVm/HCS/ERROR_NOT_SUPPORTED
|
||||
// 2. Wsl/Service/RegisterDistro/CreateVm/HCS/HCS_E_SERVICE_NOT_AVAILABLE
|
||||
cmdOutput := &bytes.Buffer{}
|
||||
err = runCmdPassThroughTee(cmdOutput, "wsl", "--import", dist, distTarget, imagePath, "--version", "2")
|
||||
decoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()
|
||||
decoded, _, decodeErr := transform.Bytes(decoder, cmdOutput.Bytes())
|
||||
if decodeErr != nil {
|
||||
return "", fmt.Errorf("failed to decode WSL output: %w", decodeErr)
|
||||
}
|
||||
decodedStr := strings.ToLower(string(decoded))
|
||||
cmd := wutil.NewWSLCommand("--import", dist, distTarget, imagePath, "--version", "2")
|
||||
err = runCmdPassThroughTee(cmdOutput, cmd)
|
||||
decodedStr := strings.ToLower(cmdOutput.String())
|
||||
for _, substr := range []string{"hcs/error_not_supported", "hcs/hcs_e_service_not_available"} {
|
||||
if strings.Contains(decodedStr, substr) {
|
||||
return "", ErrWslNotSupported
|
||||
@ -373,13 +368,15 @@ func installWsl() error {
|
||||
return err
|
||||
}
|
||||
defer log.Close()
|
||||
if err := runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
|
||||
"/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart"); isMsiError(err) {
|
||||
cmd := exec.Command("dism", "/online", "/enable-feature",
|
||||
"/featurename:Microsoft-Windows-Subsystem-Linux", "/all", "/norestart")
|
||||
if err := runCmdPassThroughTee(log, cmd); isMsiError(err) {
|
||||
return fmt.Errorf("could not enable WSL Feature: %w", err)
|
||||
}
|
||||
|
||||
if err = runCmdPassThroughTee(log, "dism", "/online", "/enable-feature",
|
||||
"/featurename:VirtualMachinePlatform", "/all", "/norestart"); isMsiError(err) {
|
||||
cmd = exec.Command("dism", "/online", "/enable-feature",
|
||||
"/featurename:VirtualMachinePlatform", "/all", "/norestart")
|
||||
if err = runCmdPassThroughTee(log, cmd); isMsiError(err) {
|
||||
return fmt.Errorf("could not enable Virtual Machine Feature: %w", err)
|
||||
}
|
||||
|
||||
@ -466,50 +463,49 @@ func withUser(s string, user string) string {
|
||||
func wslInvoke(dist string, arg ...string) error {
|
||||
newArgs := []string{"-u", "root", "-d", dist}
|
||||
newArgs = append(newArgs, arg...)
|
||||
return runCmdPassThrough("wsl", newArgs...)
|
||||
cmd := wutil.NewWSLCommand(newArgs...)
|
||||
return runCmdPassThrough(cmd)
|
||||
}
|
||||
|
||||
func wslPipe(input string, dist string, arg ...string) error {
|
||||
newArgs := []string{"-u", "root", "-d", dist}
|
||||
newArgs = append(newArgs, arg...)
|
||||
return pipeCmdPassThrough("wsl", input, newArgs...)
|
||||
cmd := wutil.NewWSLCommand(newArgs...)
|
||||
return pipeCmdPassThrough(cmd, input)
|
||||
}
|
||||
|
||||
func runCmdPassThrough(name string, arg ...string) error {
|
||||
logrus.Debugf("Running command: %s %v", name, arg)
|
||||
cmd := exec.Command(name, arg...)
|
||||
func runCmdPassThrough(cmd *exec.Cmd) error {
|
||||
logrus.Debugf("Running command: %s %v", cmd.Path, cmd.Args)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
|
||||
return fmt.Errorf("command %s %v failed: %w", cmd.Path, cmd.Args, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func runCmdPassThroughTee(out io.Writer, name string, arg ...string) error {
|
||||
logrus.Debugf("Running command: %s %v", name, arg)
|
||||
func runCmdPassThroughTee(out io.Writer, cmd *exec.Cmd) error {
|
||||
logrus.Debugf("Running command: %s %v", cmd.Path, cmd.Args)
|
||||
|
||||
// TODO - Perhaps improve this with a conpty pseudo console so that
|
||||
// dism installer text bars mirror console behavior (redraw)
|
||||
cmd := exec.Command(name, arg...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = io.MultiWriter(os.Stdout, out)
|
||||
cmd.Stderr = io.MultiWriter(os.Stderr, out)
|
||||
if err := cmd.Run(); isMsiError(err) {
|
||||
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
|
||||
return fmt.Errorf("command %s %v failed: %w", cmd.Path, cmd.Args, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func pipeCmdPassThrough(name string, input string, arg ...string) error {
|
||||
logrus.Debugf("Running command: %s %v", name, arg)
|
||||
cmd := exec.Command(name, arg...)
|
||||
func pipeCmdPassThrough(cmd *exec.Cmd, input string) error {
|
||||
logrus.Debugf("Running command: %s %v", cmd.Path, cmd.Args)
|
||||
cmd.Stdin = strings.NewReader(input)
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return fmt.Errorf("command %s %v failed: %w", name, arg, err)
|
||||
return fmt.Errorf("command %s %v failed: %w", cmd.Path, cmd.Args, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -569,7 +565,7 @@ func getAllWSLDistros(running bool) (map[string]struct{}, error) {
|
||||
if running {
|
||||
args = append(args, "--running")
|
||||
}
|
||||
cmd := exec.Command("wsl", args...)
|
||||
cmd := wutil.NewWSLCommand(args...)
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -581,7 +577,7 @@ func getAllWSLDistros(running bool) (map[string]struct{}, error) {
|
||||
}
|
||||
|
||||
all := make(map[string]struct{})
|
||||
scanner := bufio.NewScanner(transform.NewReader(out, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
|
||||
scanner := bufio.NewScanner(out)
|
||||
for scanner.Scan() {
|
||||
fields := strings.Fields(scanner.Text())
|
||||
if len(fields) > 0 {
|
||||
@ -598,7 +594,7 @@ func getAllWSLDistros(running bool) (map[string]struct{}, error) {
|
||||
}
|
||||
|
||||
func isSystemdRunning(dist string) (bool, error) {
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh")
|
||||
cmd := wutil.NewWSLCommand("-u", "root", "-d", dist, "sh")
|
||||
cmd.Stdin = strings.NewReader(sysdpid + "\necho $SYSDPID\n")
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
@ -621,26 +617,26 @@ func isSystemdRunning(dist string) (bool, error) {
|
||||
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return false, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(stderr.String()))
|
||||
return false, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args[1:], err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func terminateDist(dist string) error {
|
||||
cmd := exec.Command("wsl", "--terminate", dist)
|
||||
cmd := wutil.NewWSLCommand("--terminate", dist)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(string(out)))
|
||||
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args[1:], err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func unregisterDist(dist string) error {
|
||||
cmd := exec.Command("wsl", "--unregister", dist)
|
||||
cmd := wutil.NewWSLCommand("--unregister", dist)
|
||||
out, err := cmd.CombinedOutput()
|
||||
if err != nil {
|
||||
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(string(out)))
|
||||
return fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args[1:], err, strings.TrimSpace(string(out)))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -685,13 +681,14 @@ func getCPUs(name string) (uint64, error) {
|
||||
if run, _ := isWSLRunning(dist); !run {
|
||||
return 0, nil
|
||||
}
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "nproc")
|
||||
cmd := wutil.NewWSLCommand("-u", "root", "-d", dist, "nproc")
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
stderr := &bytes.Buffer{}
|
||||
cmd.Stderr = stderr
|
||||
cmd.Env = []string{"WSL_UTF8=1"}
|
||||
if err = cmd.Start(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
@ -702,7 +699,7 @@ func getCPUs(name string) (uint64, error) {
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(strings.TrimSpace(stderr.String())))
|
||||
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args[1:], err, strings.TrimSpace(strings.TrimSpace(stderr.String())))
|
||||
}
|
||||
|
||||
ret, err := strconv.Atoi(result)
|
||||
@ -715,7 +712,7 @@ func getMem(name string) (strongunits.MiB, error) {
|
||||
if run, _ := isWSLRunning(dist); !run {
|
||||
return 0, nil
|
||||
}
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "cat", "/proc/meminfo")
|
||||
cmd := wutil.NewWSLCommand("-u", "root", "-d", dist, "cat", "/proc/meminfo")
|
||||
out, err := cmd.StdoutPipe()
|
||||
if err != nil {
|
||||
return 0, err
|
||||
@ -746,7 +743,7 @@ func getMem(name string) (strongunits.MiB, error) {
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args, err, strings.TrimSpace(stderr.String()))
|
||||
return 0, fmt.Errorf("command %s %v failed: %w (%s)", cmd.Path, cmd.Args[1:], err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
|
||||
return strongunits.MiB(total - available), err
|
||||
|
@ -7,10 +7,10 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
|
||||
"github.com/containers/podman/v5/pkg/machine/env"
|
||||
"github.com/containers/podman/v5/pkg/machine/wsl/wutil"
|
||||
|
||||
gvproxy "github.com/containers/gvisor-tap-vsock/pkg/types"
|
||||
"github.com/containers/podman/v5/pkg/machine"
|
||||
@ -108,7 +108,8 @@ func (w WSLStubber) Remove(mc *vmconfigs.MachineConfig) ([]string, func() error,
|
||||
// below if we wanted to hard error on the wsl unregister
|
||||
// of the vm
|
||||
wslRemoveFunc := func() error {
|
||||
if err := runCmdPassThrough("wsl", "--unregister", env.WithPodmanPrefix(mc.Name)); err != nil {
|
||||
cmd := wutil.NewWSLCommand("--unregister", env.WithPodmanPrefix(mc.Name))
|
||||
if err := runCmdPassThrough(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
@ -253,7 +254,7 @@ func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
|
||||
fmt.Fprintf(os.Stderr, "Could not stop API forwarding service (win-sshproxy.exe): %s\n", err.Error())
|
||||
}
|
||||
|
||||
cmd := exec.Command("wsl", "-u", "root", "-d", dist, "sh")
|
||||
cmd := wutil.NewWSLCommand("-u", "root", "-d", dist, "sh")
|
||||
cmd.Stdin = strings.NewReader(waitTerm)
|
||||
out := &bytes.Buffer{}
|
||||
cmd.Stderr = out
|
||||
@ -263,7 +264,7 @@ func (w WSLStubber) StopVM(mc *vmconfigs.MachineConfig, hardStop bool) error {
|
||||
return fmt.Errorf("executing wait command: %w", err)
|
||||
}
|
||||
|
||||
exitCmd := exec.Command("wsl", "-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
|
||||
exitCmd := wutil.NewWSLCommand("-u", "root", "-d", dist, "/usr/local/bin/enterns", "systemctl", "exit", "0")
|
||||
if err = exitCmd.Run(); err != nil {
|
||||
return fmt.Errorf("stopping systemd: %w", err)
|
||||
}
|
||||
|
@ -6,13 +6,11 @@ import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"os/exec"
|
||||
"strings"
|
||||
"sync"
|
||||
"syscall"
|
||||
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
|
||||
var (
|
||||
@ -29,8 +27,14 @@ type wslStatus struct {
|
||||
wslFeatureEnabled bool
|
||||
}
|
||||
|
||||
func NewWSLCommand(arg ...string) *exec.Cmd {
|
||||
cmd := exec.Command("wsl", arg...)
|
||||
cmd.Env = append(os.Environ(), "WSL_UTF8=1")
|
||||
return cmd
|
||||
}
|
||||
|
||||
func SilentExec(command string, args ...string) error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd := NewWSLCommand(args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
@ -40,8 +44,8 @@ func SilentExec(command string, args ...string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func SilentExecCmd(command string, args ...string) *exec.Cmd {
|
||||
cmd := exec.Command(command, args...)
|
||||
func SilentExecCmd(args ...string) *exec.Cmd {
|
||||
cmd := NewWSLCommand(args...)
|
||||
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x08000000}
|
||||
return cmd
|
||||
}
|
||||
@ -53,7 +57,7 @@ func parseWSLStatus() wslStatus {
|
||||
vmpFeatureEnabled: false,
|
||||
wslFeatureEnabled: false,
|
||||
}
|
||||
cmd := SilentExecCmd("wsl", "--status")
|
||||
cmd := SilentExecCmd("--status")
|
||||
out, err := cmd.StdoutPipe()
|
||||
cmd.Stderr = nil
|
||||
if err != nil {
|
||||
@ -79,7 +83,7 @@ func IsWSLInstalled() bool {
|
||||
}
|
||||
|
||||
func IsWSLStoreVersionInstalled() bool {
|
||||
cmd := SilentExecCmd("wsl", "--version")
|
||||
cmd := SilentExecCmd("--version")
|
||||
cmd.Stdout = nil
|
||||
cmd.Stderr = nil
|
||||
if err := cmd.Run(); err != nil {
|
||||
@ -95,7 +99,7 @@ func matchOutputLine(output io.ReadCloser) wslStatus {
|
||||
vmpFeatureEnabled: true,
|
||||
wslFeatureEnabled: true,
|
||||
}
|
||||
scanner := bufio.NewScanner(transform.NewReader(output, unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewDecoder()))
|
||||
scanner := bufio.NewScanner(output)
|
||||
for scanner.Scan() {
|
||||
line := scanner.Text()
|
||||
for _, match := range wslNotInstalledMessages {
|
||||
|
@ -8,7 +8,6 @@ import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -136,11 +135,15 @@ func TestMatchOutputLine(t *testing.T) {
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.winVariant, func(t *testing.T) {
|
||||
encoder := unicode.UTF16(unicode.LittleEndian, unicode.UseBOM).NewEncoder()
|
||||
encodedOutput, err := encoder.String(tt.statusOutput)
|
||||
assert.Nil(t, err)
|
||||
reader := io.NopCloser(strings.NewReader(encodedOutput))
|
||||
reader := io.NopCloser(strings.NewReader(tt.statusOutput))
|
||||
assert.Equal(t, tt.want, matchOutputLine(reader))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWSLCommand(t *testing.T) {
|
||||
cmd := NewWSLCommand("--status")
|
||||
assert.Contains(t, cmd.Path, "wsl")
|
||||
assert.Equal(t, []string{"--status"}, cmd.Args[1:])
|
||||
assert.Contains(t, cmd.Env, "WSL_UTF8=1")
|
||||
}
|
||||
|
Reference in New Issue
Block a user