unionfs_test.go 21.7 KB
Newer Older
1 2 3
package unionfs

import (
4
	"exec"
5 6
	"os"
	"github.com/hanwen/go-fuse/fuse"
7
	"io/ioutil"
8 9
	"fmt"
	"log"
10
	"path/filepath"
11
	"syscall"
12
	"testing"
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
13
	"time"
14 15 16 17
)

var _ = fmt.Print
var _ = log.Print
18

19 20 21 22 23 24 25 26
var CheckSuccess = fuse.CheckSuccess

func TestFilePathHash(t *testing.T) {
	// Simple test coverage.
	t.Log(filePathHash("xyz/abc"))
}

var testOpts = UnionFsOptions{
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
27
	DeletionCacheTTLSecs: entryTtl,
28
	DeletionDirName:      "DELETIONS",
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
29
	BranchCacheTTLSecs:   entryTtl,
30 31
}

32
func setupUfs(t *testing.T) (workdir string, cleanup func()) {
33 34 35 36 37 38 39 40 41 42
	wd := fuse.MakeTempDir()
	err := os.Mkdir(wd+"/mount", 0700)
	fuse.CheckSuccess(err)

	err = os.Mkdir(wd+"/rw", 0700)
	fuse.CheckSuccess(err)

	os.Mkdir(wd+"/ro", 0700)
	fuse.CheckSuccess(err)

43 44
	var fses []fuse.FileSystem
	fses = append(fses, fuse.NewLoopbackFileSystem(wd+"/rw"))
45 46
	fses = append(fses,
		NewCachingFileSystem(fuse.NewLoopbackFileSystem(wd+"/ro"), 0))
47
	ufs := NewUnionFs(fses, testOpts)
48

49 50
	// We configure timeouts are smaller, so we can check for
	// UnionFs's cache consistency.
51
	opts := &fuse.FileSystemOptions{
52 53 54
		EntryTimeout:    .5 * entryTtl,
		AttrTimeout:     .5 * entryTtl,
		NegativeTimeout: .5 * entryTtl,
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
55
	}
56

57
	state, conn, err := fuse.MountPathFileSystem(wd+"/mount", ufs, opts)
58
	CheckSuccess(err)
59
	conn.Debug = true
60
	state.Debug = true
61 62
	go state.Loop(false)

63 64 65 66
	return wd, func() {
		state.Unmount()
		os.RemoveAll(wd)
	}
67 68
}

69 70 71
func writeToFile(path string, contents string) {
	err := ioutil.WriteFile(path, []byte(contents), 0644)
	CheckSuccess(err)
72 73 74
}

func readFromFile(path string) string {
75
	b, err := ioutil.ReadFile(path)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
76
	fmt.Println(b)
77 78
	CheckSuccess(err)
	return string(b)
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
}

func dirNames(path string) map[string]bool {
	f, err := os.Open(path)
	fuse.CheckSuccess(err)

	result := make(map[string]bool)
	names, err := f.Readdirnames(-1)
	fuse.CheckSuccess(err)
	err = f.Close()
	CheckSuccess(err)

	for _, nm := range names {
		result[nm] = true
	}
	return result
}

func checkMapEq(t *testing.T, m1, m2 map[string]bool) {
	if !mapEq(m1, m2) {
		msg := fmt.Sprintf("mismatch: got %v != expect %v", m1, m2)
		log.Print(msg)
		t.Error(msg)
	}
}

func mapEq(m1, m2 map[string]bool) bool {
	if len(m1) != len(m2) {
		return false
	}

	for k, v := range m1 {
111
		val, ok := m2[k]
112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
		if !ok || val != v {
			return false
		}
	}
	return true
}

func fileExists(path string) bool {
	f, err := os.Lstat(path)
	return err == nil && f != nil
}

func remove(path string) {
	err := os.Remove(path)
	fuse.CheckSuccess(err)
}

129 130 131 132
func TestAutocreateDeletionDir(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
133
	err := os.Remove(wd + "/rw/DELETIONS")
134 135 136 137 138
	CheckSuccess(err)

	err = os.Mkdir(wd+"/mount/dir", 0755)
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
139
	_, err = ioutil.ReadDir(wd + "/mount/dir")
140 141 142
	CheckSuccess(err)
}

