github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/internal/fileutils/fileutils_win.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  package fileutils
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"os"
    10  	"path/filepath"
    11  	"strings"
    12  	"syscall"
    13  	"unsafe"
    14  
    15  	"github.com/ActiveState/cli/internal/errs"
    16  	"github.com/ActiveState/cli/internal/logging"
    17  	"github.com/ActiveState/cli/internal/multilog"
    18  	"golang.org/x/sys/windows"
    19  )
    20  
    21  const LineEnd = "\r\n"
    22  
    23  // IsExecutable determines if the file at the given path has any execute permissions.
    24  // This function does not care whether the current user can has enough privilege to
    25  // execute the file.
    26  func IsExecutable(path string) bool {
    27  	ext := strings.ToLower(filepath.Ext(path))
    28  	if ext == ".exe" {
    29  		return true
    30  	}
    31  
    32  	pathExts := strings.Split(os.Getenv("PATHEXT"), ";")
    33  	for _, pe := range pathExts {
    34  		// pathext entries have `.` and are capitalize
    35  		if strings.EqualFold(ext, pe) {
    36  			return true
    37  		}
    38  	}
    39  	return false
    40  }
    41  
    42  // IsWritable returns true if the given path is writable
    43  func IsWritable(path string) bool {
    44  	for !TargetExists(path) && path != "" {
    45  		path = filepath.Dir(path)
    46  	}
    47  
    48  	info, err := os.Stat(path)
    49  	if err != nil {
    50  		logging.Debug("os.Stat %s failed", path)
    51  		return false
    52  	}
    53  
    54  	// Check if the user bit is enabled in file permission
    55  	if info.Mode().Perm()&(1<<(uint(7))) == 0 {
    56  		logging.Debug("Write permission bit is not set on this file for user")
    57  		return false
    58  	}
    59  
    60  	return true
    61  }
    62  
    63  // ResolveUniquePath gets the absolute location of the provided path
    64  // with the best effort attempt to produce the same result for all possible paths to the
    65  // given target.
    66  func ResolveUniquePath(path string) (string, error) {
    67  	evalPath, err := ResolvePath(filepath.Clean(path))
    68  	if err != nil {
    69  		return "", errs.Wrap(err, "cannot resolve path")
    70  	}
    71  
    72  	longPath, err := GetLongPathName(evalPath)
    73  	if err != nil {
    74  		// GetLongPathName can fail on unsupported file-systems or if evalPath is not a physical path.
    75  		// => just log the error (unless err due to file not existing) and resume with resolved path
    76  		if !errors.Is(err, os.ErrNotExist) && !errors.Is(err, os.ErrNotExist) {
    77  			multilog.Error("could not resolve long version of %s: %v", evalPath, err)
    78  		}
    79  		return filepath.Clean(evalPath), nil
    80  	}
    81  
    82  	return filepath.Clean(longPath), nil
    83  }
    84  
    85  func HideFile(path string) error {
    86  	k32 := syscall.NewLazyDLL("kernel32.dll")
    87  	setFileAttrs := k32.NewProc("SetFileAttributesW")
    88  
    89  	utfPath, err := syscall.UTF16PtrFromString(path)
    90  	if err != nil {
    91  		return fmt.Errorf("Hide file (UTF16 conversion): %w", err)
    92  	}
    93  	uipPath := uintptr(unsafe.Pointer(utfPath))
    94  	r1, _, err := setFileAttrs.Call(uipPath, 2)
    95  	if r1 == 0 && !errors.Is(err, windows.ERROR_SUCCESS) {
    96  		return fmt.Errorf("Hide file (set attributes): %w", err)
    97  	}
    98  
    99  	return nil
   100  }