返回 DeepSeek-Reasonix
relaunch.go
根目录 / internal / installlayout / relaunch.go
1 package installlayout
2
3 import (
4 "fmt"
5 "os"
6 "path/filepath"
7 "strings"
8 )
9
10 // StableRelaunchPath is the only legal post-update entry point for a versioned
11 // install: the install-root launcher when present, otherwise the active desktop
12 // named by current.json. It never returns a retained versions/<old>/ desktop.
13 func StableRelaunchPath(installRoot string) (string, error) {
14 installRoot, err := cleanInstallRoot(installRoot)
15 if err != nil {
16 return "", err
17 }
18 for _, name := range []string{CanonicalLauncherBinaryName(), LauncherBinaryName()} {
19 if name == "" {
20 continue
21 }
22 path := filepath.Join(installRoot, name)
23 info, err := os.Lstat(path)
24 if err != nil || !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
25 continue
26 }
27 return path, nil
28 }
29 if HasCurrent(installRoot) {
30 return ActiveDesktopPath(installRoot)
31 }
32 if path := siblingDesktopPath(installRoot); path != "" {
33 return path, nil
34 }
35 return "", fmt.Errorf("installlayout: no stable relaunch path under %s", installRoot)
36 }
37
38 // IsActiveDesktopExecutable reports whether exe is the current.json desktop.
39 // Installs without a versioned pointer (flat / source builds) return true.
40 func IsActiveDesktopExecutable(exe string) (bool, error) {
41 exe = filepath.Clean(strings.TrimSpace(exe))
42 if exe == "" {
43 return false, fmt.Errorf("installlayout: empty executable path")
44 }
45 root, err := ResolveInstallRoot(exe)
46 if err != nil {
47 return false, err
48 }
49 if !HasCurrent(root) {
50 return true, nil
51 }
52 active, err := ActiveDesktopPath(root)
53 if err != nil {
54 return false, err
55 }
56 same, err := SameRegularFile(exe, active)
57 if err != nil {
58 return false, err
59 }
60 return same, nil
61 }
62
63 // IsSupersededVersionedDesktop reports whether imagePath is a retained
64 // versions/<old>/ desktop binary for this install root.
65 func IsSupersededVersionedDesktop(installRoot, imagePath string) bool {
66 installRoot = filepath.Clean(strings.TrimSpace(installRoot))
67 imagePath = filepath.Clean(strings.TrimSpace(imagePath))
68 if installRoot == "" || imagePath == "" || !HasCurrent(installRoot) {
69 return false
70 }
71 if !strings.EqualFold(filepath.Base(imagePath), DesktopBinaryName()) {
72 return false
73 }
74 rel, err := filepath.Rel(installRoot, imagePath)
75 if err != nil || rel == "." || strings.HasPrefix(rel, "..") || filepath.IsAbs(rel) {
76 return false
77 }
78 if !strings.HasPrefix(filepath.ToSlash(rel), VersionsDirName+"/") {
79 return false
80 }
81 active, err := ActiveDesktopPath(installRoot)
82 if err != nil {
83 return false
84 }
85 if same, err := SameRegularFile(imagePath, active); err == nil && same {
86 return false
87 }
88 return true
89 }
90
91 // SameRegularFile reports whether two paths name the same regular file.
92 func SameRegularFile(a, b string) (bool, error) {
93 ai, err := os.Lstat(a)
94 if err != nil {
95 return false, err
96 }
97 bi, err := os.Lstat(b)
98 if err != nil {
99 return false, err
100 }
101 return os.SameFile(ai, bi), nil
102 }
103
104 func siblingDesktopPath(installRoot string) string {
105 path := filepath.Join(installRoot, DesktopBinaryName())
106 info, err := os.Lstat(path)
107 if err != nil || !info.Mode().IsRegular() || info.Mode()&os.ModeSymlink != 0 {
108 return ""
109 }
110 return path
111 }
112
112 lines GO