143
func TestSymlink(t *testing.T) {
144 145
	wd, clean := setupUfs(t)
	defer clean()
146 147 148 149 150 151 152 153 154 155 156 157

	err := os.Symlink("/foobar", wd+"/mount/link")
	CheckSuccess(err)

	val, err := os.Readlink(wd + "/mount/link")
	CheckSuccess(err)

	if val != "/foobar" {
		t.Errorf("symlink mismatch: %v", val)
	}
}

158 159 160 161
func TestSymlinkPromote(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
162
	err := os.Mkdir(wd+"/ro/subdir", 0755)
163
	CheckSuccess(err)
164

165 166 167 168
	err = os.Symlink("/foobar", wd+"/mount/subdir/link")
	CheckSuccess(err)
}

169
func TestChtimes(t *testing.T) {
170 171
	wd, clean := setupUfs(t)
	defer clean()
172

173
	writeToFile(wd+"/ro/file", "a")
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
174
	err := os.Chtimes(wd+"/ro/file", 42e9, 43e9)
175 176
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
177
	err = os.Chtimes(wd+"/mount/file", 82e9, 83e9)
178 179
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
180
	fi, err := os.Lstat(wd + "/mount/file")
181
	if fi.Atime_ns != 82e9 || fi.Mtime_ns != 83e9 {
182 183 184 185
		t.Error("Incorrect timestamp", fi)
	}
}

186
func TestChmod(t *testing.T) {
187 188
	wd, clean := setupUfs(t)
	defer clean()
189

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
190 191
	ro_fn := wd + "/ro/file"
	m_fn := wd + "/mount/file"
192
	writeToFile(ro_fn, "a")
193 194 195 196 197
	err := os.Chmod(m_fn, 07070)
	CheckSuccess(err)

	fi, err := os.Lstat(m_fn)
	CheckSuccess(err)
198
	if fi.Mode&07777 != 07272 {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
199
		t.Errorf("Unexpected mode found: %o", fi.Mode)
200
	}
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
201
	_, err = os.Lstat(wd + "/rw/file")
202 203 204 205 206
	if err != nil {
		t.Errorf("File not promoted")
	}
}

207 208 209 210 211 212 213 214 215 216 217 218 219 220 221
func TestChown(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

	ro_fn := wd + "/ro/file"
	m_fn := wd + "/mount/file"
	writeToFile(ro_fn, "a")

	err := os.Chown(m_fn, 0, 0)
	code := fuse.OsErrorToErrno(err)
	if code != fuse.EPERM {
		t.Error("Unexpected error code", code, err)
	}
}

222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
func TestDelete(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

	writeToFile(wd+"/ro/file", "a")
	_, err := os.Lstat(wd + "/mount/file")
	CheckSuccess(err)

	err = os.Remove(wd + "/mount/file")
	CheckSuccess(err)

	_, err = os.Lstat(wd + "/mount/file")
	if err == nil {
		t.Fatal("should have disappeared.")
	}
	delPath := wd + "/rw/" + testOpts.DeletionDirName
	names := dirNames(delPath)
	if len(names) != 1 {
		t.Fatal("Should have 1 deletion", names)
	}

	for k, _ := range names {
		c, err := ioutil.ReadFile(delPath + "/" + k)
		CheckSuccess(err)
		if string(c) != "file" {
247
			t.Fatal("content mismatch", string(c))
248 249 250 251
		}
	}
}

