summaryrefslogtreecommitdiff
path: root/internal/resource/link/symlink.go
blob: c7cb36136eb2881fca5afaae01b6db51855e23f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package link

import (
	"fmt"
	"log"
	"os"
)

// ensureSymlink ensures l.path is a symlink pointing at l.target.
//
// Idempotency and clobber policy:
//   - already points at the target: nothing to do.
//   - points elsewhere: the link is removed and recreated.
//   - a real file/dir is in the way: it is renamed to "<path>.old" before the
//     link is created (matching the Rexfile's rename-existing behavior).
func ensureSymlink(l *Link) error {
	log.Printf("processing symlink: %s -> %s", l.path, l.target)

	if l.target == "" {
		return fmt.Errorf("symlink %s has no target", l.path)
	}

	info, err := os.Lstat(l.path)
	switch {
	case err == nil && info.Mode()&os.ModeSymlink != 0:
		current, err := os.Readlink(l.path)
		if err != nil {
			return fmt.Errorf("failed to read symlink %s: %w", l.path, err)
		}
		if current == l.target {
			log.Printf("symlink %s already points at %s", l.path, l.target)
			return nil
		}
		log.Printf("repointing symlink %s from %s to %s", l.path, current, l.target)
		if err := os.Remove(l.path); err != nil {
			return fmt.Errorf("failed to remove stale symlink %s: %w", l.path, err)
		}

	case err == nil:
		old := l.path + ".old"
		log.Printf("%s is a real file/dir, renaming to %s", l.path, old)
		if err := os.Rename(l.path, old); err != nil {
			return fmt.Errorf("failed to move existing %s aside: %w", l.path, err)
		}

	case !os.IsNotExist(err):
		return fmt.Errorf("failed to stat %s: %w", l.path, err)
	}

	if err := os.Symlink(l.target, l.path); err != nil {
		return fmt.Errorf("failed to create symlink %s -> %s: %w", l.path, l.target, err)
	}

	log.Printf("created symlink %s -> %s", l.path, l.target)
	return nil
}