aboutsummaryrefslogtreecommitdiffhomepage
path: root/internal/sys/hsu.go
blob: 84bb07310487dc6b39f688cc128c0c78075c7bfa (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
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
package sys

import (
	"errors"
	"fmt"
	"os"
	"os/exec"
	"strconv"
	"sync"

	"hakurei.app/container"
	"hakurei.app/hst"
	"hakurei.app/internal"
)

// Hsu caches responses from cmd/hsu.
type Hsu struct {
	uidOnce sync.Once
	uidCopy map[int]struct {
		uid int
		err error
	}
	uidMu sync.RWMutex
}

var ErrHsuAccess = errors.New("current user is not in the hsurc file")

func (h *Hsu) Uid(identity int) (int, error) {
	h.uidOnce.Do(func() {
		h.uidCopy = make(map[int]struct {
			uid int
			err error
		})
	})

	{
		h.uidMu.RLock()
		u, ok := h.uidCopy[identity]
		h.uidMu.RUnlock()
		if ok {
			return u.uid, u.err
		}
	}

	h.uidMu.Lock()
	defer h.uidMu.Unlock()

	u := struct {
		uid int
		err error
	}{}
	defer func() { h.uidCopy[identity] = u }()

	u.uid = -1
	hsuPath := internal.MustHsuPath()

	cmd := exec.Command(hsuPath)
	cmd.Path = hsuPath
	cmd.Stderr = os.Stderr // pass through fatal messages
	cmd.Env = []string{"HAKUREI_APP_ID=" + strconv.Itoa(identity)}
	cmd.Dir = container.FHSRoot
	var (
		p         []byte
		exitError *exec.ExitError
	)

	const step = "obtain uid from hsu"
	if p, u.err = cmd.Output(); u.err == nil {
		u.uid, u.err = strconv.Atoi(string(p))
		if u.err != nil {
			u.err = &hst.AppError{Step: step, Err: u.err, Msg: "invalid uid string from hsu"}
		}
	} else if errors.As(u.err, &exitError) && exitError != nil && exitError.ExitCode() == 1 {
		// hsu prints an error message in this case
		u.err = &hst.AppError{Step: step, Err: ErrHsuAccess}
	} else if os.IsNotExist(u.err) {
		u.err = &hst.AppError{Step: step, Err: os.ErrNotExist,
			Msg: fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
	}
	return u.uid, u.err
}