252
func TestBasic(t *testing.T) {
253 254
	wd, clean := setupUfs(t)
	defer clean()
255

256 257 258
	writeToFile(wd+"/rw/rw", "a")
	writeToFile(wd+"/ro/ro1", "a")
	writeToFile(wd+"/ro/ro2", "b")
259 260 261 262 263 264 265

	names := dirNames(wd + "/mount")
	expected := map[string]bool{
		"rw": true, "ro1": true, "ro2": true,
	}
	checkMapEq(t, names, expected)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
266
	log.Println("new contents")
267
	writeToFile(wd+"/mount/new", "new contents")
268 269 270 271
	if !fileExists(wd + "/rw/new") {
		t.Errorf("missing file in rw layer", names)
	}

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
272
	contents := readFromFile(wd + "/mount/new")
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
273 274
	if contents != "new contents" {
		t.Errorf("read mismatch: '%v'", contents)
275
	}
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
276
	return
277
	writeToFile(wd+"/mount/ro1", "promote me")
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313
	if !fileExists(wd + "/rw/ro1") {
		t.Errorf("missing file in rw layer", names)
	}

	remove(wd + "/mount/new")
	names = dirNames(wd + "/mount")
	checkMapEq(t, names, map[string]bool{
		"rw": true, "ro1": true, "ro2": true,
	})

	names = dirNames(wd + "/rw")
	checkMapEq(t, names, map[string]bool{
		testOpts.DeletionDirName: true,
		"rw":                     true, "ro1": true,
	})
	names = dirNames(wd + "/rw/" + testOpts.DeletionDirName)
	if len(names) != 0 {
		t.Errorf("Expected 0 entry in %v", names)
	}

	remove(wd + "/mount/ro1")
	names = dirNames(wd + "/mount")
	checkMapEq(t, names, map[string]bool{
		"rw": true, "ro2": true,
	})

	names = dirNames(wd + "/rw")
	checkMapEq(t, names, map[string]bool{
		"rw": true, testOpts.DeletionDirName: true,
	})

	names = dirNames(wd + "/rw/" + testOpts.DeletionDirName)
	if len(names) != 1 {
		t.Errorf("Expected 1 entry in %v", names)
	}
}
314 315

func TestPromote(t *testing.T) {
316 317
	wd, clean := setupUfs(t)
	defer clean()
318

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
319
	err := os.Mkdir(wd+"/ro/subdir", 0755)
320
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
321 322
	writeToFile(wd+"/ro/subdir/file", "content")
	writeToFile(wd+"/mount/subdir/file", "other-content")
323
}
324

325
func TestCreate(t *testing.T) {
326 327
	wd, clean := setupUfs(t)
	defer clean()
328

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
329
	err := os.MkdirAll(wd+"/ro/subdir/sub2", 0755)
330
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
331
	writeToFile(wd+"/mount/subdir/sub2/file", "other-content")
332 333 334 335
	_, err = os.Lstat(wd + "/mount/subdir/sub2/file")
	CheckSuccess(err)
}

336
func TestOpenUndeletes(t *testing.T) {
337 338
	wd, clean := setupUfs(t)
	defer clean()
339

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
340
	writeToFile(wd+"/ro/file", "X")
341 342
	err := os.Remove(wd + "/mount/file")
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
343
	writeToFile(wd+"/mount/file", "X")
344 345 346
	_, err = os.Lstat(wd + "/mount/file")
	CheckSuccess(err)
}
347

348
func TestMkdir(t *testing.T) {
349 350
	wd, clean := setupUfs(t)
	defer clean()
351 352 353 354

	dirname := wd + "/mount/subdir"
	err := os.Mkdir(dirname, 0755)
	CheckSuccess(err)
355

356 357 358 359
	err = os.Remove(dirname)
	CheckSuccess(err)
}

360
func TestMkdirPromote(t *testing.T) {
361 362
	wd, clean := setupUfs(t)
	defer clean()
363 364 365 366 367 368 369

	dirname := wd + "/ro/subdir/subdir2"
	err := os.MkdirAll(dirname, 0755)
	CheckSuccess(err)

	err = os.Mkdir(wd+"/mount/subdir/subdir2/dir3", 0755)
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
370
	fi, _ := os.Lstat(wd + "/rw/subdir/subdir2/dir3")
371 372 373 374 375
	CheckSuccess(err)
	if fi == nil || !fi.IsDirectory() {
		t.Error("is not a directory: ", fi)
	}
}
376

377 378 379 380
func TestRmdirMkdir(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
381
	err := os.Mkdir(wd+"/ro/subdir", 0755)
382 383 384 385 386 387 388 389 390 391
	CheckSuccess(err)

	dirname := wd + "/mount/subdir"
	err = os.Remove(dirname)
	CheckSuccess(err)

	err = os.Mkdir(dirname, 0755)
	CheckSuccess(err)
}

