diff --git a/c_defs_linux.go b/c_defs_linux.go index 32bad2f..5a0dd89 100644 --- a/c_defs_linux.go +++ b/c_defs_linux.go @@ -67,6 +67,10 @@ package scoutfs // // typedef struct scoutfs_ioctl_alloc_detail_entry_mod scoutfs_ioctl_alloc_detail_entry_t; // typedef struct scoutfs_ioctl_move_blocks scoutfs_ioctl_move_blocks_t; +// typedef struct scoutfs_ioctl_quota_rule scoutfs_ioctl_quota_rule_t; +// typedef struct scoutfs_ioctl_get_quota_rules scoutfs_ioctl_get_quota_rules_t; +// typedef struct scoutfs_ioctl_xattr_index_entry scoutfs_ioctl_xattr_index_entry_t; +// typedef struct scoutfs_ioctl_read_xattr_index scoutfs_ioctl_read_xattr_index_t; import "C" const IOCQUERYINODES = C.SCOUTFS_IOC_WALK_INODES @@ -84,6 +88,11 @@ const IOCALLOCDETAIL = C.SCOUTFS_IOC_ALLOC_DETAIL const IOCMOVEBLOCKS = C.SCOUTFS_IOC_MOVE_BLOCKS const IOCREADXATTRTOTALS = C.SCOUTFS_IOC_READ_XATTR_TOTALS const IOCGETREFERRINGENTRIES = C.SCOUTFS_IOC_GET_REFERRING_ENTRIES +const IOCGETQUOTARULES = C.SCOUTFS_IOC_GET_QUOTA_RULES +const IOCDELQUOTARULE = C.SCOUTFS_IOC_DEL_QUOTA_RULE +const IOCADDQUOTARULE = C.SCOUTFS_IOC_ADD_QUOTA_RULE +const IOCGETPROJECTID = C.SCOUTFS_IOC_GET_PROJECT_ID +const IOCSETPROJECTID = C.SCOUTFS_IOC_SET_PROJECT_ID const QUERYINODESMETASEQ = C.SCOUTFS_IOC_WALK_INODES_META_SEQ const QUERYINODESDATASEQ = C.SCOUTFS_IOC_WALK_INODES_DATA_SEQ @@ -118,6 +127,10 @@ type readXattrTotals C.scoutfs_ioctl_read_xattr_totals_t type xattrTotal C.scoutfs_ioctl_xattr_total_t type getReferringEntries C.scoutfs_ioctl_get_referring_entries_t type scoutfsDirent C.scoutfs_ioctl_dirent_t +type quotaRule C.scoutfs_ioctl_quota_rule_t +type getQuotaRules C.scoutfs_ioctl_get_quota_rules_t +type indexEntry C.scoutfs_ioctl_xattr_index_entry_t +type readXattrIndex C.scoutfs_ioctl_read_xattr_index_t const sizeofstatfsMore = C.sizeof_scoutfs_ioctl_statfs_more_t const sizeofxattrTotal = C.sizeof_scoutfs_ioctl_xattr_total_t diff --git a/scoutfs.go b/scoutfs.go index 3f6961f..4fbc6ee 100644 --- a/scoutfs.go +++ b/scoutfs.go @@ -1136,3 +1136,454 @@ func parseDent(r *bytes.Reader) (Parent, bool, error) { Ent: b.String(), }, dent.Flags&DIRENTFLAGLAST == DIRENTFLAGLAST, nil } + +const ( + // format.h: SQ_NS_LITERAL + quotaLiteral = 0 + // format.h: SQ_NS_PROJ + quotaProj = 1 + // format.h: SQ_NS_UID + quotaUID = 2 + // format.h: SQ_NS_GID + quotaGID = 3 + // format.h: SQ_NF_SELECT + quotaSelect = 1 + // format.h: SQ_RF_TOTL_COUNT + quotaFlagCount = 1 +) + +type QuotaType uint8 + +func (q QuotaType) String() string { + switch q { + case quotaLiteral: + return "literal" + case quotaProj: + return "project" + case quotaUID: + return "uid" + case quotaGID: + return "gid" + default: + return "unknown" + } +} + +const ( + // format.h: SQ_OP_INODE + QuotaInode = 0 + // format.h: SQ_OP_DATA + QuotaData = 1 +) + +type QuotaOp uint8 + +func (q QuotaOp) String() string { + switch q { + case QuotaInode: + return "Inode " + case QuotaData: + return "Size " + default: + return "Unknown" + } +} + +type Quotas struct { + rules []quotaRule + iter [2]uint64 + count int + f *os.File + done bool +} + +// QuotaRule is attributes for a single quota rule +type QuotaRule struct { + Op QuotaOp + QuotaValue [3]uint64 + QuotaSource [3]uint8 + QuotaFlags [3]uint8 + Limit uint64 + Prioirity uint8 + Flags uint8 +} + +func (q QuotaRule) String() string { + switch q.QuotaSource[2] { + case quotaLiteral: + return fmt.Sprintf("P: %3v %v Literal Limit: %v", + q.Prioirity, q.Op, q.Limit) + case quotaUID: + if q.QuotaFlags[2] == quotaSelect { + return fmt.Sprintf("P: %3v %v UID [%5v] Limit: %v", + q.Prioirity, q.Op, q.QuotaValue[2], q.Limit) + } + return fmt.Sprintf("P: %3v %v UID general Limit: %v", + q.Prioirity, q.Op, q.Limit) + case quotaGID: + if q.QuotaFlags[2] == quotaSelect { + return fmt.Sprintf("P: %3v %v GID [%5v] Limit: %v", + q.Prioirity, q.Op, q.QuotaValue[2], q.Limit) + } + return fmt.Sprintf("P: %3v %v GID general Limit: %v", + q.Prioirity, q.Op, q.Limit) + case quotaProj: + if q.QuotaFlags[2] == quotaSelect { + return fmt.Sprintf("P: %3v %v Proj [%5v] Limit: %v", + q.Prioirity, q.Op, q.QuotaValue[2], q.Limit) + } + return fmt.Sprintf("P: %3v %v Proj general Limit: %v", + q.Prioirity, q.Op, q.Limit) + } + + return q.Raw(false) +} + +func (q QuotaRule) IsGeneral() bool { + return q.QuotaSource[2] != quotaLiteral && q.QuotaFlags[2] != quotaSelect +} + +func (q QuotaRule) QuotaType() string { + switch q.QuotaSource[2] { + case quotaLiteral: + return "Literal" + case quotaUID: + return "UID" + case quotaGID: + return "GID" + case quotaProj: + return "Proj" + default: + return "-" + } +} + +func (q QuotaRule) Raw(human bool) string { + if human { + return fmt.Sprintf("Op=%v, Value=%v, Source=%v, Flags=%v, Limit=%v Prioirty=%v", + q.Op, q.QuotaValue, q.QuotaSource, q.QuotaFlags, byteToHuman(q.Limit), q.Prioirity) + } + return fmt.Sprintf("Op=%v, Value=%v, Source=%v, Flags=%v, Limit=%v Prioirty=%v", + q.Op, q.QuotaValue, q.QuotaSource, q.QuotaFlags, q.Limit, q.Prioirity) +} + +func byteToHuman(b uint64) string { + const unit = 1024 + if b < unit { + return fmt.Sprintf("%d", b) + } + div, exp := int64(unit), 0 + for n := b / unit; n >= unit; n /= unit { + div *= unit + exp++ + } + return fmt.Sprintf("%.1f%c", float64(b)/float64(div), "KMGTPE"[exp]) +} + +// RuleSet is a list of quota rules, when sorted these +// will be in the order as the filesystem would match them +type RuleSet []QuotaRule + +// Len returns the length of the ruleset +func (r RuleSet) Len() int { return len(r) } + +// Less returns true if the i-th rule would be matched before the j-th rule +func (r RuleSet) Less(i, j int) bool { + if r[i].Prioirity != r[j].Prioirity { + // higher priority is matched first + return r[i].Prioirity > r[j].Prioirity + } + + if r[i].QuotaValue[0] != r[j].QuotaValue[0] { + return r[i].QuotaValue[0] > r[j].QuotaValue[0] + } + if r[i].QuotaSource[0] != r[j].QuotaSource[0] { + return r[i].QuotaSource[0] > r[j].QuotaSource[0] + } + if r[i].QuotaFlags[0] != r[j].QuotaFlags[0] { + return r[i].QuotaFlags[0] > r[j].QuotaFlags[0] + } + + if r[i].QuotaValue[1] != r[j].QuotaValue[1] { + return r[i].QuotaValue[1] > r[j].QuotaValue[1] + } + if r[i].QuotaSource[1] != r[j].QuotaSource[1] { + return r[i].QuotaSource[1] > r[j].QuotaSource[1] + } + if r[i].QuotaFlags[1] != r[j].QuotaFlags[1] { + return r[i].QuotaFlags[1] > r[j].QuotaFlags[1] + } + + if r[i].QuotaValue[2] != r[j].QuotaValue[2] { + return r[i].QuotaValue[2] > r[j].QuotaValue[2] + } + if r[i].QuotaSource[2] != r[j].QuotaSource[2] { + return r[i].QuotaSource[2] > r[j].QuotaSource[2] + } + if r[i].QuotaFlags[2] != r[j].QuotaFlags[2] { + return r[i].QuotaFlags[2] > r[j].QuotaFlags[2] + } + + if r[i].Op != r[j].Op { + return r[i].Op > r[j].Op + } + + if r[i].Limit != r[j].Limit { + return r[i].Limit > r[j].Limit + } + + // rules are the same (should never happen) + return false +} + +// Swap will swap the i-th and j-th elements in the ruleset +func (r RuleSet) Swap(i, j int) { r[i], r[j] = r[j], r[i] } + +// GetQuotaRules initalizes reading the current quota set. +// Quota rules are not returned in sorted order, so to get +// the order which they are matched the full list must be +// collected then sorted. +func GetQuotaRules(f *os.File, count int) (*Quotas, error) { + if count < 1 { + return nil, fmt.Errorf("must provide count > 0") + } + rules := make([]quotaRule, count) + + return &Quotas{rules: rules, f: f, count: count}, nil +} + +// Next returns next batch of quota rules. +func (q *Quotas) Next() ([]QuotaRule, error) { + if q.done { + return nil, nil + } + + query := getQuotaRules{ + Iterator: q.iter, + Ptr: uint64(uintptr(unsafe.Pointer(&q.rules[0]))), + Nr: uint64(q.count), + } + + n, err := scoutfsctl(q.f, IOCGETQUOTARULES, unsafe.Pointer(&query)) + if err != nil { + return nil, err + } + if n == 0 { + q.done = true + return nil, nil + } + + ret := make([]QuotaRule, n) + for i := range ret { + ret[i].Op = QuotaOp(q.rules[i].Op) + ret[i].QuotaValue = q.rules[i].Name_val + ret[i].QuotaFlags = q.rules[i].Name_flags + ret[i].QuotaSource = q.rules[i].Name_source + ret[i].Limit = q.rules[i].Limit + ret[i].Prioirity = q.rules[i].Prio + } + + q.iter = query.Iterator + + return ret, nil +} + +// Reset resets the quota listing to the start +func (t *Quotas) Reset() { + t.done = false + t.iter = [2]uint64{} +} + +func QuotaDelete(f *os.File, q QuotaRule) error { + qr := quotaRule{ + Name_val: q.QuotaValue, + Limit: q.Limit, + Prio: q.Prioirity, + Op: uint8(q.Op), + Rule_flags: q.Flags, + Name_source: q.QuotaSource, + Name_flags: q.QuotaFlags, + } + + _, err := scoutfsctl(f, IOCDELQUOTARULE, unsafe.Pointer(&qr)) + return err +} + +func QuotaAddDataLiteral(f *os.File, id1, id2, id3, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, id3}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaLiteral}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeLiteral(f *os.File, id1, id2, id3, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, id3}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaLiteral}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func QuotaAddDataProjectGeneral(f *os.File, id1, id2, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, 0}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaProj}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeProjectGeneral(f *os.File, id1, id2, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, 0}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaProj}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func QuotaAddDataProject(f *os.File, id1, id2, project, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, project}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaProj}, + QuotaFlags: [3]uint8{0, 0, quotaSelect}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeProject(f *os.File, id1, id2, project, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, project}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaProj}, + QuotaFlags: [3]uint8{0, 0, quotaSelect}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func QuotaAddDataUIDGeneral(f *os.File, id1, id2, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, 0}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaUID}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeUIDGeneral(f *os.File, id1, id2, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, 0}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaUID}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func QuotaAddDataUID(f *os.File, id1, id2, uid, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, uid}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaUID}, + QuotaFlags: [3]uint8{0, 0, quotaSelect}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeUID(f *os.File, id1, id2, uid, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, uid}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaUID}, + QuotaFlags: [3]uint8{0, 0, quotaSelect}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func QuotaAddDataGIDGeneral(f *os.File, id1, id2, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, 0}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaGID}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeGIDGeneral(f *os.File, id1, id2, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, 0}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaGID}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func QuotaAddDataGID(f *os.File, id1, id2, gid, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaData, + QuotaValue: [3]uint64{id1, id2, gid}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaGID}, + QuotaFlags: [3]uint8{0, 0, quotaSelect}, + Limit: limit, + Prioirity: priority, + }) +} + +func QuotaAddInodeGID(f *os.File, id1, id2, gid, limit uint64, priority uint8) error { + return quotaAdd(f, QuotaRule{ + Op: QuotaInode, + QuotaValue: [3]uint64{id1, id2, gid}, + QuotaSource: [3]uint8{quotaLiteral, quotaLiteral, quotaGID}, + QuotaFlags: [3]uint8{0, 0, quotaSelect}, + Limit: limit, + Prioirity: priority, + Flags: quotaFlagCount, + }) +} + +func quotaAdd(f *os.File, q QuotaRule) error { + qr := quotaRule{ + Name_val: q.QuotaValue, + Limit: q.Limit, + Prio: q.Prioirity, + Op: uint8(q.Op), + Rule_flags: q.Flags, + Name_source: q.QuotaSource, + Name_flags: q.QuotaFlags, + } + + _, err := scoutfsctl(f, IOCADDQUOTARULE, unsafe.Pointer(&qr)) + return err +} + +func GetProjectID(f *os.File) (uint64, error) { + var projectid uint64 + _, err := scoutfsctl(f, IOCGETPROJECTID, unsafe.Pointer(&projectid)) + return projectid, err +} + +func SetProjectID(f *os.File, projectid uint64) error { + _, err := scoutfsctl(f, IOCSETPROJECTID, unsafe.Pointer(&projectid)) + return err +} diff --git a/scoutfsdefs.go b/scoutfsdefs.go index 618c191..28c7e37 100644 --- a/scoutfsdefs.go +++ b/scoutfsdefs.go @@ -18,6 +18,11 @@ const IOCALLOCDETAIL = 0x4010e80c const IOCMOVEBLOCKS = 0x4030e80d const IOCREADXATTRTOTALS = 0x4028e80f const IOCGETREFERRINGENTRIES = 0x4028e811 +const IOCGETQUOTARULES = 0x8020e814 +const IOCDELQUOTARULE = 0x4030e816 +const IOCADDQUOTARULE = 0x4030e815 +const IOCGETPROJECTID = 0x8008e812 +const IOCSETPROJECTID = 0x4008e813 const QUERYINODESMETASEQ = 0x0 const QUERYINODESDATASEQ = 0x1 @@ -177,6 +182,33 @@ type scoutfsDirent struct { Name_len uint8 Name [3]uint8 } +type quotaRule struct { + Name_val [3]uint64 + Limit uint64 + Prio uint8 + Op uint8 + Rule_flags uint8 + Name_source [3]uint8 + Name_flags [3]uint8 + X_pad [7]uint8 +} +type getQuotaRules struct { + Iterator [2]uint64 + Ptr uint64 + Nr uint64 +} +type indexEntry struct { + A uint64 + B uint64 + Ino uint64 +} +type readXattrIndex struct { + Flags uint64 + First indexEntry + Last indexEntry + Ptr uint64 + Nr uint64 +} const sizeofstatfsMore = 0x30 const sizeofxattrTotal = 0x28