aboutsummaryrefslogtreecommitdiff
path: root/internal/syscallcompat/sys_darwin.go
blob: 075563f302a2b483bb907ebe1208eaa9a7c501f5 (plain)
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
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
package syscallcompat

import (
	"log"
	"path/filepath"
	"runtime"
	"syscall"
	"time"
	"unsafe"

	"golang.org/x/sys/unix"

	"github.com/hanwen/go-fuse/v2/fuse"
)

const (
	// O_DIRECT means oncached I/O on Linux. No direct equivalent on MacOS and defined
	// to zero there.
	O_DIRECT = 0

	// O_PATH is only defined on Linux
	O_PATH = 0

	// RENAME_NOREPLACE is only defined on Linux
	RENAME_NOREPLACE = 0

	// KAUTH_UID_NONE and KAUTH_GID_NONE are special values to
	// revert permissions to the process credentials.
	KAUTH_UID_NONE = ^uint32(0) - 100
	KAUTH_GID_NONE = ^uint32(0) - 100
)

// Unfortunately pthread_setugid_np does not have a syscall wrapper yet.
func pthread_setugid_np(uid uint32, gid uint32) (err error) {
	_, _, e1 := syscall.RawSyscall(syscall.SYS_SETTID, uintptr(uid), uintptr(gid), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

// Unfortunately fsetattrlist does not have a syscall wrapper yet.
func fsetattrlist(fd int, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
	_, _, e1 := syscall.Syscall6(syscall.SYS_FSETATTRLIST, uintptr(fd), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

// Setattrlist already has a syscall wrapper, but it is not exported.
func setattrlist(path *byte, list unsafe.Pointer, buf unsafe.Pointer, size uintptr, options int) (err error) {
	_, _, e1 := syscall.Syscall6(syscall.SYS_SETATTRLIST, uintptr(unsafe.Pointer(path)), uintptr(list), uintptr(buf), uintptr(size), uintptr(options), 0)
	if e1 != 0 {
		err = e1
	}
	return
}

// Sorry, fallocate is not available on OSX at all and
// fcntl F_PREALLOCATE is not accessible from Go.
// See https://github.com/rfjakob/gocryptfs/issues/18 if you want to help.
func EnospcPrealloc(fd int, off int64, len int64) error {
	return nil
}

// See above.
func Fallocate(fd int, mode uint32, off int64, len int64) error {
	return syscall.EOPNOTSUPP
}

// Dup3 is not available on Darwin, so we use Dup2 instead.
func Dup3(oldfd int, newfd int, flags int) (err error) {
	if flags != 0 {
		log.Panic("darwin does not support dup3 flags")
	}
	return syscall.Dup2(oldfd, newfd)
}

////////////////////////////////////////////////////////
//// Emulated Syscalls (see emulate.go) ////////////////
////////////////////////////////////////////////////////

func OpenatUser(dirfd int, path string, flags int, mode uint32, context *fuse.Context) (fd int, err error) {
	if context != nil {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
		if err != nil {
			return -1, err
		}
		defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
	}

	return Openat(dirfd, path, flags, mode)
}

func Mknodat(dirfd int, path string, mode uint32, dev int) (err error) {
	return emulateMknodat(dirfd, path, mode, dev)
}

func MknodatUser(dirfd int, path string, mode uint32, dev int, context *fuse.Context) (err error) {
	if context != nil {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
		if err != nil {
			return err
		}
		defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
	}

	return Mknodat(dirfd, path, mode, dev)
}

func FchmodatNofollow(dirfd int, path string, mode uint32) (err error) {
	return unix.Fchmodat(dirfd, path, mode, unix.AT_SYMLINK_NOFOLLOW)
}

func SymlinkatUser(oldpath string, newdirfd int, newpath string, context *fuse.Context) (err error) {
	if context != nil {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
		if err != nil {
			return err
		}
		defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
	}

	return unix.Symlinkat(oldpath, newdirfd, newpath)
}

func MkdiratUser(dirfd int, path string, mode uint32, context *fuse.Context) (err error) {
	if context != nil {
		runtime.LockOSThread()
		defer runtime.UnlockOSThread()

		err = pthread_setugid_np(context.Owner.Uid, context.Owner.Gid)
		if err != nil {
			return err
		}
		defer pthread_setugid_np(KAUTH_UID_NONE, KAUTH_GID_NONE)
	}

	return unix.Mkdirat(dirfd, path, mode)
}

type attrList struct {
	bitmapCount uint16
	_           uint16
	CommonAttr  uint32
	VolAttr     uint32
	DirAttr     uint32
	FileAttr    uint32
	Forkattr    uint32
}

func timesToAttrList(a *time.Time, m *time.Time) (attrList attrList, attributes [2]unix.Timespec) {
	attrList.bitmapCount = unix.ATTR_BIT_MAP_COUNT
	attrList.CommonAttr = 0
	i := 0
	if m != nil {
		attributes[i] = unix.Timespec(fuse.UtimeToTimespec(m))
		attrList.CommonAttr |= unix.ATTR_CMN_MODTIME
		i += 1
	}
	if a != nil {
		attributes[i] = unix.Timespec(fuse.UtimeToTimespec(a))
		attrList.CommonAttr |= unix.ATTR_CMN_ACCTIME
		i += 1
	}
	return attrList, attributes
}

// FutimesNano syscall.
func FutimesNano(fd int, a *time.Time, m *time.Time) (err error) {
	attrList, attributes := timesToAttrList(a, m)
	return fsetattrlist(fd, unsafe.Pointer(&attrList), unsafe.Pointer(&attributes),
		unsafe.Sizeof(attributes), 0)
}

// UtimesNanoAtNofollow is like UtimesNanoAt but never follows symlinks.
//
// Unfortunately we cannot use unix.UtimesNanoAt since it is broken and just
// ignores the provided 'dirfd'. In addition, it also lacks handling of 'nil'
// pointers (used to preserve one of both timestamps).
func UtimesNanoAtNofollow(dirfd int, path string, a *time.Time, m *time.Time) (err error) {
	if !filepath.IsAbs(path) {
		chdirMutex.Lock()
		defer chdirMutex.Unlock()
		var cwd int
		cwd, err = syscall.Open(".", syscall.O_RDONLY, 0)
		if err != nil {
			return err
		}
		defer syscall.Close(cwd)
		err = syscall.Fchdir(dirfd)
		if err != nil {
			return err
		}
		defer syscall.Fchdir(cwd)
	}

	_p0, err := syscall.BytePtrFromString(path)
	if err != nil {
		return err
	}

	attrList, attributes := timesToAttrList(a, m)
	return setattrlist(_p0, unsafe.Pointer(&attrList), unsafe.Pointer(&attributes),
		unsafe.Sizeof(attributes), unix.FSOPT_NOFOLLOW)
}

func Getdents(fd int) ([]fuse.DirEntry, error) {
	entries, _, err := emulateGetdents(fd)
	return entries, err
}

func GetdentsSpecial(fd int) (entries []fuse.DirEntry, entriesSpecial []fuse.DirEntry, err error) {
	return emulateGetdents(fd)
}

// Renameat2 does not exist on Darwin, so we call Renameat and ignore the flags.
func Renameat2(olddirfd int, oldpath string, newdirfd int, newpath string, flags uint) (err error) {
	return unix.Renameat(olddirfd, oldpath, newdirfd, newpath)
}