392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411
func TestRename(t *testing.T) {
	type Config struct {
		f1_ro bool
		f1_rw bool
		f2_ro bool
		f2_rw bool
	}

	configs := make([]Config, 0)
	for i := 0; i < 16; i++ {
		c := Config{i&0x1 != 0, i&0x2 != 0, i&0x4 != 0, i&0x8 != 0}
		if !(c.f1_ro || c.f1_rw) {
			continue
		}

		configs = append(configs, c)
	}

	for i, c := range configs {
		t.Log("Config", i, c)
412
		wd, clean := setupUfs(t)
413
		if c.f1_ro {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
414
			writeToFile(wd+"/ro/file1", "c1")
415 416
		}
		if c.f1_rw {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
417
			writeToFile(wd+"/rw/file1", "c2")
418 419
		}
		if c.f2_ro {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
420
			writeToFile(wd+"/ro/file2", "c3")
421 422
		}
		if c.f2_rw {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
423
			writeToFile(wd+"/rw/file2", "c4")
424 425
		}

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
426
		err := os.Rename(wd+"/mount/file1", wd+"/mount/file2")
427 428 429 430 431 432 433 434 435
		CheckSuccess(err)

		_, err = os.Lstat(wd + "/mount/file1")
		if err == nil {
			t.Errorf("Should have lost file1")
		}
		_, err = os.Lstat(wd + "/mount/file2")
		CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
436
		err = os.Rename(wd+"/mount/file2", wd+"/mount/file1")
437 438 439 440 441 442 443 444 445
		CheckSuccess(err)

		_, err = os.Lstat(wd + "/mount/file2")
		if err == nil {
			t.Errorf("Should have lost file2")
		}
		_, err = os.Lstat(wd + "/mount/file1")
		CheckSuccess(err)

446
		clean()
447 448 449
	}
}

450
func TestRenameDirBasic(t *testing.T) {
451 452 453
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
454
	err := os.MkdirAll(wd+"/ro/dir/subdir", 0755)
455 456
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
457
	err = os.Rename(wd+"/mount/dir", wd+"/mount/renamed")
458 459 460 461 462 463 464 465 466 467 468 469 470 471
	CheckSuccess(err)

	if fi, _ := os.Lstat(wd + "/mount/dir"); fi != nil {
		t.Fatalf("%s/mount/dir should have disappeared: %v", wd, fi)
	}

	if fi, _ := os.Lstat(wd + "/mount/renamed"); fi == nil || !fi.IsDirectory() {
		t.Fatalf("%s/mount/renamed should be directory: %v", wd, fi)
	}

	entries, err := ioutil.ReadDir(wd + "/mount/renamed")
	if err != nil || len(entries) != 1 || entries[0].Name != "subdir" {
		t.Errorf("readdir(%s/mount/renamed) should have one entry: %v, err %v", wd, entries, err)
	}
472

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
473
	if err = os.Mkdir(wd+"/mount/dir", 0755); err != nil {
474 475
		t.Errorf("mkdir should succeed %v", err)
	}
476 477 478 479 480 481
}

func TestRenameDirWithDeletions(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
482
	err := os.MkdirAll(wd+"/ro/dir/subdir", 0755)
483 484
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
485
	err = ioutil.WriteFile(wd+"/ro/dir/file.txt", []byte{42}, 0644)
486 487
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
488
	err = ioutil.WriteFile(wd+"/ro/dir/subdir/file.txt", []byte{42}, 0644)
489 490 491 492 493 494 495 496
	CheckSuccess(err)

	if fi, _ := os.Lstat(wd + "/mount/dir/subdir/file.txt"); fi == nil || !fi.IsRegular() {
		t.Fatalf("%s/mount/dir/subdir/file.txt should be file: %v", wd, fi)
	}

	err = os.Remove(wd + "/mount/dir/file.txt")
	CheckSuccess(err)
497

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
498
	err = os.Rename(wd+"/mount/dir", wd+"/mount/renamed")
499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515
	CheckSuccess(err)

	if fi, _ := os.Lstat(wd + "/mount/dir/subdir/file.txt"); fi != nil {
		t.Fatalf("%s/mount/dir/subdir/file.txt should have disappeared: %v", wd, fi)
	}

	if fi, _ := os.Lstat(wd + "/mount/dir"); fi != nil {
		t.Fatalf("%s/mount/dir should have disappeared: %v", wd, fi)
	}

	if fi, _ := os.Lstat(wd + "/mount/renamed"); fi == nil || !fi.IsDirectory() {
		t.Fatalf("%s/mount/renamed should be directory: %v", wd, fi)
	}

	if fi, _ := os.Lstat(wd + "/mount/renamed/file.txt"); fi != nil {
		t.Fatalf("%s/mount/renamed/file.txt should have disappeared %#v", wd, fi)
	}
516

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
517
	if err = os.Mkdir(wd+"/mount/dir", 0755); err != nil {
518 519 520 521 522 523
		t.Errorf("mkdir should succeed %v", err)
	}

	if fi, _ := os.Lstat(wd + "/mount/dir/subdir"); fi != nil {
		t.Fatalf("%s/mount/dir/subdir should have disappeared %#v", wd, fi)
	}
524 525 526 527 528 529
}

