mirror of
https://github.com/gitpod-io/gitpod.git
synced 2025-12-08 17:36:30 +00:00
169 lines
3.6 KiB
Go
169 lines
3.6 KiB
Go
// Copyright (c) 2021 Gitpod GmbH. All rights reserved.
|
|
// Licensed under the GNU Affero General Public License (AGPL).
|
|
// See License.AGPL.txt in the project root for license information.
|
|
|
|
package quota
|
|
|
|
import (
|
|
"fmt"
|
|
"os/exec"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
)
|
|
|
|
type xfsQuotaExec func(dir, command string) (output string, err error)
|
|
|
|
func defaultXfsQuotaExec(dir, command string) (output string, err error) {
|
|
out, err := exec.Command("xfs_quota", "-x", "-c", command, dir).CombinedOutput()
|
|
if err != nil {
|
|
return "", fmt.Errorf("xfs_quota error: %s: %v", string(out), err)
|
|
}
|
|
return string(out), nil
|
|
}
|
|
|
|
const (
|
|
prjidLow = 1000
|
|
prjidHi = 10000
|
|
)
|
|
|
|
type XFS struct {
|
|
Dir string
|
|
|
|
exec xfsQuotaExec
|
|
|
|
projectIDs map[int]struct{}
|
|
mu sync.Mutex
|
|
}
|
|
|
|
func NewXFS(path string) (*XFS, error) {
|
|
res := &XFS{
|
|
Dir: path,
|
|
projectIDs: make(map[int]struct{}),
|
|
exec: defaultXfsQuotaExec,
|
|
}
|
|
|
|
// Note: if the underlying filesystem does not support XFS project quota,
|
|
// getUsedProjectIDs will fail, hence the NewXFS call will fail.
|
|
prjIDs, err := res.getUsedProjectIDs()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, prjID := range prjIDs {
|
|
res.projectIDs[prjID] = struct{}{}
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// getUsedProjectIDs lists all project IDs used on the filesystem
|
|
func (xfs *XFS) getUsedProjectIDs() ([]int, error) {
|
|
out, err := xfs.exec(xfs.Dir, "report -N")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var res []int
|
|
for _, l := range strings.Split(out, "\n") {
|
|
fields := strings.Fields(l)
|
|
if len(fields) < 2 {
|
|
continue
|
|
}
|
|
|
|
prjID, err := strconv.Atoi(strings.TrimPrefix(fields[0], "#"))
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
used, err := strconv.Atoi(strings.TrimSpace(fields[1]))
|
|
if err != nil || used == 0 {
|
|
continue
|
|
}
|
|
|
|
res = append(res, prjID)
|
|
}
|
|
return res, nil
|
|
}
|
|
|
|
// SetQuota sets the quota for a path
|
|
func (xfs *XFS) SetQuota(path string, quota Size, isHard bool) (projectID int, err error) {
|
|
xfs.mu.Lock()
|
|
var (
|
|
prjID = prjidLow
|
|
found bool
|
|
)
|
|
for ; prjID < prjidHi; prjID++ {
|
|
_, exists := xfs.projectIDs[prjID]
|
|
if !exists {
|
|
found = true
|
|
xfs.projectIDs[prjID] = struct{}{}
|
|
break
|
|
}
|
|
}
|
|
xfs.mu.Unlock()
|
|
if !found {
|
|
return 0, fmt.Errorf("no free projectID found")
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
xfs.mu.Lock()
|
|
delete(xfs.projectIDs, prjID)
|
|
xfs.mu.Unlock()
|
|
}
|
|
}()
|
|
|
|
_, err = xfs.SetQuotaWithPrjId(path, quota, prjID, isHard)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
return prjID, nil
|
|
}
|
|
|
|
func (xfs *XFS) SetQuotaWithPrjId(path string, quota Size, prjID int, isHard bool) (projectID int, err error) {
|
|
_, err = xfs.exec(xfs.Dir, fmt.Sprintf("project -s -d 1 -p %s %d", path, prjID))
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if isHard {
|
|
_, err = xfs.exec(xfs.Dir, fmt.Sprintf("limit -p bhard=%d %d", quota, prjID))
|
|
} else {
|
|
_, err = xfs.exec(xfs.Dir, fmt.Sprintf("limit -p bsoft=%d %d", quota, prjID))
|
|
}
|
|
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return prjID, nil
|
|
}
|
|
|
|
// RegisterProject tells this implementation that a projectID is already in use
|
|
func (xfs *XFS) RegisterProject(prjID int) {
|
|
xfs.mu.Lock()
|
|
defer xfs.mu.Unlock()
|
|
|
|
xfs.projectIDs[prjID] = struct{}{}
|
|
}
|
|
|
|
// RemoveQuota removes the limitation for a project/path and frees the projectID
|
|
func (xfs *XFS) RemoveQuota(projectID int) error {
|
|
_, err := xfs.exec(xfs.Dir, fmt.Sprintf("limit -p bsoft=0 bhard=0 %d", projectID))
|
|
if err != nil {
|
|
return err
|
|
}
|
|
xfs.mu.Lock()
|
|
delete(xfs.projectIDs, projectID)
|
|
xfs.mu.Unlock()
|
|
return nil
|
|
}
|
|
|
|
// GetProjectUseCount returns the number of projectIDs in use
|
|
func (xfs *XFS) GetProjectUseCount() int {
|
|
xfs.mu.Lock()
|
|
defer xfs.mu.Unlock()
|
|
|
|
return len(xfs.projectIDs)
|
|
}
|