From 2c056f768ecd4bf25d2c9959e7452b5f207746f9 Mon Sep 17 00:00:00 2001 From: Chao Wang Date: Wed, 2 Sep 2026 22:46:06 -0700 Subject: [PATCH] Pin all ioctl buffer addresses ScoutFS ioctl structures store user buffer addresses as integers, so Go cannot update them if the backing memory moves. Pin every nested buffer before converting its pointer and unpin it after the ioctl completes. --- scoutfs.go | 82 +++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/scoutfs.go b/scoutfs.go index 71fe189..1060b62 100644 --- a/scoutfs.go +++ b/scoutfs.go @@ -36,6 +36,12 @@ const ( //leaderfile = "quorum/is_leader" ) +// pinnedAddress pins a pointer before hiding it from the runtime in an ioctl field. +func pinnedAddress[T any](pinner *runtime.Pinner, ptr *T) uint64 { + pinner.Pin(ptr) + return uint64(uintptr(unsafe.Pointer(ptr))) +} + // Query to keep track of in-process query type Query struct { first InodesEntry @@ -104,15 +110,18 @@ func WithBatchSize(size uint32) Option { // Next gets the next batch of inodes func (q *Query) Next() ([]InodesEntry, error) { + var pin runtime.Pinner + query := queryInodes{ First: q.first, Last: q.last, - Entries_ptr: uint64(uintptr(unsafe.Pointer(&q.buf[0]))), + Entries_ptr: pinnedAddress(&pin, &q.buf[0]), Nr_entries: q.batch, Index: q.index, } n, err := scoutfsctl(q.fsfd, IOCQUERYINODES, unsafe.Pointer(&query)) + pin.Unpin() if err != nil { return nil, err } @@ -231,13 +240,11 @@ type inoPathResult struct { // (usually just the base mount point directory) func InoToPath(dirfd *os.File, ino uint64) (string, error) { var res inoPathResult - // Result_ptr is an integer, so keep its target at a stable address. var pin runtime.Pinner - pin.Pin(&res) defer pin.Unpin() ip := inoPath{ Ino: ino, - Result_ptr: uint64(uintptr(unsafe.Pointer(&res))), + Result_ptr: pinnedAddress(&pin, &res), Result_bytes: uint16(unsafe.Sizeof(res)), } @@ -256,13 +263,11 @@ func InoToPath(dirfd *os.File, ino uint64) (string, error) { // (usually just the base mount point directory) func InoToPaths(dirfd *os.File, ino uint64) ([]string, error) { var res inoPathResult - // Result_ptr is an integer, so keep its target at a stable address. var pin runtime.Pinner - pin.Pin(&res) defer pin.Unpin() ip := inoPath{ Ino: ino, - Result_ptr: uint64(uintptr(unsafe.Pointer(&res))), + Result_ptr: pinnedAddress(&pin, &res), Result_bytes: uint16(unsafe.Sizeof(res)), } @@ -380,9 +385,12 @@ func StageFile(path string, version, offset uint64, b []byte) (int, error) { // FStageFile rehydrates offline file func FStageFile(f *os.File, version, offset uint64, b []byte) (int, error) { + var pin runtime.Pinner + defer pin.Unpin() + r := iocStage{ Data_version: version, - Buf_ptr: uint64(uintptr(unsafe.Pointer(&b[0]))), + Buf_ptr: pinnedAddress(&pin, &b[0]), Offset: offset, Length: int32(len(b)), } @@ -430,14 +438,17 @@ func WithWaitersCount(size uint16) WOption { // Next gets the next batch of data waiters, returns nil, nil if no waiters func (w *Waiters) Next() ([]DataWaitingEntry, error) { + var pin runtime.Pinner + dataWaiting := dataWaiting{ After_ino: w.ino, After_iblock: w.iblock, - Ents_ptr: uint64(uintptr(unsafe.Pointer(&w.buf[0]))), + Ents_ptr: pinnedAddress(&pin, &w.buf[0]), Ents_nr: w.batch, } n, err := scoutfsctl(w.fsfd, IOCDATAWAITING, unsafe.Pointer(&dataWaiting)) + pin.Unpin() if err != nil { return nil, err } @@ -545,21 +556,24 @@ func WithXStartIno(ino uint64) XOption { // Next gets the next batch of inodes func (q *XattrQuery) Next() ([]uint64, error) { - name := []byte(q.key) - query := searchXattrs{ - Next_ino: q.next, - Last_ino: max64, - Name_ptr: uint64(uintptr(unsafe.Pointer(&name[0]))), - Inodes_ptr: uint64(uintptr(unsafe.Pointer(&q.buf[0]))), - Name_bytes: uint16(len(name)), - Nr_inodes: q.batch, - } - if q.done { return nil, nil } + name := []byte(q.key) + var pin runtime.Pinner + + query := searchXattrs{ + Next_ino: q.next, + Last_ino: max64, + Name_ptr: pinnedAddress(&pin, &name[0]), + Inodes_ptr: pinnedAddress(&pin, &q.buf[0]), + Name_bytes: uint16(len(name)), + Nr_inodes: q.batch, + } + n, err := scoutfsctl(q.fsfd, IOCSEARCHXATTRS, unsafe.Pointer(&query)) + pin.Unpin() if err != nil { return nil, err } @@ -615,10 +629,13 @@ func NewListXattrHidden(f *os.File, b []byte) *ListXattrHidden { // Next gets next set of results, complete when string slice is nil func (l *ListXattrHidden) Next() ([]string, error) { + var pin runtime.Pinner + l.lxr.Buf_bytes = uint32(len(l.buf)) - l.lxr.Buf_ptr = uint64(uintptr(unsafe.Pointer(&l.buf[0]))) + l.lxr.Buf_ptr = pinnedAddress(&pin, &l.buf[0]) n, err := scoutfsctl(l.f, IOCLISTXATTRHIDDEN, unsafe.Pointer(l.lxr)) + pin.Unpin() if err != nil { return nil, err } @@ -764,12 +781,14 @@ func GetDF(f *os.File) (DiskUsage, error) { nr := dfBatchCount buf := make([]byte, int(unsafe.Sizeof(allocDetailEntry{}))*int(nr)) var ret int + var pin runtime.Pinner for { ad := allocDetail{ Nr: nr, - Ptr: uint64(uintptr(unsafe.Pointer(&buf[0]))), + Ptr: pinnedAddress(&pin, &buf[0]), } ret, err = scoutfsctl(f, IOCALLOCDETAIL, unsafe.Pointer(&ad)) + pin.Unpin() if err == syscall.EOVERFLOW { nr = nr * 2 buf = make([]byte, int(unsafe.Sizeof(allocDetailEntry{}))*int(nr)) @@ -930,10 +949,12 @@ type XattrTotal struct { // ReadXattrTotals returns the XattrTotal for the given id func ReadXattrTotals(f *os.File, id1, id2, id3 uint64) (XattrTotal, error) { totls := make([]xattrTotal, 1) + var pin runtime.Pinner + defer pin.Unpin() query := readXattrTotals{ Pos_name: [3]uint64{id1, id2, id3}, - Totals_ptr: uint64(uintptr(unsafe.Pointer(&totls[0]))), + Totals_ptr: pinnedAddress(&pin, &totls[0]), Totals_bytes: sizeofxattrTotal, } @@ -985,14 +1006,16 @@ func (t *TotalsGroup) Next() ([]XattrTotal, error) { if t.done { return nil, nil } + var pin runtime.Pinner query := readXattrTotals{ Pos_name: t.pos, - Totals_ptr: uint64(uintptr(unsafe.Pointer(&t.totls[0]))), + Totals_ptr: pinnedAddress(&pin, &t.totls[0]), Totals_bytes: sizeofxattrTotal * uint64(t.count), } n, err := scoutfsctl(t.f, IOCREADXATTRTOTALS, unsafe.Pointer(&query)) + pin.Unpin() if err != nil { return nil, err } @@ -1048,9 +1071,11 @@ func GetParents(dirfd *os.File, ino uint64, b []byte) ([]Parent, error) { } gre := getReferringEntries{} + var pin runtime.Pinner + defer pin.Unpin() gre.Entries_bytes = uint64(len(b)) - gre.Entries_ptr = uint64(uintptr(unsafe.Pointer(&b[0]))) + gre.Entries_ptr = pinnedAddress(&pin, &b[0]) gre.Ino = ino var parents []Parent @@ -1436,14 +1461,16 @@ func (q *Quotas) Next() ([]QuotaRule, error) { if q.done { return nil, nil } + var pin runtime.Pinner query := getQuotaRules{ Iterator: q.iter, - Ptr: uint64(uintptr(unsafe.Pointer(&q.rules[0]))), + Ptr: pinnedAddress(&pin, &q.rules[0]), Nr: uint64(q.count), } n, err := scoutfsctl(q.f, IOCGETQUOTARULES, unsafe.Pointer(&query)) + pin.Unpin() if err != nil { return nil, err } @@ -1767,14 +1794,17 @@ type IndexEnt struct { } func (i *IndexSearch) Next() ([]IndexEnt, error) { + var pin runtime.Pinner + query := readXattrIndex{ First: i.pos, Last: i.end, - Ptr: uint64(uintptr(unsafe.Pointer(&i.buf[0]))), + Ptr: pinnedAddress(&pin, &i.buf[0]), Nr: indexXattrBatch, } n, err := scoutfsctl(i.f, IOCREADXATTRINDEX, unsafe.Pointer(&query)) + pin.Unpin() if err != nil { return nil, err }