func TestRenameSymlink(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
530
	err := os.Symlink("linktarget", wd+"/ro/link")
531 532
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
533
	err = os.Rename(wd+"/mount/link", wd+"/mount/renamed")
534 535 536 537 538 539 540 541 542 543 544 545 546 547 548
	CheckSuccess(err)

	if fi, _ := os.Lstat(wd + "/mount/link"); fi != nil {
		t.Fatalf("%s/mount/link should have disappeared: %v", wd, fi)
	}

	if fi, _ := os.Lstat(wd + "/mount/renamed"); fi == nil || !fi.IsSymlink() {
		t.Fatalf("%s/mount/renamed should be link: %v", wd, fi)
	}

	if link, err := os.Readlink(wd + "/mount/renamed"); err != nil || link != "linktarget" {
		t.Fatalf("readlink(%s/mount/renamed) should point to 'linktarget': %v, err %v", wd, link, err)
	}
}

549 550
func TestWritableDir(t *testing.T) {
	t.Log("TestWritableDir")
551 552
	wd, clean := setupUfs(t)
	defer clean()
553 554 555 556 557

	dirname := wd + "/ro/subdir"
	err := os.Mkdir(dirname, 0555)
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
558
	fi, err := os.Lstat(wd + "/mount/subdir")
559
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
560
	if fi.Permission()&0222 == 0 {
561 562 563
		t.Errorf("unexpected permission %o", fi.Permission())
	}
}
564

565 566 567 568 569 570 571 572 573 574
func TestWriteAccess(t *testing.T) {
	t.Log("TestWriteAccess")
	wd, clean := setupUfs(t)
	defer clean()

	fn := wd + "/ro/file"
	// No write perms.
	err := ioutil.WriteFile(fn, []byte("foo"), 0444)
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
575
	errno := syscall.Access(wd+"/mount/file", fuse.W_OK)
576 577 578 579 580 581
	if errno != 0 {
		err = os.Errno(errno)
		CheckSuccess(err)
	}
}

582 583 584 585 586 587 588 589 590
func TestLink(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

	content := "blabla"
	fn := wd + "/ro/file"
	err := ioutil.WriteFile(fn, []byte(content), 0666)
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
591
	err = os.Link(wd+"/mount/file", wd+"/mount/linked")
592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607
	CheckSuccess(err)

	fi2, err := os.Lstat(wd + "/mount/linked")
	CheckSuccess(err)

	fi1, err := os.Lstat(wd + "/mount/file")
	CheckSuccess(err)
	if fi1.Ino != fi2.Ino {
		t.Errorf("inode numbers should be equal for linked files %v, %v", fi1.Ino, fi2.Ino)
	}
	c, err := ioutil.ReadFile(wd + "/mount/linked")
	if string(c) != content {
		t.Errorf("content mismatch got %q want %q", string(c), content)
	}
}

608 609
func TestTruncate(t *testing.T) {
	t.Log("TestTruncate")
610 611
	wd, clean := setupUfs(t)
	defer clean()
612 613 614

	writeToFile(wd+"/ro/file", "hello")
	os.Truncate(wd+"/mount/file", 2)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
615
	content := readFromFile(wd + "/mount/file")
616 617 618
	if content != "he" {
		t.Errorf("unexpected content %v", content)
	}
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
619
	content2 := readFromFile(wd + "/rw/file")
620 621 622 623
	if content2 != content {
		t.Errorf("unexpected rw content %v", content2)
	}
}
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
624 625 626

