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,
}
prjIDs, err := res.getUsedProjectIDs()
if err != nil {
return nil, err
}
for _, prjID := range prjIDs {
res.projectIDs[prjID] = struct{}{}
}
return res, nil
}
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
}
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
}
func (xfs *XFS) RegisterProject(prjID int) {
xfs.mu.Lock()
defer xfs.mu.Unlock()
xfs.projectIDs[prjID] = struct{}{}
}
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
}
func (xfs *XFS) GetProjectUseCount() int {
xfs.mu.Lock()
defer xfs.mu.Unlock()
return len(xfs.projectIDs)
}