| // Copyright 2020 The Monogon Project Authors. | 
 | // | 
 | // SPDX-License-Identifier: Apache-2.0 | 
 | // | 
 | // Licensed under the Apache License, Version 2.0 (the "License"); | 
 | // you may not use this file except in compliance with the License. | 
 | // You may obtain a copy of the License at | 
 | // | 
 | //     http://www.apache.org/licenses/LICENSE-2.0 | 
 | // | 
 | // Unless required by applicable law or agreed to in writing, software | 
 | // distributed under the License is distributed on an "AS IS" BASIS, | 
 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | 
 | // See the License for the specific language governing permissions and | 
 | // limitations under the License. | 
 |  | 
 | // Package quotactl implements a low-level wrapper around the modern portion of Linux's | 
 | // quotactl() syscall. See the fsquota package for a nicer interface to the most common part | 
 | // of this API. | 
 | package quotactl | 
 |  | 
 | import ( | 
 | 	"fmt" | 
 | 	"unsafe" | 
 |  | 
 | 	"golang.org/x/sys/unix" | 
 | ) | 
 |  | 
 | type QuotaType uint | 
 |  | 
 | const ( | 
 | 	QuotaTypeUser QuotaType = iota | 
 | 	QuotaTypeGroup | 
 | 	QuotaTypeProject | 
 | ) | 
 |  | 
 | const ( | 
 | 	Q_SYNC uint = ((0x800001 + iota) << 8) | 
 | 	Q_QUOTAON | 
 | 	Q_QUOTAOFF | 
 | 	Q_GETFMT | 
 | 	Q_GETINFO | 
 | 	Q_SETINFO | 
 | 	Q_GETQUOTA | 
 | 	Q_SETQUOTA | 
 | 	Q_GETNEXTQUOTA | 
 | ) | 
 |  | 
 | const ( | 
 | 	FlagBLimitsValid = 1 << iota | 
 | 	FlagSpaceValid | 
 | 	FlagILimitsValid | 
 | 	FlagInodesValid | 
 | 	FlagBTimeValid | 
 | 	FlagITimeValid | 
 | ) | 
 |  | 
 | type DQInfo struct { | 
 | 	Bgrace uint64 | 
 | 	Igrace uint64 | 
 | 	Flags  uint32 | 
 | 	Valid  uint32 | 
 | } | 
 |  | 
 | type Quota struct { | 
 | 	BHardLimit uint64 // Both Byte limits are prescaled by 1024 (so are in KiB), but CurSpace is in B | 
 | 	BSoftLimit uint64 | 
 | 	CurSpace   uint64 | 
 | 	IHardLimit uint64 | 
 | 	ISoftLimit uint64 | 
 | 	CurInodes  uint64 | 
 | 	BTime      uint64 | 
 | 	ITime      uint64 | 
 | 	Valid      uint32 | 
 | } | 
 |  | 
 | type NextDQBlk struct { | 
 | 	HardLimitBytes  uint64 | 
 | 	SoftLimitBytes  uint64 | 
 | 	CurrentBytes    uint64 | 
 | 	HardLimitInodes uint64 | 
 | 	SoftLimitInodes uint64 | 
 | 	CurrentInodes   uint64 | 
 | 	BTime           uint64 | 
 | 	ITime           uint64 | 
 | 	Valid           uint32 | 
 | 	ID              uint32 | 
 | } | 
 |  | 
 | type QuotaFormat uint32 | 
 |  | 
 | // Collected from quota_format_type structs | 
 | const ( | 
 | 	// QuotaFormatNone is a special case where all quota information is | 
 | 	// stored inside filesystem metadata and thus requires no quotaFilePath. | 
 | 	QuotaFormatNone   QuotaFormat = 0 | 
 | 	QuotaFormatVFSOld QuotaFormat = 1 | 
 | 	QuotaFormatVFSV0  QuotaFormat = 2 | 
 | 	QuotaFormatOCFS2  QuotaFormat = 3 | 
 | 	QuotaFormatVFSV1  QuotaFormat = 4 | 
 | ) | 
 |  | 
 | // QuotaOn turns quota accounting and enforcement on | 
 | func QuotaOn(device string, qtype QuotaType, quotaFormat QuotaFormat, quotaFilePath string) error { | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	pathArg, err := unix.BytePtrFromString(quotaFilePath) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_QUOTAON|uint(qtype)), uintptr(unsafe.Pointer(devArg)), uintptr(quotaFormat), uintptr(unsafe.Pointer(pathArg)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return err | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // QuotaOff turns quotas off | 
 | func QuotaOff(device string, qtype QuotaType) error { | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_QUOTAOFF|uint(qtype)), uintptr(unsafe.Pointer(devArg)), 0, 0, 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return err | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // GetFmt gets the quota format used on given filesystem | 
 | func GetFmt(device string, qtype QuotaType) (QuotaFormat, error) { | 
 | 	var fmt uint32 | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return 0, err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_GETFMT|uint(qtype)), uintptr(unsafe.Pointer(devArg)), 0, uintptr(unsafe.Pointer(&fmt)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return 0, err | 
 | 	} | 
 | 	return QuotaFormat(fmt), nil | 
 | } | 
 |  | 
 | // GetInfo gets information about quota files | 
 | func GetInfo(device string, qtype QuotaType) (*DQInfo, error) { | 
 | 	var info DQInfo | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_GETINFO|uint(qtype)), uintptr(unsafe.Pointer(devArg)), 0, uintptr(unsafe.Pointer(&info)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return nil, err | 
 | 	} | 
 | 	return &info, nil | 
 | } | 
 |  | 
 | // SetInfo sets information about quota files | 
 | func SetInfo(device string, qtype QuotaType, info *DQInfo) error { | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_SETINFO|uint(qtype)), uintptr(unsafe.Pointer(devArg)), 0, uintptr(unsafe.Pointer(info)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return err | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // GetQuota gets user quota structure | 
 | func GetQuota(device string, qtype QuotaType, id uint32) (*Quota, error) { | 
 | 	var info Quota | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_GETQUOTA|uint(qtype)), uintptr(unsafe.Pointer(devArg)), uintptr(id), uintptr(unsafe.Pointer(&info)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return nil, err | 
 | 	} | 
 | 	return &info, nil | 
 | } | 
 |  | 
 | // GetNextQuota gets disk limits and usage > ID | 
 | func GetNextQuota(device string, qtype QuotaType, id uint32) (*NextDQBlk, error) { | 
 | 	var info NextDQBlk | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return nil, err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_GETNEXTQUOTA|uint(qtype)), uintptr(unsafe.Pointer(devArg)), uintptr(id), uintptr(unsafe.Pointer(&info)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return nil, err | 
 | 	} | 
 | 	return &info, nil | 
 | } | 
 |  | 
 | // SetQuota sets the given quota | 
 | func SetQuota(device string, qtype QuotaType, id uint32, quota *Quota) error { | 
 | 	devArg, err := unix.BytePtrFromString(device) | 
 | 	if err != nil { | 
 | 		return err | 
 | 	} | 
 | 	_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_SETQUOTA|uint(qtype)), uintptr(unsafe.Pointer(devArg)), uintptr(id), uintptr(unsafe.Pointer(quota)), 0, 0) | 
 | 	if err != unix.Errno(0) { | 
 | 		return fmt.Errorf("failed to set quota: %w", err) | 
 | 	} | 
 | 	return nil | 
 | } | 
 |  | 
 | // Sync syncs disk copy of filesystems quotas. If device is empty it syncs all filesystems. | 
 | func Sync(device string) error { | 
 | 	if device != "" { | 
 | 		devArg, err := unix.BytePtrFromString(device) | 
 | 		if err != nil { | 
 | 			return err | 
 | 		} | 
 | 		_, _, err = unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_SYNC), uintptr(unsafe.Pointer(devArg)), 0, 0, 0, 0) | 
 | 		if err != unix.Errno(0) { | 
 | 			return err | 
 | 		} | 
 | 	} else { | 
 | 		_, _, err := unix.Syscall6(unix.SYS_QUOTACTL, uintptr(Q_SYNC), 0, 0, 0, 0, 0) | 
 | 		if err != unix.Errno(0) { | 
 | 			return err | 
 | 		} | 
 | 	} | 
 | 	return nil | 
 | } |