1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
|
// Package syscallcompat wraps FreeBSD-specific syscalls
package syscallcompat
import (
"errors"
"fmt"
"time"
"golang.org/x/sys/unix"
"github.com/hanwen/go-fuse/v2/fuse"
)
const (
O_DIRECT = unix.O_DIRECT
// O_PATH is supported on FreeBSD, but is missing from the sys/unix package
// FreeBSD-15.0 /usr/src/sys/sys/fcntl.h:135
O_PATH = 0x00400000
// Only defined on Linux, but we can emulate the functionality on FreeBSD
// in Renameat2() below
RENAME_NOREPLACE = 0x1
RENAME_EXCHANGE = 0x2
RENAME_WHITEOUT = 0x4
// ENODATA is only defined on Linux, but FreeBSD provides ENOATTR
ENODATA = unix.ENOATTR
// On FreeBSD, we only have O_NOFOLLOW.
OpenatFlagNofollowSymlink = unix.O_NOFOLLOW
)
// EnospcPrealloc is supposed to preallocate ciphertext space without
// changing the file size. This guarantees that we don't run out of
// space while writing a ciphertext block (that would corrupt the block).
//
// The fallocate syscall isn't supported on FreeBSD with the same semantics
// as Linux, in particular the _FALLOC_FL_KEEP_SIZE mode isn't supported.
func EnospcPrealloc(fd int, off int64, len int64) (err error) {
return nil
}
// Fallocate wraps the posix_fallocate() syscall.
// Fallocate returns an error if mode is not 0
func Fallocate(fd int, mode uint32, off int64, len int64) (err error) {
if mode != 0 {
return errors.New("fallocate unsupported mode")
}
_, _, err = unix.Syscall(unix.SYS_POSIX_FALLOCATE, uintptr(fd), uintptr(off), uintptr(len))
return err
}
// Mknodat wraps the Mknodat syscall.
func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
return unix.Mknodat(dirfd, path, mode, uint64(dev))
}
// Dup3 wraps the Dup3 syscall. We want to use Dup3 rather than Dup2 because Dup2
// is not implemented on arm64.
func Dup3(oldfd int, newfd int, flags int) (err error) {
return unix.Dup3(oldfd, newfd, flags)
}
// FchmodatNofollow is like Fchmodat but never follows symlinks.
//
// This should be handled by the AT_SYMLINK_NOFOLLOW flag, but Linux
// does not implement it, so we have to perform an elaborate dance
// with O_PATH and /proc/self/fd.
//
// See also: Qemu implemented the same logic as fchmodat_nofollow():
// https://git.qemu.org/?p=qemu.git;a=blob;f=hw/9pfs/9p-local.c#l335
func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
// Open handle to the filename (but without opening the actual file).
// This succeeds even when we don't have read permissions to the file.
fd, err := unix.Openat(dirfd, path, unix.O_NOFOLLOW|O_PATH, 0)
if err != nil {
return err
}
defer unix.Close(fd)
// Now we can check the type without the risk of race-conditions.
// Return syscall.ELOOP if it is a symlink.
var st unix.Stat_t
err = unix.Fstat(fd, &st)
if err != nil {
return err
}
if st.Mode&unix.S_IFMT == unix.S_IFLNK {
return unix.ELOOP
}
// Change mode of the actual file. Fchmod does not work with O_PATH,
// but Chmod via /proc/self/fd works.
procPath := fmt.Sprintf("/proc/self/fd/%d", fd)
return unix.Chmod(procPath, mode)
}
// LsetxattrUser runs the Lsetxattr syscall in the context of a different user.
// This is useful when setting ACLs, as the result depends on the user running
// the operation (see fuse-xfstests generic/375).
//
// If `context` is nil, this function behaves like ordinary Lsetxattr.
func LsetxattrUser(path string, attr string, data []byte, flags int, context *fuse.Context) (err error) {
f := func() (int, error) {
err := unix.Lsetxattr(path, attr, data, flags)
return -1, err
}
_, err = asUser(f, context)
return err
}
func timesToTimespec(a *time.Time, m *time.Time) []unix.Timespec {
ts := make([]unix.Timespec, 2)
if a == nil {
ts[0] = unix.Timespec{Nsec: unix.UTIME_OMIT}
} else {
ts[0], _ = unix.TimeToTimespec(*a)
}
if m == nil {
ts[1] = unix.Timespec{Nsec: unix.UTIME_OMIT}
} else {
ts[1], _ = unix.TimeToTimespec(*m)
}
return ts
}
// FutimesNano syscall.
func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) {
ts := timesToTimespec(a, m)
// To avoid introducing a separate syscall wrapper for futimens()
// (as done in go-fuse, for example), we instead use the /proc/self/fd trick.
procPath := fmt.Sprintf("/proc/self/fd/%d", fd)
return unix.UtimesNanoAt(unix.AT_FDCWD, procPath, ts, 0)
}
// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks.
// Retries on EINTR.
func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) {
ts := timesToTimespec(a, m)
err = retryEINTR(func() error {
return unix.UtimesNanoAt(dirfd, path, ts, unix.AT_SYMLINK_NOFOLLOW)
})
return err
}
// Getdents syscall with "." and ".." filtered out.
func Getdents(fd int) ([]fuse.DirEntry, error) {
entries, _, err := emulateGetdents(fd)
return entries, err
}
// GetdentsSpecial calls the Getdents syscall,
// with normal entries and "." / ".." split into two slices.
func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.DirEntry, err error) {
return emulateGetdents(fd)
}
// Renameat2 does not exist on Darwin, so we have to wrap it here.
// Retries on EINTR.
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
if flags&(RENAME_NOREPLACE|RENAME_EXCHANGE) == RENAME_NOREPLACE|RENAME_EXCHANGE {
return unix.EINVAL
}
if flags&(RENAME_NOREPLACE|RENAME_EXCHANGE) == RENAME_NOREPLACE|RENAME_EXCHANGE {
return unix.EINVAL
}
if flags&RENAME_NOREPLACE != 0 {
var st unix.Stat_t
err = unix.Fstatat(newdirfd, newpath, &st, 0)
if err == nil {
// Assume newpath is an existing file if we can stat() it.
// On Linux, RENAME_NOREPLACE fails with EEXIST if newpath
// already exists.
return unix.EEXIST
}
}
if flags&RENAME_EXCHANGE != 0 {
// Note that on Linux, RENAME_EXCHANGE can handle oldpath and
// newpath of different file types (e.g. directory and
// symbolic link). On FreeBSD the file types must be the same.
var stold, stnew unix.Stat_t
err = unix.Fstatat(olddirfd, oldpath, &stold, 0)
if err != nil {
// Assume file does not exist if we can't stat() it.
// On Linux, RENAME_EXCHANGE requires both oldpath
// and newpath exist.
return unix.ENOENT
}
err = unix.Fstatat(newdirfd, newpath, &stnew, 0)
if err != nil {
// Assume file does not exist if we can't stat() it.
// On Linux, RENAME_EXCHANGE requires both oldpath
// and newpath exist.
return unix.ENOENT
}
}
if flags&RENAME_WHITEOUT != 0 {
return unix.EINVAL
}
return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
}
|