Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
gitpod-io
GitHub Repository: gitpod-io/gitpod
Path: blob/main/components/ws-daemon/pkg/quota/xfs.go
2499 views
1
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
2
// Licensed under the GNU Affero General Public License (AGPL).
3
// See License.AGPL.txt in the project root for license information.
4
5
package quota
6
7
import (
8
"fmt"
9
"os/exec"
10
"strconv"
11
"strings"
12
"sync"
13
)
14
15
type xfsQuotaExec func(dir, command string) (output string, err error)
16
17
func defaultXfsQuotaExec(dir, command string) (output string, err error) {
18
out, err := exec.Command("xfs_quota", "-x", "-c", command, dir).CombinedOutput()
19
if err != nil {
20
return "", fmt.Errorf("xfs_quota error: %s: %v", string(out), err)
21
}
22
return string(out), nil
23
}
24
25
const (
26
prjidLow = 1000
27
prjidHi = 10000
28
)
29
30
type XFS struct {
31
Dir string
32
33
exec xfsQuotaExec
34
35
projectIDs map[int]struct{}
36
mu sync.Mutex
37
}
38
39
func NewXFS(path string) (*XFS, error) {
40
res := &XFS{
41
Dir: path,
42
projectIDs: make(map[int]struct{}),
43
exec: defaultXfsQuotaExec,
44
}
45
46
// Note: if the underlying filesystem does not support XFS project quota,
47
// getUsedProjectIDs will fail, hence the NewXFS call will fail.
48
prjIDs, err := res.getUsedProjectIDs()
49
if err != nil {
50
return nil, err
51
}
52
for _, prjID := range prjIDs {
53
res.projectIDs[prjID] = struct{}{}
54
}
55
56
return res, nil
57
}
58
59
// getUsedProjectIDs lists all project IDs used on the filesystem
60
func (xfs *XFS) getUsedProjectIDs() ([]int, error) {
61
out, err := xfs.exec(xfs.Dir, "report -N")
62
if err != nil {
63
return nil, err
64
}
65
66
var res []int
67
for _, l := range strings.Split(out, "\n") {
68
fields := strings.Fields(l)
69
if len(fields) < 2 {
70
continue
71
}
72
73
prjID, err := strconv.Atoi(strings.TrimPrefix(fields[0], "#"))
74
if err != nil {
75
continue
76
}
77
78
used, err := strconv.Atoi(strings.TrimSpace(fields[1]))
79
if err != nil || used == 0 {
80
continue
81
}
82
83
res = append(res, prjID)
84
}
85
return res, nil
86
}
87
88
// SetQuota sets the quota for a path
89
func (xfs *XFS) SetQuota(path string, quota Size, isHard bool) (projectID int, err error) {
90
xfs.mu.Lock()
91
var (
92
prjID = prjidLow
93
found bool
94
)
95
for ; prjID < prjidHi; prjID++ {
96
_, exists := xfs.projectIDs[prjID]
97
if !exists {
98
found = true
99
xfs.projectIDs[prjID] = struct{}{}
100
break
101
}
102
}
103
xfs.mu.Unlock()
104
if !found {
105
return 0, fmt.Errorf("no free projectID found")
106
}
107
108
defer func() {
109
if err != nil {
110
xfs.mu.Lock()
111
delete(xfs.projectIDs, prjID)
112
xfs.mu.Unlock()
113
}
114
}()
115
116
_, err = xfs.SetQuotaWithPrjId(path, quota, prjID, isHard)
117
if err != nil {
118
return 0, err
119
}
120
121
return prjID, nil
122
}
123
124
func (xfs *XFS) SetQuotaWithPrjId(path string, quota Size, prjID int, isHard bool) (projectID int, err error) {
125
_, err = xfs.exec(xfs.Dir, fmt.Sprintf("project -s -d 1 -p %s %d", path, prjID))
126
if err != nil {
127
return 0, err
128
}
129
130
if isHard {
131
_, err = xfs.exec(xfs.Dir, fmt.Sprintf("limit -p bhard=%d %d", quota, prjID))
132
} else {
133
_, err = xfs.exec(xfs.Dir, fmt.Sprintf("limit -p bsoft=%d %d", quota, prjID))
134
}
135
136
if err != nil {
137
return 0, err
138
}
139
return prjID, nil
140
}
141
142
// RegisterProject tells this implementation that a projectID is already in use
143
func (xfs *XFS) RegisterProject(prjID int) {
144
xfs.mu.Lock()
145
defer xfs.mu.Unlock()
146
147
xfs.projectIDs[prjID] = struct{}{}
148
}
149
150
// RemoveQuota removes the limitation for a project/path and frees the projectID
151
func (xfs *XFS) RemoveQuota(projectID int) error {
152
_, err := xfs.exec(xfs.Dir, fmt.Sprintf("limit -p bsoft=0 bhard=0 %d", projectID))
153
if err != nil {
154
return err
155
}
156
xfs.mu.Lock()
157
delete(xfs.projectIDs, projectID)
158
xfs.mu.Unlock()
159
return nil
160
}
161
162
// GetProjectUseCount returns the number of projectIDs in use
163
func (xfs *XFS) GetProjectUseCount() int {
164
xfs.mu.Lock()
165
defer xfs.mu.Unlock()
166
167
return len(xfs.projectIDs)
168
}
169
170