Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
lima-vm
GitHub Repository: lima-vm/lima
Path: blob/master/pkg/osutil/user.go
2606 views
1
// SPDX-FileCopyrightText: Copyright The Lima Authors
2
// SPDX-License-Identifier: Apache-2.0
3
4
package osutil
5
6
import (
7
"context"
8
"fmt"
9
"os/exec"
10
"os/user"
11
"regexp"
12
"runtime"
13
"strconv"
14
"strings"
15
"sync"
16
17
"github.com/sirupsen/logrus"
18
19
. "github.com/lima-vm/lima/v2/pkg/must"
20
"github.com/lima-vm/lima/v2/pkg/version/versionutil"
21
)
22
23
type User struct {
24
User string
25
Uid uint32
26
Group string
27
Gid uint32
28
Name string // or Comment
29
Home string
30
}
31
32
type Group struct {
33
Name string
34
Gid uint32
35
}
36
37
var (
38
users map[string]User
39
groups map[string]Group
40
)
41
42
// regexUsername matches user and group names to be valid for `useradd`.
43
// `useradd` allows names with a trailing '$', but it feels prudent to map those
44
// names to the fallback user as well, so the regex does not allow them.
45
var regexUsername = regexp.MustCompile("^[a-z_][a-z0-9_-]*$")
46
47
func LookupUser(name string) (User, error) {
48
if users == nil {
49
users = make(map[string]User)
50
}
51
if _, ok := users[name]; !ok {
52
u, err := user.Lookup(name)
53
if err != nil {
54
return User{}, err
55
}
56
g, err := user.LookupGroupId(u.Gid)
57
if err != nil {
58
return User{}, err
59
}
60
uid, err := parseUidGid(u.Uid)
61
if err != nil {
62
return User{}, err
63
}
64
gid, err := parseUidGid(u.Gid)
65
if err != nil {
66
return User{}, err
67
}
68
users[name] = User{User: u.Username, Uid: uid, Group: g.Name, Gid: gid, Name: u.Name, Home: u.HomeDir}
69
}
70
return users[name], nil
71
}
72
73
func LookupGroup(name string) (Group, error) {
74
if groups == nil {
75
groups = make(map[string]Group)
76
}
77
if _, ok := groups[name]; !ok {
78
g, err := user.LookupGroup(name)
79
if err != nil {
80
return Group{}, err
81
}
82
gid, err := parseUidGid(g.Gid)
83
if err != nil {
84
return Group{}, err
85
}
86
groups[name] = Group{Name: g.Name, Gid: gid}
87
}
88
return groups[name], nil
89
}
90
91
const (
92
fallbackUser = "lima"
93
fallbackUid = 1000
94
fallbackGid = 1000
95
)
96
97
var currentUser = Must(user.Current())
98
99
var (
100
once = new(sync.Once)
101
limaUser *user.User
102
warnings []string
103
)
104
105
func LimaUser(ctx context.Context, limaVersion string, warn bool) *user.User {
106
once.Do(func() {
107
limaUser = currentUser
108
if !regexUsername.MatchString(limaUser.Username) {
109
warning := fmt.Sprintf("local username %q is not a valid Linux username (must match %q); using %q instead",
110
limaUser.Username, regexUsername.String(), fallbackUser)
111
warnings = append(warnings, warning)
112
limaUser.Username = fallbackUser
113
}
114
limaUser.HomeDir = "/home/{{.User}}.linux"
115
if runtime.GOOS == "windows" {
116
idu, err := call(ctx, []string{"id", "-u"})
117
if err != nil {
118
logrus.Debug(err)
119
}
120
uid, err := parseUidGid(idu)
121
if err != nil {
122
uid = fallbackUid
123
}
124
if _, err := parseUidGid(limaUser.Uid); err != nil {
125
warning := fmt.Sprintf("local uid %q is not a valid Linux uid (must be integer); using %d uid instead",
126
limaUser.Uid, uid)
127
warnings = append(warnings, warning)
128
limaUser.Uid = formatUidGid(uid)
129
}
130
idg, err := call(ctx, []string{"id", "-g"})
131
if err != nil {
132
logrus.Debug(err)
133
}
134
gid, err := parseUidGid(idg)
135
if err != nil {
136
gid = fallbackGid
137
}
138
if _, err := parseUidGid(limaUser.Gid); err != nil {
139
warning := fmt.Sprintf("local gid %q is not a valid Linux gid (must be integer); using %d gid instead",
140
limaUser.Gid, gid)
141
warnings = append(warnings, warning)
142
limaUser.Gid = formatUidGid(gid)
143
}
144
}
145
})
146
if warn {
147
for _, warning := range warnings {
148
logrus.Warn(warning)
149
}
150
}
151
// Make sure we return a pointer to a COPY of limaUser
152
u := *limaUser
153
if versionutil.GreaterEqual(limaVersion, "1.0.0") {
154
if u.Username == "admin" {
155
if warn {
156
logrus.Warnf("local username %q is reserved; using %q instead", u.Username, fallbackUser)
157
}
158
u.Username = fallbackUser
159
}
160
}
161
return &u
162
}
163
164
func call(ctx context.Context, args []string) (string, error) {
165
cmd := exec.CommandContext(ctx, args[0], args[1:]...)
166
out, err := cmd.Output()
167
if err != nil {
168
return "", err
169
}
170
return strings.TrimSpace(string(out)), nil
171
}
172
173
// parseUidGid converts string value to Linux uid or gid.
174
func parseUidGid(uidOrGid string) (uint32, error) {
175
res, err := strconv.ParseUint(uidOrGid, 10, 32)
176
if err != nil {
177
return 0, err
178
}
179
return uint32(res), nil
180
}
181
182
// formatUidGid converts uid or gid to string value.
183
func formatUidGid(uidOrGid uint32) string {
184
return strconv.FormatUint(uint64(uidOrGid), 10)
185
}
186
187