package targets import ( "errors" "fmt" "os" "os/exec" "path/filepath" "strings" ) func CreateJunction(linkPath, targetPath string) error { if err := os.MkdirAll(filepath.Dir(linkPath), 0o755); err != nil { return err } cmd := exec.Command("cmd.exe", "/c", "mklink", "/J", linkPath, targetPath) out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("create junction failed: %w: %s", err, strings.TrimSpace(string(out))) } return nil } func ReadJunctionTarget(linkPath string) (string, error) { if _, err := os.Lstat(linkPath); err != nil { return "", err } target, err := os.Readlink(linkPath) if err == nil && target != "" { return cleanWindowsLinkTarget(target), nil } evaluated, evalErr := filepath.EvalSymlinks(linkPath) if evalErr != nil { return "", evalErr } if samePath(evaluated, linkPath) { return "", errors.New("path is not a junction") } return evaluated, nil } func RemoveJunction(linkPath string) error { if err := os.Remove(linkPath); err == nil || errors.Is(err, os.ErrNotExist) { return nil } cmd := exec.Command("cmd.exe", "/c", "rmdir", linkPath) out, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("remove junction failed: %w: %s", err, strings.TrimSpace(string(out))) } return nil } func cleanWindowsLinkTarget(target string) string { target = strings.TrimPrefix(target, `\??\`) return filepath.Clean(target) } func samePath(left, right string) bool { leftAbs, leftErr := filepath.Abs(cleanWindowsLinkTarget(left)) rightAbs, rightErr := filepath.Abs(cleanWindowsLinkTarget(right)) if leftErr == nil { left = leftAbs } if rightErr == nil { right = rightAbs } return strings.EqualFold(filepath.Clean(left), filepath.Clean(right)) }