github.com/ActiveState/cli@v0.0.0-20240508170324-6801f60cd051/cmd/state-installer/installer_windows.go (about)

     1  package main
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  	"time"
     9  
    10  	"github.com/ActiveState/cli/internal/errs"
    11  	"github.com/ActiveState/cli/internal/fileutils"
    12  	"github.com/ActiveState/cli/internal/installation"
    13  	"github.com/ActiveState/cli/internal/logging"
    14  	"github.com/ActiveState/cli/internal/subshell"
    15  	"github.com/ActiveState/cli/internal/subshell/sscommon"
    16  )
    17  
    18  func InstallSystemFiles(_, _, _ string) error {
    19  	return nil
    20  }
    21  
    22  // PrepareBinTargets will move aside any targets in the bin dir that we would otherwise overwrite.
    23  // This guards us from file in use errors as well as false positives by security software
    24  func (i *Installer) PrepareBinTargets() error {
    25  	sourceBinPath := filepath.Join(i.payloadPath, "bin")
    26  	targetBinPath := filepath.Join(i.path, "bin")
    27  
    28  	// Clean up exectuables from potentially corrupted installed
    29  	err := removeOldExecutables(i.path)
    30  	if err != nil {
    31  		return errs.Wrap(err, "Could not remove executables at: %s", i.path)
    32  	}
    33  
    34  	// Clean up exectuables from old install
    35  	err = removeOldExecutables(targetBinPath)
    36  	if err != nil {
    37  		return errs.Wrap(err, "Could not remove executables at: %s", i.path)
    38  	}
    39  
    40  	// Move aside conflicting executables in target
    41  	if fileutils.DirExists(sourceBinPath) {
    42  		files, err := os.ReadDir(sourceBinPath)
    43  		if err != nil {
    44  			return errs.Wrap(err, "Could not read target dir")
    45  		}
    46  
    47  		for _, file := range files {
    48  			if file.IsDir() {
    49  				continue
    50  			}
    51  
    52  			// Move executables aside
    53  			targetFile := filepath.Join(targetBinPath, file.Name())
    54  			if fileutils.TargetExists(targetFile) {
    55  				logging.Debug("Moving aside conflicting file: %s", targetFile)
    56  				renamedFile := filepath.Join(targetBinPath, fmt.Sprintf("%s-%d.old", file.Name(), time.Now().Unix()))
    57  				if err := os.Rename(targetFile, renamedFile); err != nil {
    58  					return errs.Wrap(err, "Could not move executable aside prior to install: %s to %s", targetFile, renamedFile)
    59  				}
    60  				// Make an attempt to remove the file. This has a decent chance of failing if we're updating.
    61  				// That's just a limitation on Windows and worse case scenario we'll clean it up the next update attempt.
    62  				os.Remove(renamedFile)
    63  			}
    64  		}
    65  	}
    66  
    67  	return nil
    68  }
    69  
    70  func (i *Installer) sanitizeInstallPath() error {
    71  	if !fileutils.DirExists(i.path) {
    72  		return nil
    73  	}
    74  
    75  	files, err := os.ReadDir(i.path)
    76  	if err != nil {
    77  		return errs.Wrap(err, "Could not installation directory: %s", i.path)
    78  	}
    79  
    80  	for _, file := range files {
    81  		fname := strings.ToLower(file.Name())
    82  		targetFile := filepath.Join(i.path, file.Name())
    83  		if isStateExecutable(fname) {
    84  			renamedFile := filepath.Join(i.path, fmt.Sprintf("%s-%d.old", fname, time.Now().Unix()))
    85  			if err := os.Rename(targetFile, renamedFile); err != nil {
    86  				return errs.Wrap(err, "Could not rename corrupted executable: %s to %s", targetFile, renamedFile)
    87  			}
    88  			// This will likely fail but we try anyways
    89  			os.Remove(renamedFile)
    90  		}
    91  	}
    92  
    93  	installContext, err := installation.GetContext()
    94  	if err != nil {
    95  		return errs.Wrap(err, "Could not get initial installation context")
    96  	}
    97  
    98  	// Since we are repairing a corrupted install we need to also remove the old
    99  	// PATH entry. The new PATH entry will be added later in the install/update process.
   100  	// This is only an issue on Windows as on other platforms we can simply rewrite
   101  	// the PATH entry.
   102  	s := subshell.New(i.cfg)
   103  	if err := s.CleanUserEnv(i.cfg, sscommon.InstallID, !installContext.InstalledAsAdmin); err != nil {
   104  		return errs.Wrap(err, "Failed to State Tool installation PATH")
   105  	}
   106  
   107  	return nil
   108  }
   109  
   110  func removeOldExecutables(dir string) error {
   111  	if !fileutils.TargetExists(dir) {
   112  		return nil
   113  	}
   114  
   115  	files, err := os.ReadDir(dir)
   116  	if err != nil {
   117  		return errs.Wrap(err, "Could not read installer dir")
   118  	}
   119  
   120  	for _, file := range files {
   121  		if file.IsDir() {
   122  			continue
   123  		}
   124  
   125  		if strings.HasSuffix(file.Name(), ".old") {
   126  			logging.Debug("Deleting old file: %s", file.Name())
   127  			oldFile := filepath.Join(dir, file.Name())
   128  			if err := os.Remove(oldFile); err != nil {
   129  				logging.Debug("Failed to remove old executable: %s. Error: %s", oldFile, errs.JoinMessage(err))
   130  			}
   131  		}
   132  	}
   133  
   134  	return nil
   135  }