Initial commit

Proof-of-concept implementation. Bugs will occur.
This commit is contained in:
2026-02-12 01:18:46 +03:00
commit 13ac06c14b
553 changed files with 253003 additions and 0 deletions

60
vendor/github.com/hanwen/go-fuse/v2/internal/access.go generated vendored Normal file
View File

@@ -0,0 +1,60 @@
// Copyright 2019 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package internal
import (
"os/user"
"strconv"
)
// HasAccess tests if a caller can access a file with permissions
// `perm` in mode `mask`
func HasAccess(callerUid, callerGid, fileUid, fileGid uint32, perm uint32, mask uint32) bool {
if callerUid == 0 {
// root can do anything.
return true
}
mask = mask & 7
if mask == 0 {
return true
}
if callerUid == fileUid {
if perm&(mask<<6) != 0 {
return true
}
}
if callerGid == fileGid {
if perm&(mask<<3) != 0 {
return true
}
}
if perm&mask != 0 {
return true
}
// Check other groups.
if perm&(mask<<3) == 0 {
// avoid expensive lookup if it's not allowed anyway
return false
}
u, err := user.LookupId(strconv.Itoa(int(callerUid)))
if err != nil {
return false
}
gs, err := u.GroupIds()
if err != nil {
return false
}
fileGidStr := strconv.Itoa(int(fileGid))
for _, gidStr := range gs {
if gidStr == fileGidStr {
return true
}
}
return false
}

View File

@@ -0,0 +1,9 @@
package fallocate
// Fallocate is a wrapper around fallocate syscall.
// On Linux, it is a wrapper around fallocate(2).
// On Darwin, it is a wrapper around fnctl(2).
// On FreeBSD, it is a wrapper around posix_fallocate(2).
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
return fallocate(fd, mode, off, len)
}

View File

@@ -0,0 +1,56 @@
package fallocate
import (
"syscall"
"unsafe"
"golang.org/x/sys/unix"
)
func fallocate(fd int, mode uint32, off int64, len int64) error {
// TODO: Handle `mode` parameter.
_ = mode
// From `man fcntl` on OSX:
// The F_PREALLOCATE command operates on the following structure:
//
// typedef struct fstore {
// u_int32_t fst_flags; /* IN: flags word */
// int fst_posmode; /* IN: indicates offset field */
// off_t fst_offset; /* IN: start of the region */
// off_t fst_length; /* IN: size of the region */
// off_t fst_bytesalloc; /* OUT: number of bytes allocated */
// } fstore_t;
//
// The flags (fst_flags) for the F_PREALLOCATE command are as follows:
//
// F_ALLOCATECONTIG Allocate contiguous space.
//
// F_ALLOCATEALL Allocate all requested space or no space at all.
//
// The position modes (fst_posmode) for the F_PREALLOCATE command indicate how to use the offset field. The modes are as fol-
// lows:
//
// F_PEOFPOSMODE Allocate from the physical end of file.
//
// F_VOLPOSMODE Allocate from the volume offset.
k := struct {
Flags uint32 // u_int32_t
Posmode int64 // int
Offset int64 // off_t
Length int64 // off_t
Bytesalloc int64 // off_t
}{
0,
0,
int64(off),
int64(len),
0,
}
_, _, errno := unix.Syscall(syscall.SYS_FCNTL, uintptr(fd), uintptr(unix.F_PREALLOCATE), uintptr(unsafe.Pointer(&k)))
if errno != 0 {
return errno
}
return nil
}

View File

@@ -0,0 +1,15 @@
package fallocate
import (
"golang.org/x/sys/unix"
)
func fallocate(fd int, mode uint32, off int64, len int64) error {
// Ignore mode
_ = mode
ret, _, _ := unix.Syscall(unix.SYS_POSIX_FALLOCATE, uintptr(fd), uintptr(off), uintptr(len))
if ret != 0 {
return unix.Errno(ret)
}
return nil
}

View File

@@ -0,0 +1,7 @@
package fallocate
import "golang.org/x/sys/unix"
func fallocate(fd int, mode uint32, off int64, len int64) error {
return unix.Fallocate(fd, mode, off, len)
}