func TestCopyChmod(t *testing.T) {
	t.Log("TestCopyChmod")
627 628
	wd, clean := setupUfs(t)
	defer clean()
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
629 630 631 632 633 634 635 636 637 638 639

	contents := "hello"
	fn := wd + "/mount/y"
	err := ioutil.WriteFile(fn, []byte(contents), 0644)
	CheckSuccess(err)

	err = os.Chmod(fn, 0755)
	CheckSuccess(err)

	fi, err := os.Lstat(fn)
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
640
	if fi.Mode&0111 == 0 {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
641 642 643 644 645
		t.Errorf("1st attr error %o", fi.Mode)
	}
	time.Sleep(entryTtl * 1.1e9)
	fi, err = os.Lstat(fn)
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
646
	if fi.Mode&0111 == 0 {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
647 648 649
		t.Errorf("uncached attr error %o", fi.Mode)
	}
}
650 651 652 653

func abs(dt int64) int64 {
	if dt >= 0 {
		return dt
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
654
	}
655 656 657 658 659
	return -dt
}

func TestTruncateTimestamp(t *testing.T) {
	t.Log("TestTruncateTimestamp")
660 661
	wd, clean := setupUfs(t)
	defer clean()
662 663 664 665 666 667 668 669 670 671 672 673 674 675

	contents := "hello"
	fn := wd + "/mount/y"
	err := ioutil.WriteFile(fn, []byte(contents), 0644)
	CheckSuccess(err)
	time.Sleep(0.2e9)

	truncTs := time.Nanoseconds()
	err = os.Truncate(fn, 3)
	CheckSuccess(err)

	fi, err := os.Lstat(fn)
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
676
	if abs(truncTs-fi.Mtime_ns) > 0.1e9 {
677 678 679
		t.Error("timestamp drift", truncTs, fi.Mtime_ns)
	}
}
680 681 682 683 684 685

func TestRemoveAll(t *testing.T) {
	t.Log("TestRemoveAll")
	wd, clean := setupUfs(t)
	defer clean()

686
	err := os.MkdirAll(wd+"/ro/dir/subdir", 0755)
687
	CheckSuccess(err)
688

689
	contents := "hello"
690
	fn := wd + "/ro/dir/subdir/y"
691 692 693
	err = ioutil.WriteFile(fn, []byte(contents), 0644)
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
694
	err = os.RemoveAll(wd + "/mount/dir")
695 696 697
	if err != nil {
		t.Error("Should delete all")
	}
698 699 700 701 702 703 704

	for _, f := range []string{"dir/subdir/y", "dir/subdir", "dir"} {
		if fi, _ := os.Lstat(filepath.Join(wd, "mount", f)); fi != nil {
			t.Errorf("file %s should have disappeared: %v", f, fi)
		}
	}

705 706 707 708 709 710 711
	names, err := Readdirnames(wd + "/rw/DELETIONS")
	CheckSuccess(err)
	if len(names) != 3 {
		t.Fatal("unexpected names", names)
	}
}

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
712
func TestRmRf(t *testing.T) {
713
	t.Log("TestRmRf")
714 715 716 717 718 719 720 721 722 723
	wd, clean := setupUfs(t)
	defer clean()

	err := os.MkdirAll(wd+"/ro/dir/subdir", 0755)
	CheckSuccess(err)

	contents := "hello"
	fn := wd + "/ro/dir/subdir/y"
	err = ioutil.WriteFile(fn, []byte(contents), 0644)
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
724 725
	bin, err := exec.LookPath("rm")
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
726
	cmd := exec.Command(bin, "-rf", wd+"/mount/dir")
727 728 729 730 731 732 733 734 735 736 737
	err = cmd.Run()
	if err != nil {
		t.Fatal("rm -rf returned error:", err)
	}

	for _, f := range []string{"dir/subdir/y", "dir/subdir", "dir"} {
		if fi, _ := os.Lstat(filepath.Join(wd, "mount", f)); fi != nil {
			t.Errorf("file %s should have disappeared: %v", f, fi)
		}
	}

738 739 740 741 742
	names, err := Readdirnames(wd + "/rw/DELETIONS")
	CheckSuccess(err)
	if len(names) != 3 {
		t.Fatal("unexpected names", names)
	}
743 744
}

745 746 747 748 749 750 751 752 753 754
func Readdirnames(dir string) ([]string, os.Error) {
	f, err := os.Open(dir)
	if err != nil {
		return nil, err
	}

	defer f.Close()
	return f.Readdirnames(-1)
}

