Skip to content

Commit

Permalink
Improve support on Windows (#4)
Browse files Browse the repository at this point in the history
  • Loading branch information
tomekjarosik authored Oct 31, 2024
1 parent 9532e62 commit 43f7cfc
Show file tree
Hide file tree
Showing 8 changed files with 154 additions and 72 deletions.
3 changes: 1 addition & 2 deletions cmd/geranos/cmd/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import (
"log"
"os"
"strings"
"syscall"
)

// Inspired by https://github.com/google/go-containerregistry/blob/main/cmd/crane/cmd/auth.go
Expand Down Expand Up @@ -58,7 +57,7 @@ type loginOptions struct {

func login(opts loginOptions) error {
if opts.passwordStdin {
bytePassword, err := term.ReadPassword(syscall.Stdin)
bytePassword, err := term.ReadPassword(int(os.Stdin.Fd()))
if err != nil {
return err
}
Expand Down
8 changes: 7 additions & 1 deletion cmd/geranos/cmd/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import (
"fmt"
"github.com/macvmio/geranos/pkg/appconfig"
"github.com/spf13/viper"
"os"
"path"
)

var flagConfigFile string
Expand All @@ -15,7 +17,11 @@ func initConfig() error {
if flagConfigFile != "" {
viper.SetConfigFile(flagConfigFile)
} else {
viper.AddConfigPath("$HOME/.geranos")
home, err := os.UserHomeDir()
if err != nil {
return fmt.Errorf("could not determine home directory: %w", err)
}
viper.AddConfigPath(path.Join(home, ".geranos"))
viper.AddConfigPath(".")
viper.SetConfigName("config")
viper.SetConfigType("yaml")
Expand Down
60 changes: 47 additions & 13 deletions pkg/duplicator/file_windows.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package duplicator

import (
"errors"
"fmt"
"io"
"os"
"unsafe"

Expand All @@ -9,8 +12,25 @@ import (

const fsctlDuplicateExtentsToFile = 0x00094CF4

// duplicateExtentsToFile is a wrapper around the FSCTL_DUPLICATE_EXTENTS_TO_FILE Windows API.
// It clones the data blocks from the source file handle to the destination file handle.
func CloneFileFallback(srcFile, dstFile string) error {
fmt.Printf("CloneFileFallback: %v -> %v\n", srcFile, dstFile)
src, err := os.Open(srcFile)
if err != nil {
return err
}
defer src.Close()

dst, err := os.Create(dstFile)
if err != nil {
return err
}
defer dst.Close()

_, err = io.Copy(dst, src)
return err
}

// duplicateExtentsToFile clones data blocks from the source file handle to the destination file handle.
func duplicateExtentsToFile(dst, src windows.Handle, srcLength int64) error {
type DuplicateExtentsData struct {
FileHandle windows.Handle
Expand All @@ -24,22 +44,25 @@ func duplicateExtentsToFile(dst, src windows.Handle, srcLength int64) error {
TargetFileOffset: 0,
ByteCount: srcLength,
}
return windows.DeviceIoControl(
dst,
fsctlDuplicateExtentsToFile,
(*byte)(unsafe.Pointer(&data)),
uint32(unsafe.Sizeof(data)),
nil,
0,
nil,
nil,
return os.NewSyscallError("DeviceIoControl",
windows.DeviceIoControl(
dst,
fsctlDuplicateExtentsToFile,
(*byte)(unsafe.Pointer(&data)),
uint32(unsafe.Sizeof(data)),
nil,
0,
nil,
nil,
),
)
}

// CloneFile efficiently clones a file from srcFile to dstFile on Windows.
func CloneFile(srcFile, dstFile string) error {
srcHandle, err := windows.CreateFile(windows.StringToUTF16Ptr(srcFile),
windows.GENERIC_READ, 0, nil, windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
windows.GENERIC_READ, windows.FILE_SHARE_READ, nil,
windows.OPEN_EXISTING, windows.FILE_ATTRIBUTE_NORMAL, 0)
if err != nil {
return os.NewSyscallError("CreateFile src", err)
}
Expand All @@ -58,5 +81,16 @@ func CloneFile(srcFile, dstFile string) error {
}
srcFileSize := srcFileInfo.Size()

return duplicateExtentsToFile(dstHandle, srcHandle, srcFileSize)
// Attempt to clone using duplicate extents
err = duplicateExtentsToFile(dstHandle, srcHandle, srcFileSize)
if err != nil {
windows.Close(srcHandle)
windows.Close(dstHandle)
if errors.Is(err, windows.ERROR_ACCESS_DENIED) || errors.Is(err, windows.ERROR_NOT_SUPPORTED) {
// Fallback to traditional file copy if access is denied or operation is not supported
return CloneFileFallback(srcFile, dstFile)
}
return err
}
return nil
}
6 changes: 5 additions & 1 deletion pkg/layout/disk_usage.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,11 @@ func DirectoryDiskUsage(path string) (string, error) {
// Parse PowerShell output to get the size in a similar format to du -sh
lines := strings.Split(result, "\n")
lastLine := strings.TrimSpace(lines[len(lines)-1])
sizeInBytes := strings.Fields(lastLine)[0]
fields := strings.Fields(lastLine)
sizeInBytes := "parsing error"
if len(fields) > 0 {
sizeInBytes = fields[0]
}
size, err := formatBytesToHumanReadable(sizeInBytes)
if err != nil {
return "", err
Expand Down
19 changes: 15 additions & 4 deletions pkg/layout/layout.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"strings"
)

const OSWindows = "windows"

type Mapper struct {
rootDir string
sketcher *sketch.Sketcher
Expand All @@ -40,12 +42,21 @@ func NewMapper(rootDir string, opts ...dirimage.Option) *Mapper {

func (lm *Mapper) refToDir(ref name.Reference) string {
refStr := ref.String()
if runtime.GOOS == "windows" {
if runtime.GOOS == OSWindows {
refStr = strings.ReplaceAll(refStr, ":", "@")
}
return filepath.Join(lm.rootDir, refStr)
}

func (lm *Mapper) dirToRef(dir string) (name.Reference, error) {
processedPath := strings.TrimPrefix(filepath.Clean(dir), lm.rootDir)
processedPath = filepath.ToSlash(strings.Trim(processedPath, "/\\"))
if runtime.GOOS == OSWindows {
processedPath = strings.Replace(processedPath, "@", ":", -1)
}
return name.ParseReference(processedPath, name.StrictValidation)
}

func (lm *Mapper) WriteIfNotPresent(ctx context.Context, img v1.Image, ref name.Reference) error {
originalDigest, err := img.Digest()
if err != nil {
Expand Down Expand Up @@ -207,10 +218,10 @@ func (lm *Mapper) List() ([]Properties, error) {
if d == nil || !d.IsDir() {
return nil
}
processedPath := strings.TrimPrefix(path, lm.rootDir)
processedPath = strings.Trim(processedPath, "/")
ref, err := name.ParseReference(processedPath, name.StrictValidation)

ref, err := lm.dirToRef(path)
if err != nil {
// fmt.Printf("skipping error %v\n", err)
return nil
}
dirSize, err := directorySize(path)
Expand Down
Loading

0 comments on commit 43f7cfc

Please sign in to comment.