1// Copyright 2019 the Go-FUSE Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style 3// license that can be found in the LICENSE file. 4 5package fs 6 7import ( 8 "bytes" 9 "io/ioutil" 10 "os" 11 "reflect" 12 "sync" 13 "syscall" 14 "testing" 15 "time" 16 17 "github.com/hanwen/go-fuse/v2/fuse" 18 "github.com/hanwen/go-fuse/v2/internal/testutil" 19 "github.com/kylelemons/godebug/pretty" 20 "golang.org/x/sys/unix" 21) 22 23func TestRenameExchange(t *testing.T) { 24 tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) 25 defer tc.Clean() 26 27 if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil { 28 t.Fatalf("Mkdir: %v", err) 29 } 30 tc.writeOrig("file", "hello", 0644) 31 tc.writeOrig("dir/file", "x", 0644) 32 33 f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0) 34 if err != nil { 35 t.Fatalf("open 1: %v", err) 36 } 37 defer syscall.Close(f1) 38 f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0) 39 if err != nil { 40 t.Fatalf("open 2: %v", err) 41 } 42 defer syscall.Close(f2) 43 44 var before1, before2 unix.Stat_t 45 if err := unix.Fstatat(f1, "file", &before1, 0); err != nil { 46 t.Fatalf("Fstatat: %v", err) 47 } 48 if err := unix.Fstatat(f2, "file", &before2, 0); err != nil { 49 t.Fatalf("Fstatat: %v", err) 50 } 51 52 if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_EXCHANGE); err != nil { 53 t.Errorf("rename EXCHANGE: %v", err) 54 } 55 56 var after1, after2 unix.Stat_t 57 if err := unix.Fstatat(f1, "file", &after1, 0); err != nil { 58 t.Fatalf("Fstatat: %v", err) 59 } 60 if err := unix.Fstatat(f2, "file", &after2, 0); err != nil { 61 t.Fatalf("Fstatat: %v", err) 62 } 63 clearCtime := func(s *unix.Stat_t) { 64 s.Ctim.Sec = 0 65 s.Ctim.Nsec = 0 66 } 67 68 clearCtime(&after1) 69 clearCtime(&after2) 70 clearCtime(&before2) 71 clearCtime(&before1) 72 if diff := pretty.Compare(after1, before2); diff != "" { 73 t.Errorf("after1, before2: %s", diff) 74 } 75 if !reflect.DeepEqual(after2, before1) { 76 t.Errorf("after2, before1: %#v, %#v", after2, before1) 77 } 78} 79 80func TestRenameNoOverwrite(t *testing.T) { 81 tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) 82 defer tc.Clean() 83 84 if err := os.Mkdir(tc.origDir+"/dir", 0755); err != nil { 85 t.Fatalf("Mkdir: %v", err) 86 } 87 tc.writeOrig("file", "hello", 0644) 88 tc.writeOrig("dir/file", "x", 0644) 89 90 f1, err := syscall.Open(tc.mntDir+"/", syscall.O_DIRECTORY, 0) 91 if err != nil { 92 t.Fatalf("open 1: %v", err) 93 } 94 defer syscall.Close(f1) 95 f2, err := syscall.Open(tc.mntDir+"/dir", syscall.O_DIRECTORY, 0) 96 if err != nil { 97 t.Fatalf("open 2: %v", err) 98 } 99 defer syscall.Close(f2) 100 101 if err := unix.Renameat2(f1, "file", f2, "file", unix.RENAME_NOREPLACE); err == nil { 102 t.Errorf("rename NOREPLACE succeeded") 103 } else if err != syscall.EEXIST { 104 t.Errorf("got %v (%T) want EEXIST", err, err) 105 } 106} 107 108func TestXAttr(t *testing.T) { 109 tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) 110 defer tc.Clean() 111 112 tc.writeOrig("file", "", 0644) 113 114 buf := make([]byte, 1024) 115 attr := "user.xattrtest" 116 if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err == syscall.ENOTSUP { 117 t.Skip("$TMP does not support xattrs. Rerun this test with a $TMPDIR override") 118 } 119 120 if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA { 121 t.Fatalf("got %v want ENOATTR", err) 122 } 123 value := []byte("value") 124 if err := syscall.Setxattr(tc.mntDir+"/file", attr, value, 0); err != nil { 125 t.Fatalf("Setxattr: %v", err) 126 } 127 128 sz, err := syscall.Listxattr(tc.mntDir+"/file", nil) 129 if err != nil { 130 t.Fatalf("Listxattr: %v", err) 131 } 132 buf = make([]byte, sz) 133 if _, err := syscall.Listxattr(tc.mntDir+"/file", buf); err != nil { 134 t.Fatalf("Listxattr: %v", err) 135 } else { 136 attributes := bytes.Split(buf[:sz], []byte{0}) 137 found := false 138 for _, a := range attributes { 139 if string(a) == attr { 140 found = true 141 break 142 } 143 } 144 145 if !found { 146 t.Fatalf("Listxattr: %q (not found: %q", buf[:sz], attr) 147 } 148 } 149 150 sz, err = syscall.Getxattr(tc.mntDir+"/file", attr, buf) 151 if err != nil { 152 t.Fatalf("Getxattr: %v", err) 153 } 154 if bytes.Compare(buf[:sz], value) != 0 { 155 t.Fatalf("Getxattr got %q want %q", buf[:sz], value) 156 } 157 if err := syscall.Removexattr(tc.mntDir+"/file", attr); err != nil { 158 t.Fatalf("Removexattr: %v", err) 159 } 160 161 if _, err := syscall.Getxattr(tc.mntDir+"/file", attr, buf); err != syscall.ENODATA { 162 t.Fatalf("got %v want ENOATTR", err) 163 } 164} 165 166// TestXAttrSymlink verifies that we did not forget to use Lgetxattr instead 167// of Getxattr. This test is Linux-specific because it depends on the behavoir 168// of the `security` namespace. 169// 170// On Linux, symlinks can not have xattrs in the `user` namespace, so we 171// try to read something from `security`. Writing would need root rights, 172// so don't even bother. See `man 7 xattr` for more info. 173func TestXAttrSymlink(t *testing.T) { 174 tc := newTestCase(t, nil) 175 defer tc.Clean() 176 177 path := tc.mntDir + "/symlink" 178 if err := syscall.Symlink("target/does/not/exist", path); err != nil { 179 t.Fatal(err) 180 } 181 buf := make([]byte, 10) 182 _, err := unix.Lgetxattr(path, "security.foo", buf) 183 if err != unix.ENODATA { 184 t.Errorf("want %d=ENODATA, got error %d=%q instead", unix.ENODATA, err, err) 185 } 186} 187 188func TestCopyFileRange(t *testing.T) { 189 tc := newTestCase(t, &testOptions{attrCache: true, entryCache: true}) 190 defer tc.Clean() 191 192 if !tc.server.KernelSettings().SupportsVersion(7, 28) { 193 t.Skip("need v7.28 for CopyFileRange") 194 } 195 196 tc.writeOrig("src", "01234567890123456789", 0644) 197 tc.writeOrig("dst", "abcdefghijabcdefghij", 0644) 198 199 f1, err := syscall.Open(tc.mntDir+"/src", syscall.O_RDONLY, 0) 200 if err != nil { 201 t.Fatalf("Open src: %v", err) 202 } 203 defer func() { 204 // syscall.Close() is treacherous; because fds are 205 // reused, a double close can cause serious havoc 206 if f1 > 0 { 207 syscall.Close(f1) 208 } 209 }() 210 211 f2, err := syscall.Open(tc.mntDir+"/dst", syscall.O_RDWR, 0) 212 if err != nil { 213 t.Fatalf("Open dst: %v", err) 214 } 215 defer func() { 216 if f2 > 0 { 217 defer syscall.Close(f2) 218 } 219 }() 220 221 srcOff := int64(5) 222 dstOff := int64(7) 223 if sz, err := unix.CopyFileRange(f1, &srcOff, f2, &dstOff, 3, 0); err != nil || sz != 3 { 224 t.Fatalf("CopyFileRange: %d,%v", sz, err) 225 } 226 227 err = syscall.Close(f1) 228 f1 = 0 229 if err != nil { 230 t.Fatalf("Close src: %v", err) 231 } 232 233 err = syscall.Close(f2) 234 f2 = 0 235 if err != nil { 236 t.Fatalf("Close dst: %v", err) 237 } 238 c, err := ioutil.ReadFile(tc.mntDir + "/dst") 239 if err != nil { 240 t.Fatalf("ReadFile: %v", err) 241 } 242 243 want := "abcdefg567abcdefghij" 244 got := string(c) 245 if got != want { 246 t.Errorf("got %q want %q", got, want) 247 } 248 249} 250 251// Wait for a change in /proc/self/mounts. Efficient through the use of 252// unix.Poll(). 253func waitProcMountsChange() error { 254 fd, err := syscall.Open("/proc/self/mounts", syscall.O_RDONLY, 0) 255 defer syscall.Close(fd) 256 if err != nil { 257 return err 258 } 259 pollFds := []unix.PollFd{ 260 { 261 Fd: int32(fd), 262 Events: unix.POLLPRI, 263 }, 264 } 265 _, err = unix.Poll(pollFds, 1000) 266 return err 267} 268 269// Wait until mountpoint "mnt" shows up /proc/self/mounts 270func waitMount(mnt string) error { 271 for { 272 err := waitProcMountsChange() 273 if err != nil { 274 return err 275 } 276 content, err := ioutil.ReadFile("/proc/self/mounts") 277 if err != nil { 278 return err 279 } 280 if bytes.Contains(content, []byte(mnt)) { 281 return nil 282 } 283 } 284} 285 286// There is a hang that appears when enabling CAP_PARALLEL_DIROPS on Linux 287// 4.15.0: https://github.com/hanwen/go-fuse/issues/281 288// The hang was originally triggered by gvfs-udisks2-volume-monitor. This 289// test emulates what gvfs-udisks2-volume-monitor does. 290func TestParallelDiropsHang(t *testing.T) { 291 // We do NOT want to use newTestCase() here because we need to know the 292 // mnt path before the filesystem is mounted 293 dir := testutil.TempDir() 294 orig := dir + "/orig" 295 mnt := dir + "/mnt" 296 if err := os.Mkdir(orig, 0755); err != nil { 297 t.Fatal(err) 298 } 299 if err := os.Mkdir(mnt, 0755); err != nil { 300 t.Fatal(err) 301 } 302 defer os.RemoveAll(dir) 303 304 // Unblock the goroutines onces the mount shows up in /proc/self/mounts 305 wait := make(chan struct{}) 306 go func() { 307 err := waitMount(mnt) 308 if err != nil { 309 t.Error(err) 310 } 311 // Unblock the goroutines regardless of an error. We don't want to hang 312 // the test. 313 close(wait) 314 }() 315 316 // gvfs-udisks2-volume-monitor hits the mount with three threads - we try to 317 // emulate exactly what it does acc. to an strace log. 318 var wg sync.WaitGroup 319 wg.Add(3) 320 // [pid 2117] lstat(".../mnt/autorun.inf", <unfinished ...> 321 go func() { 322 defer wg.Done() 323 <-wait 324 var st unix.Stat_t 325 unix.Lstat(mnt+"/autorun.inf", &st) 326 }() 327 // [pid 2116] open(".../mnt/.xdg-volume-info", O_RDONLY <unfinished ...> 328 go func() { 329 defer wg.Done() 330 <-wait 331 syscall.Open(mnt+"/.xdg-volume-info", syscall.O_RDONLY, 0) 332 }() 333 // 25 times this: 334 // [pid 1874] open(".../mnt", O_RDONLY|O_NONBLOCK|O_DIRECTORY|O_CLOEXEC <unfinished ...> 335 // [pid 1874] fstat(11, {st_mode=S_IFDIR|0775, st_size=4096, ...}) = 0 336 // [pid 1874] getdents(11, /* 2 entries */, 32768) = 48 337 // [pid 1874] close(11) = 0 338 go func() { 339 defer wg.Done() 340 <-wait 341 for i := 1; i <= 25; i++ { 342 f, err := os.Open(mnt) 343 if err != nil { 344 t.Error(err) 345 return 346 } 347 _, err = f.Stat() 348 if err != nil { 349 t.Error(err) 350 f.Close() 351 return 352 } 353 _, err = f.Readdirnames(-1) 354 if err != nil { 355 t.Errorf("iteration %d: fd %d: %v", i, f.Fd(), err) 356 return 357 } 358 f.Close() 359 } 360 }() 361 362 loopbackRoot, err := NewLoopbackRoot(orig) 363 if err != nil { 364 t.Fatalf("NewLoopbackRoot(%s): %v\n", orig, err) 365 } 366 sec := time.Second 367 opts := &Options{ 368 AttrTimeout: &sec, 369 EntryTimeout: &sec, 370 } 371 opts.Debug = testutil.VerboseTest() 372 373 rawFS := NewNodeFS(loopbackRoot, opts) 374 server, err := fuse.NewServer(rawFS, mnt, &opts.MountOptions) 375 if err != nil { 376 t.Fatal(err) 377 } 378 go server.Serve() 379 380 wg.Wait() 381 server.Unmount() 382} 383 384func TestRoMount(t *testing.T) { 385 tc := newTestCase(t, &testOptions{ro: true}) 386 defer tc.Clean() 387} 388