mirror of
https://github.com/lima-vm/lima.git
synced 2026-10-28 11:44:35 +08:00
pkg/ioutilx: move Windows path helpers to pkg/fsutil
WindowsSubsystemPath, WindowsSubsystemPathWithCygpath, WindowsSubsystemPathForLinux, and the unexported windowsSubsystemPathWithoutCygpath fallback do not resemble io/ioutil and belong with the other filesystem helpers in pkg/fsutil, per #5283. ReadAtMaximum and the UTF16 helpers stay in pkg/ioutilx; their eventual home is still an open question in the issue. The real implementation moves into pkg/fsutil/fsutil_windows.go, which is only compiled on GOOS=windows per its filename suffix. All existing callers (cmd/limactl/shell.go, pkg/copytool, pkg/hostagent, pkg/sshutil, pkg/limayaml/defaults.go) are cross-platform files that already gate these calls behind "runtime.GOOS == \"windows\"" checks, so the symbols still need to resolve at compile time on non-Windows. pkg/fsutil/fsutil_unix.go (//go:build !windows) adds stub definitions with matching signatures that return an error; they are unreachable in practice since every call site is runtime.GOOS-gated, mirroring the existing errNotSupported pattern in pkg/guestagent/timesync/timesync_others.go. This keeps every caller's code unchanged and keeps the diff minimal while still not breaking any GOOS build. The Windows-only unit test (pkg/ioutilx/ioutilx_windows_test.go) moves alongside the code to pkg/fsutil/fsutil_windows_test.go, unchanged apart from the package name. No compatibility alias/stub was kept in pkg/ioutilx for the moved symbols: recent pkg/ moves in this repo's history (e.g. the sshutil/ioutilx cygpath work in7a5a207and54db504) rewrite call sites directly rather than leaving forwarding shims, so that convention is followed here too. Verified: go build ./... on darwin, GOOS=linux go build ./..., GOOS=windows go build ./... (all clean); go test ./pkg/ioutilx/... ./pkg/fsutil/... plus the caller packages (copytool, hostagent, sshutil, limayaml, cmd/limactl) on darwin; GOOS=windows go vet/test -c on pkg/fsutil to compile-check the Windows-only files; go vet ./... on darwin; golangci-lint run on all touched packages (0 issues). Signed-off-by: arimu1 <19286898+arimu1@users.noreply.github.com>
This commit is contained in:
1 parent
596bc53e45
commit
cf7b44a727
10 files changed
+128
-81
No files matched your search
@@ -25,8 +25,8 @@ import (
|
||||
"github.com/lima-vm/lima/v2/pkg/autostart"
|
||||
"github.com/lima-vm/lima/v2/pkg/copytool"
|
||||
"github.com/lima-vm/lima/v2/pkg/envutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/fsutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/instance"
|
||||
"github.com/lima-vm/lima/v2/pkg/ioutilx"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/limayaml"
|
||||
"github.com/lima-vm/lima/v2/pkg/localpathutil"
|
||||
@@ -784,9 +784,9 @@ func rsyncDirectory(ctx context.Context, cmd *cobra.Command, rsync copytool.Copy
|
||||
func mountDirFromWindowsDir(ctx context.Context, inst *limatype.Instance, dir string) (string, error) {
|
||||
if inst.VMType == limatype.WSL2 {
|
||||
distroName := "lima-" + inst.Name
|
||||
return ioutilx.WindowsSubsystemPathForLinux(ctx, dir, distroName)
|
||||
return fsutil.WindowsSubsystemPathForLinux(ctx, dir, distroName)
|
||||
}
|
||||
return ioutilx.WindowsSubsystemPath(ctx, dir)
|
||||
return fsutil.WindowsSubsystemPath(ctx, dir)
|
||||
}
|
||||
|
||||
func shellBashComplete(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/ioutilx"
|
||||
"github.com/lima-vm/lima/v2/pkg/fsutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/store"
|
||||
)
|
||||
@@ -124,7 +124,7 @@ func parseCopyPaths(ctx context.Context, paths []string) ([]*Path, error) {
|
||||
if runtime.GOOS == "windows" {
|
||||
if filepath.IsAbs(path) {
|
||||
var err error
|
||||
path, err = ioutilx.WindowsSubsystemPath(ctx, path)
|
||||
path, err = fsutil.WindowsSubsystemPath(ctx, path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
//go:build !windows
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
)
|
||||
|
||||
// errWindowsPathConversionNotSupported is returned by the Windows path
|
||||
// conversion stubs below. Callers only invoke them behind a
|
||||
// runtime.GOOS == "windows" check, so this is never actually returned in
|
||||
// practice; it exists so the fsutil_windows.go API still compiles here.
|
||||
var errWindowsPathConversionNotSupported = errors.New("fsutil: Windows path conversion is not supported on this platform")
|
||||
|
||||
// WindowsSubsystemPath is the non-Windows stub for the Windows-only
|
||||
// implementation in fsutil_windows.go.
|
||||
func WindowsSubsystemPath(_ context.Context, _ string) (string, error) {
|
||||
return "", errWindowsPathConversionNotSupported
|
||||
}
|
||||
|
||||
// WindowsSubsystemPathWithCygpath is the non-Windows stub for the
|
||||
// Windows-only implementation in fsutil_windows.go.
|
||||
func WindowsSubsystemPathWithCygpath(_ context.Context, _, _ string) (string, error) {
|
||||
return "", errWindowsPathConversionNotSupported
|
||||
}
|
||||
|
||||
// WindowsSubsystemPathForLinux is the non-Windows stub for the Windows-only
|
||||
// implementation in fsutil_windows.go.
|
||||
func WindowsSubsystemPathForLinux(_ context.Context, _, _ string) (string, error) {
|
||||
return "", errWindowsPathConversionNotSupported
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
)
|
||||
|
||||
// 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) {
|
||||
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 the cygpath binary is not found it falls back
|
||||
// to a native conversion of the absolute drive-letter case; a cygpath that
|
||||
// runs and fails returns its error, and non-drive-letter inputs (UNC, device,
|
||||
// extended-length) 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
|
||||
}
|
||||
if !errors.Is(err, exec.ErrNotFound) && !errors.Is(err, fs.ErrNotExist) {
|
||||
return "", fmt.Errorf("failed to run %#q on %#q: %#q: %w", cygpathExe, orig, strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
|
||||
logrus.WithError(err).Debugf("%#q not found for %#q, attempting native conversion", cygpathExe, orig)
|
||||
|
||||
return windowsSubsystemPathWithoutCygpath(orig)
|
||||
}
|
||||
|
||||
func windowsSubsystemPathWithoutCygpath(orig string) (string, error) {
|
||||
// The /c/... form this produces is the MSYS2 and Git-for-Windows
|
||||
// convention; stock Cygwin defaults to /cygdrive/c/. Only an absolute
|
||||
// drive-letter path ("C:\foo") has a well-defined 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 an MSYS-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 an MSYS-style path: input is not an absolute drive-letter path", orig)
|
||||
}
|
||||
|
||||
// WindowsSubsystemPathForLinux converts a Windows path to the WSL form of
|
||||
// the given distro (e.g. C:\Users\jan -> /mnt/c/Users/jan) via wsl.exe.
|
||||
func WindowsSubsystemPathForLinux(ctx context.Context, orig, distro string) (string, error) {
|
||||
out, err := exec.CommandContext(ctx, "wsl", "-d", distro, "--exec", "wslpath", filepath.ToSlash(orig)).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("failed to convert path to mingw, maybe wsl command is not operational?")
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
// SPDX-FileCopyrightText: Copyright The Lima Authors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package ioutilx
|
||||
package fsutil
|
||||
|
||||
import (
|
||||
"strings"
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"github.com/lima-vm/sshocker/pkg/reversesshfs"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/ioutilx"
|
||||
"github.com/lima-vm/lima/v2/pkg/fsutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/sshutil"
|
||||
)
|
||||
@@ -55,7 +55,7 @@ func (a *HostAgent) setupMount(ctx context.Context, m limatype.Mount) (*mount, e
|
||||
resolvedLocation := m.Location
|
||||
if runtime.GOOS == "windows" {
|
||||
var err error
|
||||
resolvedLocation, err = ioutilx.WindowsSubsystemPath(ctx, m.Location)
|
||||
resolvedLocation, err = fsutil.WindowsSubsystemPath(ctx, m.Location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -4,16 +4,10 @@
|
||||
package ioutilx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/text/encoding/unicode"
|
||||
"golang.org/x/text/transform"
|
||||
)
|
||||
@@ -50,63 +44,3 @@ 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) {
|
||||
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 the cygpath binary is not found it falls back
|
||||
// to a native conversion of the absolute drive-letter case; a cygpath that
|
||||
// runs and fails returns its error, and non-drive-letter inputs (UNC, device,
|
||||
// extended-length) 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
|
||||
}
|
||||
if !errors.Is(err, exec.ErrNotFound) && !errors.Is(err, fs.ErrNotExist) {
|
||||
return "", fmt.Errorf("failed to run %#q on %#q: %#q: %w", cygpathExe, orig, strings.TrimSpace(string(out)), err)
|
||||
}
|
||||
|
||||
logrus.WithError(err).Debugf("%#q not found for %#q, attempting native conversion", cygpathExe, orig)
|
||||
|
||||
return windowsSubsystemPathWithoutCygpath(orig)
|
||||
}
|
||||
|
||||
func windowsSubsystemPathWithoutCygpath(orig string) (string, error) {
|
||||
// The /c/... form this produces is the MSYS2 and Git-for-Windows
|
||||
// convention; stock Cygwin defaults to /cygdrive/c/. Only an absolute
|
||||
// drive-letter path ("C:\foo") has a well-defined 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 an MSYS-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 an MSYS-style path: input is not an absolute drive-letter path", orig)
|
||||
}
|
||||
|
||||
func WindowsSubsystemPathForLinux(ctx context.Context, orig, distro string) (string, error) {
|
||||
out, err := exec.CommandContext(ctx, "wsl", "-d", distro, "--exec", "wslpath", filepath.ToSlash(orig)).CombinedOutput()
|
||||
if err != nil {
|
||||
logrus.WithError(err).Errorf("failed to convert path to mingw, maybe wsl command is not operational?")
|
||||
return "", err
|
||||
}
|
||||
return strings.TrimSpace(string(out)), nil
|
||||
}
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
"github.com/pbnjay/memory"
|
||||
"github.com/sirupsen/logrus"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/fsutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/instance/hostname"
|
||||
"github.com/lima-vm/lima/v2/pkg/ioutilx"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
|
||||
@@ -729,7 +729,7 @@ func FillDefault(ctx context.Context, y, d, o *limatype.LimaYAML, filePath strin
|
||||
mountLocation := mount.Location
|
||||
if runtime.GOOS == "windows" {
|
||||
var err error
|
||||
mountLocation, err = ioutilx.WindowsSubsystemPath(ctx, mountLocation)
|
||||
mountLocation, err = fsutil.WindowsSubsystemPath(ctx, mountLocation)
|
||||
if err != nil {
|
||||
logrus.WithError(err).Warnf("Couldn't convert location %#q into mount target", mount.Location)
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"gotest.tools/v3/assert"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/ioutilx"
|
||||
"github.com/lima-vm/lima/v2/pkg/fsutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
|
||||
@@ -240,7 +240,7 @@ func TestFillDefault(t *testing.T) {
|
||||
expect.Mounts = slices.Clone(y.Mounts)
|
||||
expect.Mounts[0].MountPoint = ptr.Of(expect.Mounts[0].Location)
|
||||
if runtime.GOOS == "windows" {
|
||||
mountLocation, err := ioutilx.WindowsSubsystemPath(t.Context(), expect.Mounts[0].Location)
|
||||
mountLocation, err := fsutil.WindowsSubsystemPath(t.Context(), expect.Mounts[0].Location)
|
||||
if err == nil {
|
||||
expect.Mounts[0].MountPoint = ptr.Of(mountLocation)
|
||||
}
|
||||
@@ -477,7 +477,7 @@ func TestFillDefault(t *testing.T) {
|
||||
expect.Mounts = slices.Clone(d.Mounts)
|
||||
expect.Mounts[0].MountPoint = ptr.Of(expect.Mounts[0].Location)
|
||||
if runtime.GOOS == "windows" {
|
||||
mountLocation, err := ioutilx.WindowsSubsystemPath(t.Context(), expect.Mounts[0].Location)
|
||||
mountLocation, err := fsutil.WindowsSubsystemPath(t.Context(), expect.Mounts[0].Location)
|
||||
if err == nil {
|
||||
expect.Mounts[0].MountPoint = ptr.Of(mountLocation)
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ import (
|
||||
"github.com/sirupsen/logrus"
|
||||
"golang.org/x/sys/cpu"
|
||||
|
||||
"github.com/lima-vm/lima/v2/pkg/ioutilx"
|
||||
"github.com/lima-vm/lima/v2/pkg/fsutil"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/dirnames"
|
||||
"github.com/lima-vm/lima/v2/pkg/limatype/filenames"
|
||||
"github.com/lima-vm/lima/v2/pkg/lockutil"
|
||||
@@ -232,7 +232,7 @@ func PathForSSH(ctx context.Context, sshExe SSHExe, orig string) (string, error)
|
||||
return orig, nil
|
||||
}
|
||||
if cygpathExe, ok := cygpathForSSH(sshExe); ok {
|
||||
return ioutilx.WindowsSubsystemPathWithCygpath(ctx, cygpathExe, orig)
|
||||
return fsutil.WindowsSubsystemPathWithCygpath(ctx, cygpathExe, orig)
|
||||
}
|
||||
return filepath.ToSlash(orig), nil
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user