755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791
func TestDropDeletionCache(t *testing.T) {
	t.Log("TestDropDeletionCache")
	wd, clean := setupUfs(t)
	defer clean()

	err := ioutil.WriteFile(wd+"/ro/file", []byte("bla"), 0644)
	CheckSuccess(err)
	_, err = os.Lstat(wd + "/mount/file")
	CheckSuccess(err)
	err = os.Remove(wd + "/mount/file")
	CheckSuccess(err)
	fi, _ := os.Lstat(wd + "/mount/file")
	if fi != nil {
		t.Fatal("Lstat() should have failed", fi)
	}

	names, err := Readdirnames(wd + "/rw/DELETIONS")
	CheckSuccess(err)
	if len(names) != 1 {
		t.Fatal("unexpected names", names)
	}
	os.Remove(wd + "/rw/DELETIONS/" + names[0])
	fi, _ = os.Lstat(wd + "/mount/file")
	if fi != nil {
		t.Fatal("Lstat() should have failed", fi)
	}

	// Expire kernel entry.
	time.Sleep(0.6e9 * entryTtl)
	err = ioutil.WriteFile(wd+"/mount/.drop_cache", []byte(""), 0644)
	CheckSuccess(err)
	_, err = os.Lstat(wd + "/mount/file")
	if err != nil {
		t.Fatal("Lstat() should have succeeded", err)
	}
}

792 793 794 795 796
func TestDropCache(t *testing.T) {
	t.Log("TestDropCache")
	wd, clean := setupUfs(t)
	defer clean()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
797
	err := ioutil.WriteFile(wd+"/ro/file", []byte("bla"), 0644)
798 799 800 801 802 803 804
	CheckSuccess(err)

	_, err = os.Lstat(wd + "/mount/.drop_cache")
	CheckSuccess(err)

	names, err := Readdirnames(wd + "/mount")
	CheckSuccess(err)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
805
	if len(names) != 1 || names[0] != "file" {
806 807 808
		t.Fatal("unexpected names", names)
	}

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
809
	err = ioutil.WriteFile(wd+"/ro/file2", []byte("blabla"), 0644)
810 811 812 813 814 815
	names2, err := Readdirnames(wd + "/mount")
	CheckSuccess(err)
	if len(names2) != len(names) {
		t.Fatal("mismatch", names2)
	}

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
816
	err = ioutil.WriteFile(wd+"/mount/.drop_cache", []byte("does not matter"), 0644)
817 818 819 820 821 822
	CheckSuccess(err)
	names2, err = Readdirnames(wd + "/mount")
	if len(names2) != 2 {
		t.Fatal("mismatch 2", names2)
	}
}
823 824 825 826 827 828 829 830 831 832 833 834 835 836 837

func TestDisappearing(t *testing.T) {
	// This init is like setupUfs, but we want access to the
	// writable Fs.
	wd := fuse.MakeTempDir()
	defer os.RemoveAll(wd)
	err := os.Mkdir(wd+"/mount", 0700)
	fuse.CheckSuccess(err)

	err = os.Mkdir(wd+"/rw", 0700)
	fuse.CheckSuccess(err)

	os.Mkdir(wd+"/ro", 0700)
	fuse.CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
838
	wrFs := fuse.NewLoopbackFileSystem(wd + "/rw")
839 840 841
	var fses []fuse.FileSystem
	fses = append(fses, wrFs)
	fses = append(fses, fuse.NewLoopbackFileSystem(wd+"/ro"))
842
	ufs := NewUnionFs(fses, testOpts)
843 844

	opts := &fuse.FileSystemOptions{
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
845 846 847
		EntryTimeout:    entryTtl,
		AttrTimeout:     entryTtl,
		NegativeTimeout: entryTtl,
848 849
	}

850
	state, _, err := fuse.MountPathFileSystem(wd+"/mount", ufs, opts)
851 852 853 854 855 856 857
	CheckSuccess(err)
	defer state.Unmount()
	state.Debug = true
	go state.Loop(true)

	log.Println("TestDisappearing2")

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
858
	err = ioutil.WriteFile(wd+"/ro/file", []byte("blabla"), 0644)
859 860
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
861
	err = os.Remove(wd + "/mount/file")
862 863 864 865
	CheckSuccess(err)

	oldRoot := wrFs.Root
	wrFs.Root = "/dev/null"
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
866
	time.Sleep(1.5 * entryTtl * 1e9)
867

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
868
	_, err = ioutil.ReadDir(wd + "/mount")
869
	if err == nil {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
870
		t.Fatal("Readdir should have failed")
871
	}
872
	log.Println("expected readdir failure:", err)
873

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
874
	err = ioutil.WriteFile(wd+"/mount/file2", []byte("blabla"), 0644)
875 876 877 878 879 880 881
	if err == nil {
		t.Fatal("write should have failed")
	}
	log.Println("expected write failure:", err)

	// Restore, and wait for caches to catch up.
	wrFs.Root = oldRoot
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
882
	time.Sleep(1.5 * entryTtl * 1e9)
883

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
884
	_, err = ioutil.ReadDir(wd + "/mount")
885
	if err != nil {
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
886
		t.Fatal("Readdir should succeed", err)
887
	}
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
888
	err = ioutil.WriteFile(wd+"/mount/file2", []byte("blabla"), 0644)
889 890
	if err != nil {
		t.Fatal("write should succeed", err)
891
	}
892
}
893 894 895 896 897 898 899 900 901 902 903 904