View File

@@ -0,0 +1,45 @@
package ioctl
// https://github.com/torvalds/linux/blob/master/include/uapi/asm-generic/ioctl.h
const (
NONE = 0x0
READ = 0x1
WRITE = 0x2
)
// The ioctl command. It encodes direction (read/write), argument size
// and a type/number, where type should be globally unique and number
// is unique to the driver.
type Command uint32
// New constructs an ioctl command. size should be less than 16kb.
func New(dir byte, typ byte, nr byte, size uintptr) Command {
if size >= (1 << 14) {
panic("invalid ioctl sizeof")
}
return Command(dir)<<(14+16) |
Command(size)<<16 |
Command(typ)<<8 |
Command(nr)
}
// Read returns true if the ioctl reads data
func (c Command) Read() bool {
return (c>>(14+16))&READ != 0
}
// Write returns true if the ioctl writes data
func (c Command) Write() bool {
return (c>>(14+16))&WRITE != 0
}
// Number returns the lower 8 bits of the command
func (c Command) Number() byte {
return byte(c & 0xff)
}
// Type returns the upper 8 bits of the command
func (c Command) Type() byte {
return byte((c >> 8) & 0xff)
}

View File

@@ -0,0 +1,35 @@
package openat
import (
"golang.org/x/sys/unix"
)
// OpenSymlinkAware is a symlink-aware syscall.Open replacement.
//
// What it does:
//
// 1. Open baseDir (usually an absolute path), following symlinks.
//
// The user may have set up the directory tree with symlinks,
// that's not neccessarily malicous, but a normal use case.
//
// 2. Open path (must be a relative path) within baseDir, rejecting symlinks with ELOOP.
//
// On Linux, it calls openat2(2) with RESOLVE_NO_SYMLINKS. This prevents following
// symlinks in any component of the path.
//
// On other platforms, it calls openat(2) with O_NOFOLLOW.
// TODO: This is insecure as O_NOFOLLOW only affects the final path component.
func OpenSymlinkAware(baseDir string, path string, flags int, mode uint32) (fd int, err error) {
// Passing an absolute path is a bug in the caller
if len(path) > 0 && path[0] == '/' {
return -1, unix.EINVAL
}
baseFd, err := unix.Open(baseDir, unix.O_RDONLY|unix.O_DIRECTORY|unix.O_CLOEXEC, 0)
if err != nil {
return -1, err
}
defer unix.Close(baseFd)
return openatNoSymlinks(baseFd, path, flags, mode)
}

View File

@@ -0,0 +1,20 @@
package openat
import (
"golang.org/x/sys/unix"
)
func openatNoSymlinks(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
how := unix.OpenHow{
// os/exec expects all fds to have O_CLOEXEC or it will leak them to subprocesses.
Flags: uint64(flags) | unix.O_CLOEXEC,
Mode: uint64(mode),
Resolve: unix.RESOLVE_NO_SYMLINKS,
}
fd, err = unix.Openat2(dirfd, path, &how)
if err != nil && err == unix.ENOSYS {
flags |= unix.O_NOFOLLOW | unix.O_CLOEXEC
fd, err = unix.Openat(dirfd, path, flags, mode)
}
return fd, err
}

View File

@@ -0,0 +1,15 @@
//go:build !linux
package openat
import (
"golang.org/x/sys/unix"
)
// TODO: This is insecure as O_NOFOLLOW only affects the final path component.
// See https://github.com/rfjakob/gocryptfs/issues/165 for how this could be handled.
func openatNoSymlinks(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
// os/exec expects all fds to have O_CLOEXEC or it will leak them to subprocesses.
flags |= unix.O_NOFOLLOW | unix.O_CLOEXEC
return unix.Openat(dirfd, path, flags, mode)
}

View File

@@ -0,0 +1,9 @@
package renameat
// Renameat is a wrapper around renameat syscall.
// On Linux, it is a wrapper around renameat2(2).
// On Darwin, it is a wrapper around renameatx_np(2).
// On FreeBSD, it is a wrapper around renameat(2).
func Renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return renameat(olddirfd, oldpath, newdirfd, newpath, flags)
}

