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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
|
package sys
import (
"errors"
"fmt"
"io/fs"
"log"
"os"
"os/exec"
"os/user"
"path/filepath"
"strconv"
"sync"
"syscall"
"hakurei.app/container"
"hakurei.app/hst"
"hakurei.app/internal"
"hakurei.app/internal/hlog"
)
// Std implements System using the standard library.
type Std struct {
paths hst.Paths
pathsOnce sync.Once
uidOnce sync.Once
uidCopy map[int]struct {
uid int
err error
}
uidMu sync.RWMutex
}
func (s *Std) Getuid() int { return os.Getuid() }
func (s *Std) Getgid() int { return os.Getgid() }
func (s *Std) LookupEnv(key string) (string, bool) { return os.LookupEnv(key) }
func (s *Std) TempDir() string { return os.TempDir() }
func (s *Std) LookPath(file string) (string, error) { return exec.LookPath(file) }
func (s *Std) MustExecutable() string { return container.MustExecutable() }
func (s *Std) LookupGroup(name string) (*user.Group, error) { return user.LookupGroup(name) }
func (s *Std) ReadDir(name string) ([]os.DirEntry, error) { return os.ReadDir(name) }
func (s *Std) Stat(name string) (fs.FileInfo, error) { return os.Stat(name) }
func (s *Std) Open(name string) (fs.File, error) { return os.Open(name) }
func (s *Std) EvalSymlinks(path string) (string, error) { return filepath.EvalSymlinks(path) }
func (s *Std) Exit(code int) { internal.Exit(code) }
func (s *Std) Println(v ...any) { hlog.Verbose(v...) }
func (s *Std) Printf(format string, v ...any) { hlog.Verbosef(format, v...) }
const xdgRuntimeDir = "XDG_RUNTIME_DIR"
func (s *Std) Paths() hst.Paths {
s.pathsOnce.Do(func() {
if userid, err := GetUserID(s); err != nil {
// TODO(ophestra): this duplicates code in cmd/hakurei/command.go, keep this up to date until removal
if m, ok := container.GetErrorMessage(err); ok {
if m != "\x00" {
log.Print(m)
}
} else {
log.Println("cannot obtain user id from hsu:", err)
}
hlog.BeforeExit()
s.Exit(1)
} else {
CopyPaths(s, &s.paths, userid)
}
})
return s.paths
}
// this is a temporary placeholder until this package is removed
type wrappedError struct {
Err error
Msg string
}
func (e *wrappedError) Error() string { return e.Err.Error() }
func (e *wrappedError) Unwrap() error { return e.Err }
func (e *wrappedError) Message() string { return e.Msg }
func (s *Std) Uid(identity int) (int, error) {
s.uidOnce.Do(func() {
s.uidCopy = make(map[int]struct {
uid int
err error
})
})
{
s.uidMu.RLock()
u, ok := s.uidCopy[identity]
s.uidMu.RUnlock()
if ok {
return u.uid, u.err
}
}
s.uidMu.Lock()
defer s.uidMu.Unlock()
u := struct {
uid int
err error
}{}
defer func() { s.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
)
if p, u.err = cmd.Output(); u.err == nil {
u.uid, u.err = strconv.Atoi(string(p))
if u.err != nil {
u.err = &wrappedError{u.err, "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 = &wrappedError{syscall.EACCES, "\x00"} // this drops the message, handled in cmd/hakurei/command.go
} else if os.IsNotExist(u.err) {
u.err = &wrappedError{os.ErrNotExist, fmt.Sprintf("the setuid helper is missing: %s", hsuPath)}
}
return u.uid, u.err
}
|