func TestDeletedGetAttr(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

	err := ioutil.WriteFile(wd+"/ro/file", []byte("blabla"), 0644)
	CheckSuccess(err)

	f, err := os.Open(wd + "/mount/file")
	CheckSuccess(err)
	defer f.Close()

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
905
	err = os.Remove(wd + "/mount/file")
906 907 908 909 910 911
	CheckSuccess(err)

	if fi, err := f.Stat(); err != nil || !fi.IsRegular() {
		t.Fatalf("stat returned error or non-file: %v %v", err, fi)
	}
}
912

913
func TestDoubleOpen(t *testing.T) {
914 915
	wd, clean := setupUfs(t)
	defer clean()
916
	err := ioutil.WriteFile(wd+"/ro/file", []byte("blablabla"), 0644)
917 918 919 920 921
	CheckSuccess(err)

	roFile, err := os.Open(wd + "/mount/file")
	CheckSuccess(err)
	defer roFile.Close()
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
922
	rwFile, err := os.OpenFile(wd+"/mount/file", os.O_WRONLY|os.O_TRUNC, 0666)
923 924
	CheckSuccess(err)
	defer rwFile.Close()
925 926 927 928

	output, err := ioutil.ReadAll(roFile)
	CheckSuccess(err)
	if len(output) != 0 {
929
		t.Errorf("After r/w truncation, r/o file should be empty too: %q", string(output))
930 931
	}

932 933 934
	want := "hello"
	_, err = rwFile.Write([]byte(want))
	CheckSuccess(err)
935

936
	b := make([]byte, 100)
Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
937

938 939 940 941
	roFile.Seek(0, 0)
	n, err := roFile.Read(b)
	CheckSuccess(err)
	b = b[:n]
942

943 944
	if string(b) != "hello" {
		t.Errorf("r/w and r/o file are not synchronized: got %q want %q", string(b), want)
945
	}
946
}
947 948 949 950 951 952 953 954 955 956 957 958

func TestFdLeak(t *testing.T) {
	beforeEntries, err := ioutil.ReadDir("/proc/self/fd")
	CheckSuccess(err)

	wd, clean := setupUfs(t)
	err = ioutil.WriteFile(wd+"/ro/file", []byte("blablabla"), 0644)
	CheckSuccess(err)

	contents, err := ioutil.ReadFile(wd + "/mount/file")
	CheckSuccess(err)

Han-Wen Nienhuys's avatar
Han-Wen Nienhuys committed
959
	err = ioutil.WriteFile(wd+"/mount/file", contents, 0644)
960 961 962 963 964 965 966 967 968 969 970
	CheckSuccess(err)

	clean()

	afterEntries, err := ioutil.ReadDir("/proc/self/fd")
	CheckSuccess(err)

	if len(afterEntries) != len(beforeEntries) {
		t.Errorf("/proc/self/fd changed size: after %v before %v", len(beforeEntries), len(afterEntries))
	}
}
971 972 973 974 975 976 977 978 979 980 981 982 983 984

func TestStatFs(t *testing.T) {
	wd, clean := setupUfs(t)
	defer clean()

	s1 := syscall.Statfs_t{}
	err := syscall.Statfs(wd + "/mount", &s1)
	if err != 0 {
		t.Fatal("statfs mnt", err)
	}
	if s1.Bsize == 0 {
		t.Fatal("Expect blocksize > 0")
	}
}