cmd/tailscaled: handle tailscaled symlink on macOS

When Tailscale is installed via Homebrew, `/usr/local/bin/tailscaled`
is a symlink to the actual binary.

Now when `tailscaled install-system-daemon` runs, it will not attempt
to overwrite that symlink if it already points to the tailscaled binary.
However, if executed binary and the link target differ, the path will
he overwritten - this can happen when a user decides to replace
Homebrew-installed tailscaled with a one compiled from source code.

Fixes #5353

Signed-off-by: Anton Tolchanov <anton@tailscale.com>
pull/5093/head
Anton Tolchanov 2 years ago committed by Anton Tolchanov
parent 51d488673a
commit c070d39287

@ -11,6 +11,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"io/fs"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
@ -83,6 +84,13 @@ func uninstallSystemDaemonDarwin(args []string) (ret error) {
ret = err ret = err
} }
} }
// Do not delete targetBin if it's a symlink, which happens if it was installed via
// Homebrew.
if isSymlink(targetBin) {
return ret
}
if err := os.Remove(targetBin); err != nil { if err := os.Remove(targetBin); err != nil {
if os.IsNotExist(err) { if os.IsNotExist(err) {
err = nil err = nil
@ -107,26 +115,56 @@ func installSystemDaemonDarwin(args []string) (err error) {
// Best effort: // Best effort:
uninstallSystemDaemonDarwin(nil) uninstallSystemDaemonDarwin(nil)
// Copy ourselves to /usr/local/bin/tailscaled.
if err := os.MkdirAll(filepath.Dir(targetBin), 0755); err != nil {
return err
}
exe, err := os.Executable() exe, err := os.Executable()
if err != nil { if err != nil {
return fmt.Errorf("failed to find our own executable path: %w", err) return fmt.Errorf("failed to find our own executable path: %w", err)
} }
tmpBin := targetBin + ".tmp"
same, err := sameFile(exe, targetBin)
if err != nil {
return err
}
// Do not overwrite targetBin with the binary file if it it's already
// pointing to it. This is primarily to handle Homebrew that writes
// /usr/local/bin/tailscaled is a symlink to the actual binary.
if !same {
if err := copyBinary(exe, targetBin); err != nil {
return err
}
}
if err := os.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil {
return err
}
if out, err := exec.Command("launchctl", "load", sysPlist).CombinedOutput(); err != nil {
return fmt.Errorf("error running launchctl load %s: %v, %s", sysPlist, err, out)
}
if out, err := exec.Command("launchctl", "start", service).CombinedOutput(); err != nil {
return fmt.Errorf("error running launchctl start %s: %v, %s", service, err, out)
}
return nil
}
// copyBinary copies binary file `src` into `dst`.
func copyBinary(src, dst string) error {
if err := os.MkdirAll(filepath.Dir(dst), 0755); err != nil {
return err
}
tmpBin := dst + ".tmp"
f, err := os.Create(tmpBin) f, err := os.Create(tmpBin)
if err != nil { if err != nil {
return err return err
} }
self, err := os.Open(exe) srcf, err := os.Open(src)
if err != nil { if err != nil {
f.Close() f.Close()
return err return err
} }
_, err = io.Copy(f, self) _, err = io.Copy(f, srcf)
self.Close() srcf.Close()
if err != nil { if err != nil {
f.Close() f.Close()
return err return err
@ -137,21 +175,27 @@ func installSystemDaemonDarwin(args []string) (err error) {
if err := os.Chmod(tmpBin, 0755); err != nil { if err := os.Chmod(tmpBin, 0755); err != nil {
return err return err
} }
if err := os.Rename(tmpBin, targetBin); err != nil { if err := os.Rename(tmpBin, dst); err != nil {
return err return err
} }
if err := os.WriteFile(sysPlist, []byte(darwinLaunchdPlist), 0700); err != nil { return nil
return err
} }
if out, err := exec.Command("launchctl", "load", sysPlist).CombinedOutput(); err != nil { func isSymlink(path string) bool {
return fmt.Errorf("error running launchctl load %s: %v, %s", sysPlist, err, out) fi, err := os.Lstat(path)
return err == nil && (fi.Mode()&os.ModeSymlink == os.ModeSymlink)
} }
if out, err := exec.Command("launchctl", "start", service).CombinedOutput(); err != nil { // sameFile returns true if both file paths exist and resolve to the same file.
return fmt.Errorf("error running launchctl start %s: %v, %s", service, err, out) func sameFile(path1, path2 string) (bool, error) {
dst1, err := filepath.EvalSymlinks(path1)
if err != nil && !errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("EvalSymlinks(%s): %w", path1, err)
} }
dst2, err := filepath.EvalSymlinks(path2)
return nil if err != nil && !errors.Is(err, fs.ErrNotExist) {
return false, fmt.Errorf("EvalSymlinks(%s): %w", path2, err)
}
return dst1 == dst2, nil
} }

Loading…
Cancel
Save