mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 19:54:35 +08:00
ioutilx: add native fallback for cygpath path conversion
Plain Windows ships no cygpath, so WindowsSubsystemPath failed there. Add a native fallback for the absolute drive-letter case, and split out WindowsSubsystemPathWithCygpath so a caller can pin the conversion to a specific ssh toolchain's own cygpath. Signed-off-by: mie313 <mie313.tanaka@gmail.com> Co-authored-by: Jan Dubois <jan.dubois@suse.com>
This commit is contained in:
1 parent
1ba38dc238
commit
54db504f87
2 files changed
+85
-5
No files matched your search
+41
-5
@@ -50,13 +50,49 @@ func FromUTF16leToString(r io.Reader) (string, error) {
|
||||
return string(out), nil
|
||||
}
|
||||
|
||||
// WindowsSubsystemPath converts a Windows path to a Cygwin/MSYS form
|
||||
// (C:\Users\jan -> /c/Users/jan) via cygpath on PATH. A caller holding a
|
||||
// specific ssh toolchain should use WindowsSubsystemPathWithCygpath, which
|
||||
// runs that toolchain's own cygpath and respects its fstab.
|
||||
func WindowsSubsystemPath(ctx context.Context, orig string) (string, error) {
|
||||
out, err := exec.CommandContext(ctx, "cygpath", filepath.ToSlash(orig)).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("failed to convert path to mingw, maybe not using Git ssh?")
|
||||
return "", err
|
||||
return WindowsSubsystemPathWithCygpath(ctx, "cygpath", orig)
|
||||
}
|
||||
|
||||
// WindowsSubsystemPathWithCygpath converts a Windows path with the given
|
||||
// cygpathExe ("cygpath" for PATH, or an absolute path to bind the
|
||||
// conversion to one Cygwin install). When cygpath is unavailable it falls
|
||||
// back to a native conversion of the drive-letter case; Universal Naming Convention
|
||||
// (UNC) and other non-drive-letter inputs return an error.
|
||||
func WindowsSubsystemPathWithCygpath(ctx context.Context, cygpathExe, orig string) (string, error) {
|
||||
out, err := exec.CommandContext(ctx, cygpathExe, filepath.ToSlash(orig)).CombinedOutput()
|
||||
if err == nil {
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
|
||||
logrus.WithError(err).Debugf("cygpath unavailable for %#q (output: %#q), attempting native conversion", orig, strings.TrimSpace(string(out)))
|
||||
|
||||
return windowsSubsystemPathWithoutCygpath(orig)
|
||||
}
|
||||
|
||||
func windowsSubsystemPathWithoutCygpath(orig string) (string, error) {
|
||||
// Only an absolute drive-letter path ("C:\foo") has a well-defined
|
||||
// Cygwin form here. A drive-relative path ("C:foo") would become
|
||||
// an unrelated absolute path, so reject it.
|
||||
if !filepath.IsAbs(orig) {
|
||||
return "", fmt.Errorf("cannot convert %#q to a Cygwin-style path: input is not an absolute drive-letter path", orig)
|
||||
}
|
||||
|
||||
// UNC path ("\\server\share\foo") is rejected here.
|
||||
if vol := filepath.VolumeName(orig); len(vol) == 2 && vol[1] == ':' {
|
||||
// orig[2:] starts with a separator for an absolute drive path
|
||||
// (C:\foo, C:/foo); strip it so the result stays canonical.
|
||||
tail := strings.TrimPrefix(filepath.ToSlash(orig[2:]), "/")
|
||||
converted := "/" + strings.ToLower(vol[:1]) + "/" + tail
|
||||
logrus.Debugf("native cygpath fallback: %#q -> %#q", orig, converted)
|
||||
return converted, nil
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("cannot convert %#q to a Cygwin-style path: cygpath is unavailable and input is not an absolute drive-letter path", orig)
|
||||
}
|
||||
|
||||
func WindowsSubsystemPathForLinux(ctx context.Context, orig, distro string) (string, error) {
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ioutilx
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
// TestWindowsSubsystemPathWithoutCygpath exercises the native
|
||||
// drive-letter fallback path: when the cygpath binary is unreachable,
|
||||
// absolute drive-letter inputs convert in-process (e.g., "C:\foo" -> "/c/foo")
|
||||
// and inputs without a drive letter return an error.
|
||||
func TestWindowsSubsystemPathWithoutCygpath(t *testing.T) {
|
||||
t.Run("drive-letter backslash", func(t *testing.T) {
|
||||
got, err := windowsSubsystemPathWithoutCygpath(`C:\Users\USER`)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got, "/c/Users/USER")
|
||||
})
|
||||
|
||||
t.Run("drive-letter forward-slash", func(t *testing.T) {
|
||||
got, err := windowsSubsystemPathWithoutCygpath(`C:/Users/USER`)
|
||||
assert.NilError(t, err)
|
||||
assert.Equal(t, got, "/c/Users/USER")
|
||||
})
|
||||
|
||||
t.Run("non-drive-letter rejected", func(t *testing.T) {
|
||||
// UNC and POSIX inputs both lack a drive letter; on every
|
||||
// platform the fallback must refuse rather than fabricate.
|
||||
for _, input := range []string{`\\server\share\foo`, "/etc/hosts", "relative.txt"} {
|
||||
_, err := windowsSubsystemPathWithoutCygpath(input)
|
||||
assert.ErrorContains(t, err, "cannot convert", "input=%q", input)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("lowercases drive letter", func(t *testing.T) {
|
||||
got, err := windowsSubsystemPathWithoutCygpath(`d:\path`)
|
||||
assert.NilError(t, err)
|
||||
assert.Assert(t, strings.HasPrefix(got, "/d/"), "got %q", got)
|
||||
})
|
||||
}
|
||||
Reference in new issue
Block a user