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 }