github.com/release-engineering/exodus-rsync@v1.11.2/internal/rsync/path.go (about)

     1  package rsync
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"os"
     7  	exec "os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/release-engineering/exodus-rsync/internal/log"
    12  )
    13  
    14  // ErrMissingRsync for use when rsync command cannot be found.
    15  var ErrMissingRsync = errors.New("an 'rsync' command is required but could not be found")
    16  
    17  // Returns path to current exodus-rsync executable.
    18  func lookupSelf() string {
    19  	path, err := exec.LookPath(os.Args[0])
    20  	maybePanic(err)
    21  	return path
    22  }
    23  
    24  func resolveLinks(path string) string {
    25  	resolved, err := filepath.EvalSymlinks(path)
    26  	maybePanic(err)
    27  	return resolved
    28  }
    29  
    30  func lookupAnyRsync() string {
    31  	rsync, err := exec.LookPath("rsync")
    32  	maybePanic(err)
    33  	return rsync
    34  }
    35  
    36  func panic2err(err *error) {
    37  	if panicked := recover(); panicked != nil {
    38  		pErr, haveErr := panicked.(error)
    39  		if haveErr {
    40  			// panicked with error, return it
    41  			*err = pErr
    42  		} else {
    43  			// panicked with something else, re-panic
    44  			panic(panicked)
    45  		}
    46  	}
    47  }
    48  
    49  func maybePanic(err error) {
    50  	if err != nil {
    51  		panic(err)
    52  	}
    53  }
    54  
    55  func pathWithoutDir(path string, remove string) string {
    56  	out := path
    57  	out = strings.ReplaceAll(out, ":"+remove+":", "")
    58  	out = strings.TrimPrefix(out, remove+":")
    59  	out = strings.TrimSuffix(out, ":"+remove)
    60  	return out
    61  }
    62  
    63  func lookupTrueRsync(ctx context.Context) (rsync string, outerr error) {
    64  	defer panic2err(&outerr)
    65  
    66  	logger := log.FromContext(ctx)
    67  
    68  	self := lookupSelf()
    69  	rsync = lookupAnyRsync()
    70  
    71  	logger.F("self", self, "rsync", rsync).Debug("Looked up paths")
    72  
    73  	resolvedSelf := resolveLinks(self)
    74  	resolvedRsync := resolveLinks(rsync)
    75  
    76  	logger.F("self", resolvedSelf, "rsync", resolvedRsync).Debug("Resolved paths")
    77  
    78  	if resolvedSelf == resolvedRsync {
    79  		// Since we found ourselves, adjust PATH and try one more time.
    80  		oldPath := os.Getenv("PATH")
    81  		defer func() {
    82  			maybePanic(os.Setenv("PATH", oldPath))
    83  		}()
    84  
    85  		maybePanic(os.Setenv("PATH", pathWithoutDir(oldPath, filepath.Dir(self))))
    86  
    87  		rsync = lookupAnyRsync()
    88  
    89  		// Ensure we didn't find ourselves on the adjusted PATH.
    90  		if resolvedSelf == resolveLinks(rsync) {
    91  			logger.F("rsync", rsync, "PATH", os.Getenv("PATH")).Error(
    92  				"Cannot find 'rsync' command")
    93  			outerr = ErrMissingRsync
    94  		} else {
    95  			logger.F("rsync", rsync, "PATH", os.Getenv("PATH")).Debug(
    96  				"Resolved with adjusted PATH")
    97  		}
    98  	}
    99  
   100  	return
   101  }