View File

@@ -0,0 +1,38 @@
package renameat
import (
"syscall"
"unsafe"
)
const (
SYS_RENAMEATX_NP = 488
RENAME_SWAP = 0x2
RENAME_EXCHANGE = RENAME_SWAP
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) error {
oldpathCString, err := syscall.BytePtrFromString(oldpath)
if err != nil {
return err
}
newpathCString, err := syscall.BytePtrFromString(newpath)
if err != nil {
return err
}
_, _, errno := syscall.Syscall6(
SYS_RENAMEATX_NP,
uintptr(olddirfd),
uintptr(unsafe.Pointer(oldpathCString)),
uintptr(newdirfd),
uintptr(unsafe.Pointer(newpathCString)),
uintptr(flags),
0,
)
if errno != 0 {
return errno
}
return nil
}

View File

@@ -0,0 +1,17 @@
package renameat
import "golang.org/x/sys/unix"
const (
// Since FreeBSD does not currently privode renameat syscall
// beyond POSIX standard like Linux and Darwin do, we borrow
// the defination from Linux but reject these non-POSIX flags.
RENAME_EXCHANGE = (1 << 1)
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
if flags != 0 {
return unix.ENOSYS
}
return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
}

View File

@@ -0,0 +1,11 @@
package renameat
import "golang.org/x/sys/unix"
const (
RENAME_EXCHANGE = unix.RENAME_EXCHANGE
)
func renameat(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
return unix.Renameat2(olddirfd, oldpath, newdirfd, newpath, flags)
}

View File

@@ -0,0 +1,40 @@
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utimens
import (
"syscall"
"time"
"github.com/hanwen/go-fuse/v2/fuse"
)
// timeToTimeval converts time.Time to syscall.Timeval
func timeToTimeval(t *time.Time) syscall.Timeval {
// Note: This does not use syscall.NsecToTimespec because
// that does not work properly for times before 1970,
// see https://github.com/golang/go/issues/12777
var tv syscall.Timeval
tv.Usec = int32(t.Nanosecond() / 1000)
tv.Sec = t.Unix()
return tv
}
// Fill converts a and m to a syscall.Timeval slice that can be passed
// to syscall.Utimes. Missing values (if any) are taken from attr
func Fill(a *time.Time, m *time.Time, attr *fuse.Attr) []syscall.Timeval {
if a == nil {
a2 := time.Unix(int64(attr.Atime), int64(attr.Atimensec))
a = &a2
}
if m == nil {
m2 := time.Unix(int64(attr.Mtime), int64(attr.Mtimensec))
m = &m2
}
tv := make([]syscall.Timeval, 2)
tv[0] = timeToTimeval(a)
tv[1] = timeToTimeval(m)
return tv
}

View File

@@ -0,0 +1,7 @@
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package utimens
// placeholder file so this package exists on all platforms.

View File

@@ -0,0 +1,10 @@
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xattr
import "golang.org/x/sys/unix"
// ENOATTR indicates that an extended attribute was not present.
const ENOATTR = unix.ENODATA

View File

@@ -0,0 +1,11 @@
//go:build !linux
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xattr
import "golang.org/x/sys/unix"
const ENOATTR = unix.ENOATTR

View File

@@ -0,0 +1,9 @@
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xattr
func ParseAttrNames(buf []byte) [][]byte {
return parseAttrNames(buf)
}

View File

@@ -0,0 +1,20 @@
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xattr
// BSDs syscall use different convention of data buf retrieved
// through syscall `unix.Listxattr`.
// Ref: extattr_list_file(2)
func parseAttrNames(buf []byte) [][]byte {
var attrList [][]byte
for p := 0; p < len(buf); {
attrNameLen := int(buf[p])
p++
attrName := buf[p : p+attrNameLen]
attrList = append(attrList, attrName)
p += attrNameLen
}
return attrList
}

View File

@@ -0,0 +1,13 @@
//go:build !freebsd
// Copyright 2024 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package xattr
import "bytes"
func parseAttrNames(buf []byte) [][]byte {
return bytes.Split(buf, []byte{0})
}