Browse code

Merge pull request #9648 from estesp/9202-update-resolvconf

Update container resolv.conf when host network changes /etc/resolv.conf

Alexander Morozov authored on 2015/01/09 07:06:55
Showing 24 changed files
... ...
@@ -81,6 +81,7 @@ type Container struct {
81 81
 	MountLabel, ProcessLabel string
82 82
 	AppArmorProfile          string
83 83
 	RestartCount             int
84
+	UpdateDns                bool
84 85
 
85 86
 	// Maps container paths to volume paths.  The key in this is the path to which
86 87
 	// the volume is being mounted inside the container.  Value is the path of the
... ...
@@ -942,6 +943,29 @@ func (container *Container) DisableLink(name string) {
942 942
 
943 943
 func (container *Container) setupContainerDns() error {
944 944
 	if container.ResolvConfPath != "" {
945
+		// check if this is an existing container that needs DNS update:
946
+		if container.UpdateDns {
947
+			// read the host's resolv.conf, get the hash and call updateResolvConf
948
+			log.Debugf("Check container (%s) for update to resolv.conf - UpdateDns flag was set", container.ID)
949
+			latestResolvConf, latestHash := resolvconf.GetLastModified()
950
+
951
+			// because the new host resolv.conf might have localhost nameservers..
952
+			updatedResolvConf, modified := resolvconf.RemoveReplaceLocalDns(latestResolvConf)
953
+			if modified {
954
+				// changes have occurred during resolv.conf localhost cleanup: generate an updated hash
955
+				newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf))
956
+				if err != nil {
957
+					return err
958
+				}
959
+				latestHash = newHash
960
+			}
961
+
962
+			if err := container.updateResolvConf(updatedResolvConf, latestHash); err != nil {
963
+				return err
964
+			}
965
+			// successful update of the restarting container; set the flag off
966
+			container.UpdateDns = false
967
+		}
945 968
 		return nil
946 969
 	}
947 970
 
... ...
@@ -980,17 +1004,86 @@ func (container *Container) setupContainerDns() error {
980 980
 		}
981 981
 
982 982
 		// replace any localhost/127.* nameservers
983
-		resolvConf = utils.RemoveLocalDns(resolvConf)
984
-		// if the resulting resolvConf is empty, use DefaultDns
985
-		if !bytes.Contains(resolvConf, []byte("nameserver")) {
986
-			log.Infof("No non localhost DNS resolver found in resolv.conf and containers can't use it. Using default external servers : %v", DefaultDns)
987
-			// prefix the default dns options with nameserver
988
-			resolvConf = append(resolvConf, []byte("\nnameserver "+strings.Join(DefaultDns, "\nnameserver "))...)
989
-		}
983
+		resolvConf, _ = resolvconf.RemoveReplaceLocalDns(resolvConf)
984
+	}
985
+	//get a sha256 hash of the resolv conf at this point so we can check
986
+	//for changes when the host resolv.conf changes (e.g. network update)
987
+	resolvHash, err := utils.HashData(bytes.NewReader(resolvConf))
988
+	if err != nil {
989
+		return err
990
+	}
991
+	resolvHashFile := container.ResolvConfPath + ".hash"
992
+	if err = ioutil.WriteFile(resolvHashFile, []byte(resolvHash), 0644); err != nil {
993
+		return err
990 994
 	}
991 995
 	return ioutil.WriteFile(container.ResolvConfPath, resolvConf, 0644)
992 996
 }
993 997
 
998
+// called when the host's resolv.conf changes to check whether container's resolv.conf
999
+// is unchanged by the container "user" since container start: if unchanged, the
1000
+// container's resolv.conf will be updated to match the host's new resolv.conf
1001
+func (container *Container) updateResolvConf(updatedResolvConf []byte, newResolvHash string) error {
1002
+
1003
+	if container.ResolvConfPath == "" {
1004
+		return nil
1005
+	}
1006
+	if container.Running {
1007
+		//set a marker in the hostConfig to update on next start/restart
1008
+		container.UpdateDns = true
1009
+		return nil
1010
+	}
1011
+
1012
+	resolvHashFile := container.ResolvConfPath + ".hash"
1013
+
1014
+	//read the container's current resolv.conf and compute the hash
1015
+	resolvBytes, err := ioutil.ReadFile(container.ResolvConfPath)
1016
+	if err != nil {
1017
+		return err
1018
+	}
1019
+	curHash, err := utils.HashData(bytes.NewReader(resolvBytes))
1020
+	if err != nil {
1021
+		return err
1022
+	}
1023
+
1024
+	//read the hash from the last time we wrote resolv.conf in the container
1025
+	hashBytes, err := ioutil.ReadFile(resolvHashFile)
1026
+	if err != nil {
1027
+		return err
1028
+	}
1029
+
1030
+	//if the user has not modified the resolv.conf of the container since we wrote it last
1031
+	//we will replace it with the updated resolv.conf from the host
1032
+	if string(hashBytes) == curHash {
1033
+		log.Debugf("replacing %q with updated host resolv.conf", container.ResolvConfPath)
1034
+
1035
+		// for atomic updates to these files, use temporary files with os.Rename:
1036
+		dir := path.Dir(container.ResolvConfPath)
1037
+		tmpHashFile, err := ioutil.TempFile(dir, "hash")
1038
+		if err != nil {
1039
+			return err
1040
+		}
1041
+		tmpResolvFile, err := ioutil.TempFile(dir, "resolv")
1042
+		if err != nil {
1043
+			return err
1044
+		}
1045
+
1046
+		// write the updates to the temp files
1047
+		if err = ioutil.WriteFile(tmpHashFile.Name(), []byte(newResolvHash), 0644); err != nil {
1048
+			return err
1049
+		}
1050
+		if err = ioutil.WriteFile(tmpResolvFile.Name(), updatedResolvConf, 0644); err != nil {
1051
+			return err
1052
+		}
1053
+
1054
+		// rename the temp files for atomic replace
1055
+		if err = os.Rename(tmpHashFile.Name(), resolvHashFile); err != nil {
1056
+			return err
1057
+		}
1058
+		return os.Rename(tmpResolvFile.Name(), container.ResolvConfPath)
1059
+	}
1060
+	return nil
1061
+}
1062
+
994 1063
 func (container *Container) updateParentsHosts() error {
995 1064
 	parents, err := container.daemon.Parents(container.Name)
996 1065
 	if err != nil {
... ...
@@ -1,6 +1,7 @@
1 1
 package daemon
2 2
 
3 3
 import (
4
+	"bytes"
4 5
 	"fmt"
5 6
 	"io"
6 7
 	"io/ioutil"
... ...
@@ -32,6 +33,7 @@ import (
32 32
 	"github.com/docker/docker/pkg/graphdb"
33 33
 	"github.com/docker/docker/pkg/ioutils"
34 34
 	"github.com/docker/docker/pkg/namesgenerator"
35
+	"github.com/docker/docker/pkg/networkfs/resolvconf"
35 36
 	"github.com/docker/docker/pkg/parsers"
36 37
 	"github.com/docker/docker/pkg/parsers/kernel"
37 38
 	"github.com/docker/docker/pkg/sysinfo"
... ...
@@ -40,10 +42,11 @@ import (
40 40
 	"github.com/docker/docker/trust"
41 41
 	"github.com/docker/docker/utils"
42 42
 	"github.com/docker/docker/volumes"
43
+
44
+	"github.com/go-fsnotify/fsnotify"
43 45
 )
44 46
 
45 47
 var (
46
-	DefaultDns                = []string{"8.8.8.8", "8.8.4.4"}
47 48
 	validContainerNameChars   = `[a-zA-Z0-9][a-zA-Z0-9_.-]`
48 49
 	validContainerNamePattern = regexp.MustCompile(`^/?` + validContainerNameChars + `+$`)
49 50
 )
... ...
@@ -408,6 +411,60 @@ func (daemon *Daemon) restore() error {
408 408
 	return nil
409 409
 }
410 410
 
411
+// set up the watch on the host's /etc/resolv.conf so that we can update container's
412
+// live resolv.conf when the network changes on the host
413
+func (daemon *Daemon) setupResolvconfWatcher() error {
414
+
415
+	watcher, err := fsnotify.NewWatcher()
416
+	if err != nil {
417
+		return err
418
+	}
419
+
420
+	//this goroutine listens for the events on the watch we add
421
+	//on the resolv.conf file on the host
422
+	go func() {
423
+		for {
424
+			select {
425
+			case event := <-watcher.Events:
426
+				if event.Op&fsnotify.Write == fsnotify.Write {
427
+					// verify a real change happened before we go further--a file write may have happened
428
+					// without an actual change to the file
429
+					updatedResolvConf, newResolvConfHash, err := resolvconf.GetIfChanged()
430
+					if err != nil {
431
+						log.Debugf("Error retrieving updated host resolv.conf: %v", err)
432
+					} else if updatedResolvConf != nil {
433
+						// because the new host resolv.conf might have localhost nameservers..
434
+						updatedResolvConf, modified := resolvconf.RemoveReplaceLocalDns(updatedResolvConf)
435
+						if modified {
436
+							// changes have occurred during localhost cleanup: generate an updated hash
437
+							newHash, err := utils.HashData(bytes.NewReader(updatedResolvConf))
438
+							if err != nil {
439
+								log.Debugf("Error generating hash of new resolv.conf: %v", err)
440
+							} else {
441
+								newResolvConfHash = newHash
442
+							}
443
+						}
444
+						log.Debugf("host network resolv.conf changed--walking container list for updates")
445
+						contList := daemon.containers.List()
446
+						for _, container := range contList {
447
+							if err := container.updateResolvConf(updatedResolvConf, newResolvConfHash); err != nil {
448
+								log.Debugf("Error on resolv.conf update check for container ID: %s: %v", container.ID, err)
449
+							}
450
+						}
451
+					}
452
+				}
453
+			case err := <-watcher.Errors:
454
+				log.Debugf("host resolv.conf notify error: %v", err)
455
+			}
456
+		}
457
+	}()
458
+
459
+	if err := watcher.Add("/etc/resolv.conf"); err != nil {
460
+		return err
461
+	}
462
+	return nil
463
+}
464
+
411 465
 func (daemon *Daemon) checkDeprecatedExpose(config *runconfig.Config) bool {
412 466
 	if config != nil {
413 467
 		if config.PortSpecs != nil {
... ...
@@ -930,6 +987,12 @@ func NewDaemonFromDirectory(config *Config, eng *engine.Engine) (*Daemon, error)
930 930
 	if err := daemon.restore(); err != nil {
931 931
 		return nil, err
932 932
 	}
933
+
934
+	// set up filesystem watch on resolv.conf for network changes
935
+	if err := daemon.setupResolvconfWatcher(); err != nil {
936
+		return nil, err
937
+	}
938
+
933 939
 	// Setup shutdown handlers
934 940
 	// FIXME: can these shutdown handlers be registered closer to their source?
935 941
 	eng.OnShutdown(func() {
... ...
@@ -24,34 +24,3 @@ func TestMergeLxcConfig(t *testing.T) {
24 24
 		t.Fatalf("expected %s got %s", expected, cpuset)
25 25
 	}
26 26
 }
27
-
28
-func TestRemoveLocalDns(t *testing.T) {
29
-	ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
30
-
31
-	if result := utils.RemoveLocalDns([]byte(ns0)); result != nil {
32
-		if ns0 != string(result) {
33
-			t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
34
-		}
35
-	}
36
-
37
-	ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
38
-	if result := utils.RemoveLocalDns([]byte(ns1)); result != nil {
39
-		if ns0 != string(result) {
40
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
41
-		}
42
-	}
43
-
44
-	ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
45
-	if result := utils.RemoveLocalDns([]byte(ns1)); result != nil {
46
-		if ns0 != string(result) {
47
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
48
-		}
49
-	}
50
-
51
-	ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
52
-	if result := utils.RemoveLocalDns([]byte(ns1)); result != nil {
53
-		if ns0 != string(result) {
54
-			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
55
-		}
56
-	}
57
-}
... ...
@@ -130,7 +130,7 @@ information.  You can see this by running `mount` inside a container:
130 130
     ...
131 131
     /dev/disk/by-uuid/1fec...ebdf on /etc/hostname type ext4 ...
132 132
     /dev/disk/by-uuid/1fec...ebdf on /etc/hosts type ext4 ...
133
-    tmpfs on /etc/resolv.conf type tmpfs ...
133
+    /dev/disk/by-uuid/1fec...ebdf on /etc/resolv.conf type ext4 ...
134 134
     ...
135 135
 
136 136
 This arrangement allows Docker to do clever things like keep
... ...
@@ -178,7 +178,20 @@ Four different options affect container domain name services.
178 178
 Note that Docker, in the absence of either of the last two options
179 179
 above, will make `/etc/resolv.conf` inside of each container look like
180 180
 the `/etc/resolv.conf` of the host machine where the `docker` daemon is
181
-running.  The options then modify this default configuration.
181
+running.  You might wonder what happens when the host machine's
182
+`/etc/resolv.conf` file changes.  The `docker` daemon has a file change
183
+notifier active which will watch for changes to the host DNS configuration.
184
+When the host file changes, all stopped containers which have a matching
185
+`resolv.conf` to the host will be updated immediately to this newest host
186
+configuration.  Containers which are running when the host configuration
187
+changes will need to stop and start to pick up the host changes due to lack
188
+of a facility to ensure atomic writes of the `resolv.conf` file while the
189
+container is running. If the container's `resolv.conf` has been edited since
190
+it was started with the default configuration, no replacement will be
191
+attempted as it would overwrite the changes performed by the container.
192
+If the options (`--dns` or `--dns-search`) have been used to modify the 
193
+default host configuration, then the replacement with an updated host's
194
+`/etc/resolv.conf` will not happen as well.
182 195
 
183 196
 ## Communication between containers and the wider world
184 197
 
... ...
@@ -1403,6 +1403,157 @@ func TestRunDnsOptionsBasedOnHostResolvConf(t *testing.T) {
1403 1403
 	logDone("run - dns options based on host resolv.conf")
1404 1404
 }
1405 1405
 
1406
+// Test the file watch notifier on docker host's /etc/resolv.conf
1407
+// A go-routine is responsible for auto-updating containers which are
1408
+// stopped and have an unmodified copy of resolv.conf, as well as
1409
+// marking running containers as requiring an update on next restart
1410
+func TestRunResolvconfUpdater(t *testing.T) {
1411
+
1412
+	tmpResolvConf := []byte("search pommesfrites.fr\nnameserver 12.34.56.78")
1413
+	tmpLocalhostResolvConf := []byte("nameserver 127.0.0.1")
1414
+
1415
+	//take a copy of resolv.conf for restoring after test completes
1416
+	resolvConfSystem, err := ioutil.ReadFile("/etc/resolv.conf")
1417
+	if err != nil {
1418
+		t.Fatal(err)
1419
+	}
1420
+
1421
+	//cleanup
1422
+	defer func() {
1423
+		deleteAllContainers()
1424
+		if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
1425
+			t.Fatal(err)
1426
+		}
1427
+	}()
1428
+
1429
+	//1. test that a non-running container gets an updated resolv.conf
1430
+	cmd := exec.Command(dockerBinary, "run", "--name='first'", "busybox", "true")
1431
+	if _, err := runCommand(cmd); err != nil {
1432
+		t.Fatal(err)
1433
+	}
1434
+	containerID1, err := getIDByName("first")
1435
+	if err != nil {
1436
+		t.Fatal(err)
1437
+	}
1438
+
1439
+	// replace resolv.conf with our temporary copy
1440
+	bytesResolvConf := []byte(tmpResolvConf)
1441
+	if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
1442
+		t.Fatal(err)
1443
+	}
1444
+
1445
+	time.Sleep(time.Second / 2)
1446
+	// check for update in container
1447
+	containerResolv, err := readContainerFile(containerID1, "resolv.conf")
1448
+	if err != nil {
1449
+		t.Fatal(err)
1450
+	}
1451
+	if !bytes.Equal(containerResolv, bytesResolvConf) {
1452
+		t.Fatalf("Stopped container does not have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv))
1453
+	}
1454
+
1455
+	//2. test that a non-running container does not receive resolv.conf updates
1456
+	//   if it modified the container copy of the starting point resolv.conf
1457
+	cmd = exec.Command(dockerBinary, "run", "--name='second'", "busybox", "sh", "-c", "echo 'search mylittlepony.com' >>/etc/resolv.conf")
1458
+	if _, err = runCommand(cmd); err != nil {
1459
+		t.Fatal(err)
1460
+	}
1461
+	containerID2, err := getIDByName("second")
1462
+	if err != nil {
1463
+		t.Fatal(err)
1464
+	}
1465
+	containerResolvHashBefore, err := readContainerFile(containerID2, "resolv.conf.hash")
1466
+	if err != nil {
1467
+		t.Fatal(err)
1468
+	}
1469
+
1470
+	//make a change to resolv.conf (in this case replacing our tmp copy with orig copy)
1471
+	if err := ioutil.WriteFile("/etc/resolv.conf", resolvConfSystem, 0644); err != nil {
1472
+		t.Fatal(err)
1473
+	}
1474
+
1475
+	time.Sleep(time.Second / 2)
1476
+	containerResolvHashAfter, err := readContainerFile(containerID2, "resolv.conf.hash")
1477
+	if err != nil {
1478
+		t.Fatal(err)
1479
+	}
1480
+
1481
+	if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) {
1482
+		t.Fatalf("Stopped container with modified resolv.conf should not have been updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter)
1483
+	}
1484
+
1485
+	//3. test that a running container's resolv.conf is not modified while running
1486
+	cmd = exec.Command(dockerBinary, "run", "-d", "busybox", "top")
1487
+	out, _, err := runCommandWithOutput(cmd)
1488
+	if err != nil {
1489
+		t.Fatal(err)
1490
+	}
1491
+	runningContainerID := strings.TrimSpace(out)
1492
+
1493
+	containerResolvHashBefore, err = readContainerFile(runningContainerID, "resolv.conf.hash")
1494
+	if err != nil {
1495
+		t.Fatal(err)
1496
+	}
1497
+
1498
+	// replace resolv.conf
1499
+	if err := ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
1500
+		t.Fatal(err)
1501
+	}
1502
+
1503
+	// make sure the updater has time to run to validate we really aren't
1504
+	// getting updated
1505
+	time.Sleep(time.Second / 2)
1506
+	containerResolvHashAfter, err = readContainerFile(runningContainerID, "resolv.conf.hash")
1507
+	if err != nil {
1508
+		t.Fatal(err)
1509
+	}
1510
+
1511
+	if !bytes.Equal(containerResolvHashBefore, containerResolvHashAfter) {
1512
+		t.Fatalf("Running container's resolv.conf should not be updated; expected hash: %v, new hash: %v", containerResolvHashBefore, containerResolvHashAfter)
1513
+	}
1514
+
1515
+	//4. test that a running container's resolv.conf is updated upon restart
1516
+	//   (the above container is still running..)
1517
+	cmd = exec.Command(dockerBinary, "restart", runningContainerID)
1518
+	if _, err = runCommand(cmd); err != nil {
1519
+		t.Fatal(err)
1520
+	}
1521
+
1522
+	// check for update in container
1523
+	containerResolv, err = readContainerFile(runningContainerID, "resolv.conf")
1524
+	if err != nil {
1525
+		t.Fatal(err)
1526
+	}
1527
+	if !bytes.Equal(containerResolv, bytesResolvConf) {
1528
+		t.Fatalf("Restarted container should have updated resolv.conf; expected %q, got %q", tmpResolvConf, string(containerResolv))
1529
+	}
1530
+
1531
+	//5. test that additions of a localhost resolver are cleaned from
1532
+	//   host resolv.conf before updating container's resolv.conf copies
1533
+
1534
+	// replace resolv.conf with a localhost-only nameserver copy
1535
+	bytesResolvConf = []byte(tmpLocalhostResolvConf)
1536
+	if err = ioutil.WriteFile("/etc/resolv.conf", bytesResolvConf, 0644); err != nil {
1537
+		t.Fatal(err)
1538
+	}
1539
+
1540
+	time.Sleep(time.Second / 2)
1541
+	// our first exited container ID should have been updated, but with default DNS
1542
+	// after the cleanup of resolv.conf found only a localhost nameserver:
1543
+	containerResolv, err = readContainerFile(containerID1, "resolv.conf")
1544
+	if err != nil {
1545
+		t.Fatal(err)
1546
+	}
1547
+
1548
+	expected := "\nnameserver 8.8.8.8\nnameserver 8.8.4.4"
1549
+	if !bytes.Equal(containerResolv, []byte(expected)) {
1550
+		t.Fatalf("Container does not have cleaned/replaced DNS in resolv.conf; expected %q, got %q", expected, string(containerResolv))
1551
+	}
1552
+
1553
+	//cleanup, restore original resolv.conf happens in defer func()
1554
+	logDone("run - resolv.conf updater")
1555
+}
1556
+
1406 1557
 func TestRunAddHost(t *testing.T) {
1407 1558
 	defer deleteAllContainers()
1408 1559
 	cmd := exec.Command(dockerBinary, "run", "--add-host=extra:86.75.30.9", "busybox", "grep", "extra", "/etc/hosts")
... ...
@@ -5,13 +5,25 @@ import (
5 5
 	"io/ioutil"
6 6
 	"regexp"
7 7
 	"strings"
8
+	"sync"
9
+
10
+	log "github.com/Sirupsen/logrus"
11
+	"github.com/docker/docker/utils"
8 12
 )
9 13
 
10 14
 var (
11
-	nsRegexp     = regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`)
12
-	searchRegexp = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
15
+	defaultDns      = []string{"8.8.8.8", "8.8.4.4"}
16
+	localHostRegexp = regexp.MustCompile(`(?m)^nameserver 127[^\n]+\n*`)
17
+	nsRegexp        = regexp.MustCompile(`^\s*nameserver\s*(([0-9]+\.){3}([0-9]+))\s*$`)
18
+	searchRegexp    = regexp.MustCompile(`^\s*search\s*(([^\s]+\s*)*)$`)
13 19
 )
14 20
 
21
+var lastModified struct {
22
+	sync.Mutex
23
+	sha256   string
24
+	contents []byte
25
+}
26
+
15 27
 func Get() ([]byte, error) {
16 28
 	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
17 29
 	if err != nil {
... ...
@@ -20,6 +32,57 @@ func Get() ([]byte, error) {
20 20
 	return resolv, nil
21 21
 }
22 22
 
23
+// Retrieves the host /etc/resolv.conf file, checks against the last hash
24
+// and, if modified since last check, returns the bytes and new hash.
25
+// This feature is used by the resolv.conf updater for containers
26
+func GetIfChanged() ([]byte, string, error) {
27
+	lastModified.Lock()
28
+	defer lastModified.Unlock()
29
+
30
+	resolv, err := ioutil.ReadFile("/etc/resolv.conf")
31
+	if err != nil {
32
+		return nil, "", err
33
+	}
34
+	newHash, err := utils.HashData(bytes.NewReader(resolv))
35
+	if err != nil {
36
+		return nil, "", err
37
+	}
38
+	if lastModified.sha256 != newHash {
39
+		lastModified.sha256 = newHash
40
+		lastModified.contents = resolv
41
+		return resolv, newHash, nil
42
+	}
43
+	// nothing changed, so return no data
44
+	return nil, "", nil
45
+}
46
+
47
+// retrieve the last used contents and hash of the host resolv.conf
48
+// Used by containers updating on restart
49
+func GetLastModified() ([]byte, string) {
50
+	lastModified.Lock()
51
+	defer lastModified.Unlock()
52
+
53
+	return lastModified.contents, lastModified.sha256
54
+}
55
+
56
+// RemoveReplaceLocalDns looks for localhost (127.*) entries in the provided
57
+// resolv.conf, removing local nameserver entries, and, if the resulting
58
+// cleaned config has no defined nameservers left, adds default DNS entries
59
+// It also returns a boolean to notify the caller if changes were made at all
60
+func RemoveReplaceLocalDns(resolvConf []byte) ([]byte, bool) {
61
+	changed := false
62
+	cleanedResolvConf := localHostRegexp.ReplaceAll(resolvConf, []byte{})
63
+	// if the resulting resolvConf is empty, use defaultDns
64
+	if !bytes.Contains(cleanedResolvConf, []byte("nameserver")) {
65
+		log.Infof("No non-localhost DNS nameservers are left in resolv.conf. Using default external servers : %v", defaultDns)
66
+		cleanedResolvConf = append(cleanedResolvConf, []byte("\nnameserver "+strings.Join(defaultDns, "\nnameserver "))...)
67
+	}
68
+	if !bytes.Equal(resolvConf, cleanedResolvConf) {
69
+		changed = true
70
+	}
71
+	return cleanedResolvConf, changed
72
+}
73
+
23 74
 // getLines parses input into lines and strips away comments.
24 75
 func getLines(input []byte, commentMarker []byte) [][]byte {
25 76
 	lines := bytes.Split(input, []byte("\n"))
... ...
@@ -156,3 +156,34 @@ func TestBuildWithZeroLengthDomainSearch(t *testing.T) {
156 156
 		t.Fatalf("Expected to not find '%s' got '%s'", notExpected, content)
157 157
 	}
158 158
 }
159
+
160
+func TestRemoveReplaceLocalDns(t *testing.T) {
161
+	ns0 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\n"
162
+
163
+	if result, _ := RemoveReplaceLocalDns([]byte(ns0)); result != nil {
164
+		if ns0 != string(result) {
165
+			t.Fatalf("Failed No Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
166
+		}
167
+	}
168
+
169
+	ns1 := "nameserver 10.16.60.14\nnameserver 10.16.60.21\nnameserver 127.0.0.1\n"
170
+	if result, _ := RemoveReplaceLocalDns([]byte(ns1)); result != nil {
171
+		if ns0 != string(result) {
172
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
173
+		}
174
+	}
175
+
176
+	ns1 = "nameserver 10.16.60.14\nnameserver 127.0.0.1\nnameserver 10.16.60.21\n"
177
+	if result, _ := RemoveReplaceLocalDns([]byte(ns1)); result != nil {
178
+		if ns0 != string(result) {
179
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
180
+		}
181
+	}
182
+
183
+	ns1 = "nameserver 127.0.1.1\nnameserver 10.16.60.14\nnameserver 10.16.60.21\n"
184
+	if result, _ := RemoveReplaceLocalDns([]byte(ns1)); result != nil {
185
+		if ns0 != string(result) {
186
+			t.Fatalf("Failed Localhost: expected \n<%s> got \n<%s>", ns0, string(result))
187
+		}
188
+	}
189
+}
... ...
@@ -55,6 +55,8 @@ clone git github.com/docker/libtrust 230dfd18c232
55 55
 
56 56
 clone git github.com/Sirupsen/logrus v0.6.0
57 57
 
58
+clone git github.com/go-fsnotify/fsnotify v1.0.4
59
+
58 60
 # get Go tip's archive/tar, for xattr support and improved performance
59 61
 # TODO after Go 1.4 drops, bump our minimum supported version and drop this vendored dep
60 62
 if [ "$1" = '--go' ]; then
... ...
@@ -291,14 +291,6 @@ func NewHTTPRequestError(msg string, res *http.Response) error {
291 291
 	}
292 292
 }
293 293
 
294
-var localHostRx = regexp.MustCompile(`(?m)^nameserver 127[^\n]+\n*`)
295
-
296
-// RemoveLocalDns looks into the /etc/resolv.conf,
297
-// and removes any local nameserver entries.
298
-func RemoveLocalDns(resolvConf []byte) []byte {
299
-	return localHostRx.ReplaceAll(resolvConf, []byte{})
300
-}
301
-
302 294
 // An StatusError reports an unsuccessful exit by a command.
303 295
 type StatusError struct {
304 296
 	Status     string
305 297
new file mode 100644
... ...
@@ -0,0 +1,6 @@
0
+# Setup a Global .gitignore for OS and editor generated files:
1
+# https://help.github.com/articles/ignoring-files
2
+# git config --global core.excludesfile ~/.gitignore_global
3
+
4
+.vagrant
5
+*.sublime-project
0 6
new file mode 100644
... ...
@@ -0,0 +1,13 @@
0
+language: go
1
+
2
+go:
3
+  - 1.2
4
+  - tip
5
+
6
+# not yet https://github.com/travis-ci/travis-ci/issues/2318
7
+os:
8
+  - linux
9
+  - osx
10
+
11
+notifications:
12
+  email: false
0 13
new file mode 100644
... ...
@@ -0,0 +1,32 @@
0
+# Names should be added to this file as
1
+#	Name or Organization <email address>
2
+# The email address is not required for organizations.
3
+
4
+# You can update this list using the following command:
5
+#
6
+#   $ git shortlog -se | awk '{print $2 " " $3 " " $4}'
7
+
8
+# Please keep the list sorted.
9
+
10
+Adrien Bustany <adrien@bustany.org>
11
+Caleb Spare <cespare@gmail.com>
12
+Case Nelson <case@teammating.com>
13
+Chris Howey <howeyc@gmail.com> <chris@howey.me>
14
+Christoffer Buchholz <christoffer.buchholz@gmail.com>
15
+Dave Cheney <dave@cheney.net>
16
+Francisco Souza <f@souza.cc>
17
+Hari haran <hariharan.uno@gmail.com>
18
+John C Barstow
19
+Kelvin Fo <vmirage@gmail.com>
20
+Nathan Youngman <git@nathany.com>
21
+Paul Hammond <paul@paulhammond.org>
22
+Pursuit92 <JoshChase@techpursuit.net>
23
+Rob Figueiredo <robfig@gmail.com>
24
+Soge Zhang <zhssoge@gmail.com>
25
+Tilak Sharma <tilaks@google.com>
26
+Travis Cline <travis.cline@gmail.com>
27
+Tudor Golubenco <tudor.g@gmail.com>
28
+Yukang <moorekang@gmail.com>
29
+bronze1man <bronze1man@gmail.com>
30
+debrando <denis.brandolini@gmail.com>
31
+henrikedwards <henrik.edwards@gmail.com>
0 32
new file mode 100644
... ...
@@ -0,0 +1,237 @@
0
+# Changelog
1
+
2
+## v1.0.4 / 2014-09-07
3
+
4
+* kqueue: add dragonfly to the build tags.
5
+* Rename source code files, rearrange code so exported APIs are at the top.
6
+* Add done channel to example code. [#37](https://github.com/go-fsnotify/fsnotify/pull/37) (thanks @chenyukang)
7
+
8
+## v1.0.3 / 2014-08-19
9
+
10
+* [Fix] Windows MOVED_TO now translates to Create like on BSD and Linux. [#36](https://github.com/go-fsnotify/fsnotify/issues/36)
11
+
12
+## v1.0.2 / 2014-08-17
13
+
14
+* [Fix] Missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)
15
+* [Fix] Make ./path and path equivalent. (thanks @zhsso)
16
+
17
+## v1.0.0 / 2014-08-15
18
+
19
+* [API] Remove AddWatch on Windows, use Add.
20
+* Improve documentation for exported identifiers. [#30](https://github.com/go-fsnotify/fsnotify/issues/30)
21
+* Minor updates based on feedback from golint.
22
+
23
+## dev / 2014-07-09
24
+
25
+* Moved to [github.com/go-fsnotify/fsnotify](https://github.com/go-fsnotify/fsnotify).
26
+* Use os.NewSyscallError instead of returning errno (thanks @hariharan-uno)
27
+ 
28
+## dev / 2014-07-04
29
+
30
+* kqueue: fix incorrect mutex used in Close()
31
+* Update example to demonstrate usage of Op.
32
+
33
+## dev / 2014-06-28
34
+
35
+* [API] Don't set the Write Op for attribute notifications [#4](https://github.com/go-fsnotify/fsnotify/issues/4)
36
+* Fix for String() method on Event (thanks Alex Brainman)
37
+* Don't build on Plan 9 or Solaris (thanks @4ad)
38
+
39
+## dev / 2014-06-21
40
+
41
+* Events channel of type Event rather than *Event.
42
+* [internal] use syscall constants directly for inotify and kqueue.
43
+* [internal] kqueue: rename events to kevents and fileEvent to event.
44
+
45
+## dev / 2014-06-19
46
+
47
+* Go 1.3+ required on Windows (uses syscall.ERROR_MORE_DATA internally).
48
+* [internal] remove cookie from Event struct (unused).
49
+* [internal] Event struct has the same definition across every OS.
50
+* [internal] remove internal watch and removeWatch methods.
51
+
52
+## dev / 2014-06-12
53
+
54
+* [API] Renamed Watch() to Add() and RemoveWatch() to Remove().
55
+* [API] Pluralized channel names: Events and Errors.
56
+* [API] Renamed FileEvent struct to Event.
57
+* [API] Op constants replace methods like IsCreate().
58
+
59
+## dev / 2014-06-12
60
+
61
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
62
+
63
+## dev / 2014-05-23
64
+
65
+* [API] Remove current implementation of WatchFlags.
66
+    * current implementation doesn't take advantage of OS for efficiency
67
+    * provides little benefit over filtering events as they are received, but has  extra bookkeeping and mutexes
68
+    * no tests for the current implementation
69
+    * not fully implemented on Windows [#93](https://github.com/howeyc/fsnotify/issues/93#issuecomment-39285195)
70
+
71
+## v0.9.2 / 2014-08-17
72
+
73
+* [Backport] Fix missing create events on OS X. [#14](https://github.com/go-fsnotify/fsnotify/issues/14) (thanks @zhsso)
74
+
75
+## v0.9.1 / 2014-06-12
76
+
77
+* Fix data race on kevent buffer (thanks @tilaks) [#98](https://github.com/howeyc/fsnotify/pull/98)
78
+
79
+## v0.9.0 / 2014-01-17
80
+
81
+* IsAttrib() for events that only concern a file's metadata [#79][] (thanks @abustany)
82
+* [Fix] kqueue: fix deadlock [#77][] (thanks @cespare)
83
+* [NOTICE] Development has moved to `code.google.com/p/go.exp/fsnotify` in preparation for inclusion in the Go standard library.
84
+
85
+## v0.8.12 / 2013-11-13
86
+
87
+* [API] Remove FD_SET and friends from Linux adapter
88
+
89
+## v0.8.11 / 2013-11-02
90
+
91
+* [Doc] Add Changelog [#72][] (thanks @nathany)
92
+* [Doc] Spotlight and double modify events on OS X [#62][] (reported by @paulhammond)
93
+
94
+## v0.8.10 / 2013-10-19
95
+
96
+* [Fix] kqueue: remove file watches when parent directory is removed [#71][] (reported by @mdwhatcott)
97
+* [Fix] kqueue: race between Close and readEvents [#70][] (reported by @bernerdschaefer)
98
+* [Doc] specify OS-specific limits in README (thanks @debrando)
99
+
100
+## v0.8.9 / 2013-09-08
101
+
102
+* [Doc] Contributing (thanks @nathany)
103
+* [Doc] update package path in example code [#63][] (thanks @paulhammond)
104
+* [Doc] GoCI badge in README (Linux only) [#60][]
105
+* [Doc] Cross-platform testing with Vagrant  [#59][] (thanks @nathany)
106
+
107
+## v0.8.8 / 2013-06-17
108
+
109
+* [Fix] Windows: handle `ERROR_MORE_DATA` on Windows [#49][] (thanks @jbowtie)
110
+
111
+## v0.8.7 / 2013-06-03
112
+
113
+* [API] Make syscall flags internal
114
+* [Fix] inotify: ignore event changes
115
+* [Fix] race in symlink test [#45][] (reported by @srid)
116
+* [Fix] tests on Windows
117
+* lower case error messages
118
+
119
+## v0.8.6 / 2013-05-23
120
+
121
+* kqueue: Use EVT_ONLY flag on Darwin
122
+* [Doc] Update README with full example
123
+
124
+## v0.8.5 / 2013-05-09
125
+
126
+* [Fix] inotify: allow monitoring of "broken" symlinks (thanks @tsg)
127
+
128
+## v0.8.4 / 2013-04-07
129
+
130
+* [Fix] kqueue: watch all file events [#40][] (thanks @ChrisBuchholz)
131
+
132
+## v0.8.3 / 2013-03-13
133
+
134
+* [Fix] inoitfy/kqueue memory leak [#36][] (reported by @nbkolchin)
135
+* [Fix] kqueue: use fsnFlags for watching a directory [#33][] (reported by @nbkolchin)
136
+
137
+## v0.8.2 / 2013-02-07
138
+
139
+* [Doc] add Authors
140
+* [Fix] fix data races for map access [#29][] (thanks @fsouza)
141
+
142
+## v0.8.1 / 2013-01-09
143
+
144
+* [Fix] Windows path separators
145
+* [Doc] BSD License
146
+
147
+## v0.8.0 / 2012-11-09
148
+
149
+* kqueue: directory watching improvements (thanks @vmirage)
150
+* inotify: add `IN_MOVED_TO` [#25][] (requested by @cpisto)
151
+* [Fix] kqueue: deleting watched directory [#24][] (reported by @jakerr)
152
+
153
+## v0.7.4 / 2012-10-09
154
+
155
+* [Fix] inotify: fixes from https://codereview.appspot.com/5418045/ (ugorji)
156
+* [Fix] kqueue: preserve watch flags when watching for delete [#21][] (reported by @robfig)
157
+* [Fix] kqueue: watch the directory even if it isn't a new watch (thanks @robfig)
158
+* [Fix] kqueue: modify after recreation of file
159
+
160
+## v0.7.3 / 2012-09-27
161
+
162
+* [Fix] kqueue: watch with an existing folder inside the watched folder (thanks @vmirage)
163
+* [Fix] kqueue: no longer get duplicate CREATE events
164
+
165
+## v0.7.2 / 2012-09-01
166
+
167
+* kqueue: events for created directories
168
+
169
+## v0.7.1 / 2012-07-14
170
+
171
+* [Fix] for renaming files
172
+
173
+## v0.7.0 / 2012-07-02
174
+
175
+* [Feature] FSNotify flags
176
+* [Fix] inotify: Added file name back to event path
177
+
178
+## v0.6.0 / 2012-06-06
179
+
180
+* kqueue: watch files after directory created (thanks @tmc)
181
+
182
+## v0.5.1 / 2012-05-22
183
+
184
+* [Fix] inotify: remove all watches before Close()
185
+
186
+## v0.5.0 / 2012-05-03
187
+
188
+* [API] kqueue: return errors during watch instead of sending over channel
189
+* kqueue: match symlink behavior on Linux
190
+* inotify: add `DELETE_SELF` (requested by @taralx)
191
+* [Fix] kqueue: handle EINTR (reported by @robfig)
192
+* [Doc] Godoc example [#1][] (thanks @davecheney)
193
+
194
+## v0.4.0 / 2012-03-30
195
+
196
+* Go 1 released: build with go tool
197
+* [Feature] Windows support using winfsnotify
198
+* Windows does not have attribute change notifications
199
+* Roll attribute notifications into IsModify
200
+
201
+## v0.3.0 / 2012-02-19
202
+
203
+* kqueue: add files when watch directory
204
+
205
+## v0.2.0 / 2011-12-30
206
+
207
+* update to latest Go weekly code
208
+
209
+## v0.1.0 / 2011-10-19
210
+
211
+* kqueue: add watch on file creation to match inotify
212
+* kqueue: create file event
213
+* inotify: ignore `IN_IGNORED` events
214
+* event String()
215
+* linux: common FileEvent functions
216
+* initial commit
217
+
218
+[#79]: https://github.com/howeyc/fsnotify/pull/79
219
+[#77]: https://github.com/howeyc/fsnotify/pull/77
220
+[#72]: https://github.com/howeyc/fsnotify/issues/72
221
+[#71]: https://github.com/howeyc/fsnotify/issues/71
222
+[#70]: https://github.com/howeyc/fsnotify/issues/70
223
+[#63]: https://github.com/howeyc/fsnotify/issues/63
224
+[#62]: https://github.com/howeyc/fsnotify/issues/62
225
+[#60]: https://github.com/howeyc/fsnotify/issues/60
226
+[#59]: https://github.com/howeyc/fsnotify/issues/59
227
+[#49]: https://github.com/howeyc/fsnotify/issues/49
228
+[#45]: https://github.com/howeyc/fsnotify/issues/45
229
+[#40]: https://github.com/howeyc/fsnotify/issues/40
230
+[#36]: https://github.com/howeyc/fsnotify/issues/36
231
+[#33]: https://github.com/howeyc/fsnotify/issues/33
232
+[#29]: https://github.com/howeyc/fsnotify/issues/29
233
+[#25]: https://github.com/howeyc/fsnotify/issues/25
234
+[#24]: https://github.com/howeyc/fsnotify/issues/24
235
+[#21]: https://github.com/howeyc/fsnotify/issues/21
236
+
0 237
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+# Contributing
1
+
2
+* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). 
3
+
4
+### Issues
5
+
6
+* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues).
7
+* Please indicate the platform you are running on.
8
+
9
+### Pull Requests
10
+
11
+A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
12
+
13
+Please indicate that you have signed the CLA in your pull request.
14
+
15
+To hack on fsnotify:
16
+
17
+1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
18
+2. Create your feature branch (`git checkout -b my-new-feature`)
19
+3. Ensure everything works and the tests pass (see below)
20
+4. Commit your changes (`git commit -am 'Add some feature'`)
21
+
22
+Contribute upstream:
23
+
24
+1. Fork fsnotify on GitHub
25
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
26
+3. Push to the branch (`git push fork my-new-feature`)
27
+4. Create a new Pull Request on GitHub
28
+
29
+If other team members need your patch before I merge it:
30
+
31
+1. Install as usual (`go get -u github.com/go-fsnotify/fsnotify`)
32
+2. Add your remote (`git remote add fork git@github.com:mycompany/repo.git`)
33
+3. Pull your revisions (`git fetch fork; git checkout -b my-new-feature fork/my-new-feature`)
34
+
35
+Notice: For smooth sailing, always use the original import path. Installing with `go get` makes this easy.
36
+
37
+Note: The maintainers will update the CHANGELOG on your behalf. Please don't modify it in your pull request.
38
+
39
+### Testing
40
+
41
+fsnotify uses build tags to compile different code on Linux, BSD, OS X, and Windows.
42
+
43
+Before doing a pull request, please do your best to test your changes on multiple platforms, and list which platforms you were able/unable to test on.
44
+
45
+To make cross-platform testing easier, I've created a Vagrantfile for Linux and BSD.
46
+
47
+* Install [Vagrant](http://www.vagrantup.com/) and [VirtualBox](https://www.virtualbox.org/)
48
+* Setup [Vagrant Gopher](https://github.com/nathany/vagrant-gopher) in your `src` folder.
49
+* Run `vagrant up` from the project folder. You can also setup just one box with `vagrant up linux` or `vagrant up bsd` (note: the BSD box doesn't support Windows hosts at this time, and NFS may prompt for your host OS password)
50
+* Once setup, you can run the test suite on a given OS with a single command `vagrant ssh linux -c 'cd go-fsnotify/fsnotify; go test'`.
51
+* When you're done, you will want to halt or destroy the Vagrant boxes.
52
+
53
+Notice: fsnotify file system events don't work on shared folders. The tests get around this limitation by using a tmp directory, but it is something to be aware of.
54
+
55
+Right now I don't have an equivalent solution for Windows and OS X, but there are Windows VMs [freely available from Microsoft](http://www.modern.ie/en-us/virtualization-tools#downloads).
0 56
new file mode 100644
... ...
@@ -0,0 +1,28 @@
0
+Copyright (c) 2012 The Go Authors. All rights reserved.
1
+Copyright (c) 2012 fsnotify Authors. All rights reserved.
2
+
3
+Redistribution and use in source and binary forms, with or without
4
+modification, are permitted provided that the following conditions are
5
+met:
6
+
7
+   * Redistributions of source code must retain the above copyright
8
+notice, this list of conditions and the following disclaimer.
9
+   * Redistributions in binary form must reproduce the above
10
+copyright notice, this list of conditions and the following disclaimer
11
+in the documentation and/or other materials provided with the
12
+distribution.
13
+   * Neither the name of Google Inc. nor the names of its
14
+contributors may be used to endorse or promote products derived from
15
+this software without specific prior written permission.
16
+
17
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
18
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
19
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
21
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
23
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
27
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0 28
new file mode 100644
... ...
@@ -0,0 +1,53 @@
0
+# File system notifications for Go
1
+
2
+[![Coverage](http://gocover.io/_badge/github.com/go-fsnotify/fsnotify)](http://gocover.io/github.com/go-fsnotify/fsnotify) [![GoDoc](https://godoc.org/gopkg.in/fsnotify.v1?status.svg)](https://godoc.org/gopkg.in/fsnotify.v1)
3
+
4
+Cross platform: Windows, Linux, BSD and OS X.
5
+
6
+|Adapter   |OS        |Status    |
7
+|----------|----------|----------|
8
+|inotify   |Linux, Android\*|Supported|
9
+|kqueue    |BSD, OS X, iOS\*|Supported|
10
+|ReadDirectoryChangesW|Windows|Supported|
11
+|FSEvents  |OS X          |[Planned](https://github.com/go-fsnotify/fsnotify/issues/11)|
12
+|FEN       |Solaris 11    |[Planned](https://github.com/go-fsnotify/fsnotify/issues/12)|
13
+|fanotify  |Linux 2.6.37+ | |
14
+|Polling   |*All*         |[Maybe](https://github.com/go-fsnotify/fsnotify/issues/9)|
15
+|          |Plan 9        | |
16
+
17
+\* Android and iOS are untested.
18
+
19
+Please see [the documentation](https://godoc.org/gopkg.in/fsnotify.v1) for usage. Consult the [Wiki](https://github.com/go-fsnotify/fsnotify/wiki) for the FAQ and further information.
20
+
21
+## API stability
22
+
23
+Two major versions of fsnotify exist. 
24
+
25
+**[fsnotify.v1](https://gopkg.in/fsnotify.v1)** provides [a new API](https://godoc.org/gopkg.in/fsnotify.v1) based on [this design document](http://goo.gl/MrYxyA). You can import v1 with:
26
+
27
+```go
28
+import "gopkg.in/fsnotify.v1"
29
+```
30
+
31
+\* Refer to the package as fsnotify (without the .v1 suffix).
32
+
33
+**[fsnotify.v0](https://gopkg.in/fsnotify.v0)** is API-compatible with [howeyc/fsnotify](https://godoc.org/github.com/howeyc/fsnotify). Bugfixes *may* be backported, but I recommend upgrading to v1.
34
+
35
+```go
36
+import "gopkg.in/fsnotify.v0"
37
+```
38
+
39
+Further API changes are [planned](https://github.com/go-fsnotify/fsnotify/milestones), but a new major revision will be tagged, so you can depend on the v1 API.
40
+
41
+## Contributing
42
+
43
+* Send questions to [golang-dev@googlegroups.com](mailto:golang-dev@googlegroups.com). 
44
+* Request features and report bugs using the [GitHub Issue Tracker](https://github.com/go-fsnotify/fsnotify/issues).
45
+
46
+A future version of Go will have [fsnotify in the standard library](https://code.google.com/p/go/issues/detail?id=4068), therefore fsnotify carries the same [LICENSE](https://github.com/go-fsnotify/fsnotify/blob/master/LICENSE) as Go. Contributors retain their copyright, so we need you to fill out a short form before we can accept your contribution: [Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual).
47
+
48
+Please read [CONTRIBUTING](https://github.com/go-fsnotify/fsnotify/blob/master/CONTRIBUTING.md) before opening a pull request.
49
+
50
+## Example
51
+
52
+See [example_test.go](https://github.com/go-fsnotify/fsnotify/blob/master/example_test.go).
0 53
new file mode 100644
... ...
@@ -0,0 +1,42 @@
0
+// Copyright 2012 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build !plan9,!solaris
5
+
6
+package fsnotify_test
7
+
8
+import (
9
+	"log"
10
+
11
+	"gopkg.in/fsnotify.v1"
12
+)
13
+
14
+func ExampleNewWatcher() {
15
+	watcher, err := fsnotify.NewWatcher()
16
+	if err != nil {
17
+		log.Fatal(err)
18
+	}
19
+	defer watcher.Close()
20
+
21
+	done := make(chan bool)
22
+	go func() {
23
+		for {
24
+			select {
25
+			case event := <-watcher.Events:
26
+				log.Println("event:", event)
27
+				if event.Op&fsnotify.Write == fsnotify.Write {
28
+					log.Println("modified file:", event.Name)
29
+				}
30
+			case err := <-watcher.Errors:
31
+				log.Println("error:", err)
32
+			}
33
+		}
34
+	}()
35
+
36
+	err = watcher.Add("/tmp/foo")
37
+	if err != nil {
38
+		log.Fatal(err)
39
+	}
40
+	<-done
41
+}
0 42
new file mode 100644
... ...
@@ -0,0 +1,56 @@
0
+// Copyright 2012 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build !plan9,!solaris
5
+
6
+// Package fsnotify provides a platform-independent interface for file system notifications.
7
+package fsnotify
8
+
9
+import "fmt"
10
+
11
+// Event represents a single file system notification.
12
+type Event struct {
13
+	Name string // Relative path to the file or directory.
14
+	Op   Op     // File operation that triggered the event.
15
+}
16
+
17
+// Op describes a set of file operations.
18
+type Op uint32
19
+
20
+// These are the generalized file operations that can trigger a notification.
21
+const (
22
+	Create Op = 1 << iota
23
+	Write
24
+	Remove
25
+	Rename
26
+	Chmod
27
+)
28
+
29
+// String returns a string representation of the event in the form
30
+// "file: REMOVE|WRITE|..."
31
+func (e Event) String() string {
32
+	events := ""
33
+
34
+	if e.Op&Create == Create {
35
+		events += "|CREATE"
36
+	}
37
+	if e.Op&Remove == Remove {
38
+		events += "|REMOVE"
39
+	}
40
+	if e.Op&Write == Write {
41
+		events += "|WRITE"
42
+	}
43
+	if e.Op&Rename == Rename {
44
+		events += "|RENAME"
45
+	}
46
+	if e.Op&Chmod == Chmod {
47
+		events += "|CHMOD"
48
+	}
49
+
50
+	if len(events) > 0 {
51
+		events = events[1:]
52
+	}
53
+
54
+	return fmt.Sprintf("%q: %s", e.Name, events)
55
+}
0 56
new file mode 100644
... ...
@@ -0,0 +1,239 @@
0
+// Copyright 2010 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build linux
5
+
6
+package fsnotify
7
+
8
+import (
9
+	"errors"
10
+	"fmt"
11
+	"os"
12
+	"path/filepath"
13
+	"strings"
14
+	"sync"
15
+	"syscall"
16
+	"unsafe"
17
+)
18
+
19
+// Watcher watches a set of files, delivering events to a channel.
20
+type Watcher struct {
21
+	Events   chan Event
22
+	Errors   chan error
23
+	mu       sync.Mutex        // Map access
24
+	fd       int               // File descriptor (as returned by the inotify_init() syscall)
25
+	watches  map[string]*watch // Map of inotify watches (key: path)
26
+	paths    map[int]string    // Map of watched paths (key: watch descriptor)
27
+	done     chan bool         // Channel for sending a "quit message" to the reader goroutine
28
+	isClosed bool              // Set to true when Close() is first called
29
+}
30
+
31
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
32
+func NewWatcher() (*Watcher, error) {
33
+	fd, errno := syscall.InotifyInit()
34
+	if fd == -1 {
35
+		return nil, os.NewSyscallError("inotify_init", errno)
36
+	}
37
+	w := &Watcher{
38
+		fd:      fd,
39
+		watches: make(map[string]*watch),
40
+		paths:   make(map[int]string),
41
+		Events:  make(chan Event),
42
+		Errors:  make(chan error),
43
+		done:    make(chan bool, 1),
44
+	}
45
+
46
+	go w.readEvents()
47
+	return w, nil
48
+}
49
+
50
+// Close removes all watches and closes the events channel.
51
+func (w *Watcher) Close() error {
52
+	if w.isClosed {
53
+		return nil
54
+	}
55
+	w.isClosed = true
56
+
57
+	// Remove all watches
58
+	for name := range w.watches {
59
+		w.Remove(name)
60
+	}
61
+
62
+	// Send "quit" message to the reader goroutine
63
+	w.done <- true
64
+
65
+	return nil
66
+}
67
+
68
+// Add starts watching the named file or directory (non-recursively).
69
+func (w *Watcher) Add(name string) error {
70
+	name = filepath.Clean(name)
71
+	if w.isClosed {
72
+		return errors.New("inotify instance already closed")
73
+	}
74
+
75
+	const agnosticEvents = syscall.IN_MOVED_TO | syscall.IN_MOVED_FROM |
76
+		syscall.IN_CREATE | syscall.IN_ATTRIB | syscall.IN_MODIFY |
77
+		syscall.IN_MOVE_SELF | syscall.IN_DELETE | syscall.IN_DELETE_SELF
78
+
79
+	var flags uint32 = agnosticEvents
80
+
81
+	w.mu.Lock()
82
+	watchEntry, found := w.watches[name]
83
+	w.mu.Unlock()
84
+	if found {
85
+		watchEntry.flags |= flags
86
+		flags |= syscall.IN_MASK_ADD
87
+	}
88
+	wd, errno := syscall.InotifyAddWatch(w.fd, name, flags)
89
+	if wd == -1 {
90
+		return os.NewSyscallError("inotify_add_watch", errno)
91
+	}
92
+
93
+	w.mu.Lock()
94
+	w.watches[name] = &watch{wd: uint32(wd), flags: flags}
95
+	w.paths[wd] = name
96
+	w.mu.Unlock()
97
+
98
+	return nil
99
+}
100
+
101
+// Remove stops watching the the named file or directory (non-recursively).
102
+func (w *Watcher) Remove(name string) error {
103
+	name = filepath.Clean(name)
104
+	w.mu.Lock()
105
+	defer w.mu.Unlock()
106
+	watch, ok := w.watches[name]
107
+	if !ok {
108
+		return fmt.Errorf("can't remove non-existent inotify watch for: %s", name)
109
+	}
110
+	success, errno := syscall.InotifyRmWatch(w.fd, watch.wd)
111
+	if success == -1 {
112
+		return os.NewSyscallError("inotify_rm_watch", errno)
113
+	}
114
+	delete(w.watches, name)
115
+	return nil
116
+}
117
+
118
+type watch struct {
119
+	wd    uint32 // Watch descriptor (as returned by the inotify_add_watch() syscall)
120
+	flags uint32 // inotify flags of this watch (see inotify(7) for the list of valid flags)
121
+}
122
+
123
+// readEvents reads from the inotify file descriptor, converts the
124
+// received events into Event objects and sends them via the Events channel
125
+func (w *Watcher) readEvents() {
126
+	var (
127
+		buf   [syscall.SizeofInotifyEvent * 4096]byte // Buffer for a maximum of 4096 raw events
128
+		n     int                                     // Number of bytes read with read()
129
+		errno error                                   // Syscall errno
130
+	)
131
+
132
+	for {
133
+		// See if there is a message on the "done" channel
134
+		select {
135
+		case <-w.done:
136
+			syscall.Close(w.fd)
137
+			close(w.Events)
138
+			close(w.Errors)
139
+			return
140
+		default:
141
+		}
142
+
143
+		n, errno = syscall.Read(w.fd, buf[:])
144
+
145
+		// If EOF is received
146
+		if n == 0 {
147
+			syscall.Close(w.fd)
148
+			close(w.Events)
149
+			close(w.Errors)
150
+			return
151
+		}
152
+
153
+		if n < 0 {
154
+			w.Errors <- os.NewSyscallError("read", errno)
155
+			continue
156
+		}
157
+		if n < syscall.SizeofInotifyEvent {
158
+			w.Errors <- errors.New("inotify: short read in readEvents()")
159
+			continue
160
+		}
161
+
162
+		var offset uint32
163
+		// We don't know how many events we just read into the buffer
164
+		// While the offset points to at least one whole event...
165
+		for offset <= uint32(n-syscall.SizeofInotifyEvent) {
166
+			// Point "raw" to the event in the buffer
167
+			raw := (*syscall.InotifyEvent)(unsafe.Pointer(&buf[offset]))
168
+
169
+			mask := uint32(raw.Mask)
170
+			nameLen := uint32(raw.Len)
171
+			// If the event happened to the watched directory or the watched file, the kernel
172
+			// doesn't append the filename to the event, but we would like to always fill the
173
+			// the "Name" field with a valid filename. We retrieve the path of the watch from
174
+			// the "paths" map.
175
+			w.mu.Lock()
176
+			name := w.paths[int(raw.Wd)]
177
+			w.mu.Unlock()
178
+			if nameLen > 0 {
179
+				// Point "bytes" at the first byte of the filename
180
+				bytes := (*[syscall.PathMax]byte)(unsafe.Pointer(&buf[offset+syscall.SizeofInotifyEvent]))
181
+				// The filename is padded with NULL bytes. TrimRight() gets rid of those.
182
+				name += "/" + strings.TrimRight(string(bytes[0:nameLen]), "\000")
183
+			}
184
+
185
+			event := newEvent(name, mask)
186
+
187
+			// Send the events that are not ignored on the events channel
188
+			if !event.ignoreLinux(mask) {
189
+				w.Events <- event
190
+			}
191
+
192
+			// Move to the next event in the buffer
193
+			offset += syscall.SizeofInotifyEvent + nameLen
194
+		}
195
+	}
196
+}
197
+
198
+// Certain types of events can be "ignored" and not sent over the Events
199
+// channel. Such as events marked ignore by the kernel, or MODIFY events
200
+// against files that do not exist.
201
+func (e *Event) ignoreLinux(mask uint32) bool {
202
+	// Ignore anything the inotify API says to ignore
203
+	if mask&syscall.IN_IGNORED == syscall.IN_IGNORED {
204
+		return true
205
+	}
206
+
207
+	// If the event is not a DELETE or RENAME, the file must exist.
208
+	// Otherwise the event is ignored.
209
+	// *Note*: this was put in place because it was seen that a MODIFY
210
+	// event was sent after the DELETE. This ignores that MODIFY and
211
+	// assumes a DELETE will come or has come if the file doesn't exist.
212
+	if !(e.Op&Remove == Remove || e.Op&Rename == Rename) {
213
+		_, statErr := os.Lstat(e.Name)
214
+		return os.IsNotExist(statErr)
215
+	}
216
+	return false
217
+}
218
+
219
+// newEvent returns an platform-independent Event based on an inotify mask.
220
+func newEvent(name string, mask uint32) Event {
221
+	e := Event{Name: name}
222
+	if mask&syscall.IN_CREATE == syscall.IN_CREATE || mask&syscall.IN_MOVED_TO == syscall.IN_MOVED_TO {
223
+		e.Op |= Create
224
+	}
225
+	if mask&syscall.IN_DELETE_SELF == syscall.IN_DELETE_SELF || mask&syscall.IN_DELETE == syscall.IN_DELETE {
226
+		e.Op |= Remove
227
+	}
228
+	if mask&syscall.IN_MODIFY == syscall.IN_MODIFY {
229
+		e.Op |= Write
230
+	}
231
+	if mask&syscall.IN_MOVE_SELF == syscall.IN_MOVE_SELF || mask&syscall.IN_MOVED_FROM == syscall.IN_MOVED_FROM {
232
+		e.Op |= Rename
233
+	}
234
+	if mask&syscall.IN_ATTRIB == syscall.IN_ATTRIB {
235
+		e.Op |= Chmod
236
+	}
237
+	return e
238
+}
0 239
new file mode 100644
... ...
@@ -0,0 +1,1120 @@
0
+// Copyright 2010 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build !plan9,!solaris
5
+
6
+package fsnotify
7
+
8
+import (
9
+	"io/ioutil"
10
+	"os"
11
+	"os/exec"
12
+	"path/filepath"
13
+	"runtime"
14
+	"sync/atomic"
15
+	"testing"
16
+	"time"
17
+)
18
+
19
+// An atomic counter
20
+type counter struct {
21
+	val int32
22
+}
23
+
24
+func (c *counter) increment() {
25
+	atomic.AddInt32(&c.val, 1)
26
+}
27
+
28
+func (c *counter) value() int32 {
29
+	return atomic.LoadInt32(&c.val)
30
+}
31
+
32
+func (c *counter) reset() {
33
+	atomic.StoreInt32(&c.val, 0)
34
+}
35
+
36
+// tempMkdir makes a temporary directory
37
+func tempMkdir(t *testing.T) string {
38
+	dir, err := ioutil.TempDir("", "fsnotify")
39
+	if err != nil {
40
+		t.Fatalf("failed to create test directory: %s", err)
41
+	}
42
+	return dir
43
+}
44
+
45
+// newWatcher initializes an fsnotify Watcher instance.
46
+func newWatcher(t *testing.T) *Watcher {
47
+	watcher, err := NewWatcher()
48
+	if err != nil {
49
+		t.Fatalf("NewWatcher() failed: %s", err)
50
+	}
51
+	return watcher
52
+}
53
+
54
+// addWatch adds a watch for a directory
55
+func addWatch(t *testing.T, watcher *Watcher, dir string) {
56
+	if err := watcher.Add(dir); err != nil {
57
+		t.Fatalf("watcher.Add(%q) failed: %s", dir, err)
58
+	}
59
+}
60
+
61
+func TestFsnotifyMultipleOperations(t *testing.T) {
62
+	watcher := newWatcher(t)
63
+
64
+	// Receive errors on the error channel on a separate goroutine
65
+	go func() {
66
+		for err := range watcher.Errors {
67
+			t.Fatalf("error received: %s", err)
68
+		}
69
+	}()
70
+
71
+	// Create directory to watch
72
+	testDir := tempMkdir(t)
73
+	defer os.RemoveAll(testDir)
74
+
75
+	// Create directory that's not watched
76
+	testDirToMoveFiles := tempMkdir(t)
77
+	defer os.RemoveAll(testDirToMoveFiles)
78
+
79
+	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
80
+	testFileRenamed := filepath.Join(testDirToMoveFiles, "TestFsnotifySeqRename.testfile")
81
+
82
+	addWatch(t, watcher, testDir)
83
+
84
+	// Receive events on the event channel on a separate goroutine
85
+	eventstream := watcher.Events
86
+	var createReceived, modifyReceived, deleteReceived, renameReceived counter
87
+	done := make(chan bool)
88
+	go func() {
89
+		for event := range eventstream {
90
+			// Only count relevant events
91
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
92
+				t.Logf("event received: %s", event)
93
+				if event.Op&Remove == Remove {
94
+					deleteReceived.increment()
95
+				}
96
+				if event.Op&Write == Write {
97
+					modifyReceived.increment()
98
+				}
99
+				if event.Op&Create == Create {
100
+					createReceived.increment()
101
+				}
102
+				if event.Op&Rename == Rename {
103
+					renameReceived.increment()
104
+				}
105
+			} else {
106
+				t.Logf("unexpected event received: %s", event)
107
+			}
108
+		}
109
+		done <- true
110
+	}()
111
+
112
+	// Create a file
113
+	// This should add at least one event to the fsnotify event queue
114
+	var f *os.File
115
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
116
+	if err != nil {
117
+		t.Fatalf("creating test file failed: %s", err)
118
+	}
119
+	f.Sync()
120
+
121
+	time.Sleep(time.Millisecond)
122
+	f.WriteString("data")
123
+	f.Sync()
124
+	f.Close()
125
+
126
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
127
+
128
+	if err := testRename(testFile, testFileRenamed); err != nil {
129
+		t.Fatalf("rename failed: %s", err)
130
+	}
131
+
132
+	// Modify the file outside of the watched dir
133
+	f, err = os.Open(testFileRenamed)
134
+	if err != nil {
135
+		t.Fatalf("open test renamed file failed: %s", err)
136
+	}
137
+	f.WriteString("data")
138
+	f.Sync()
139
+	f.Close()
140
+
141
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
142
+
143
+	// Recreate the file that was moved
144
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
145
+	if err != nil {
146
+		t.Fatalf("creating test file failed: %s", err)
147
+	}
148
+	f.Close()
149
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
150
+
151
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
152
+	time.Sleep(500 * time.Millisecond)
153
+	cReceived := createReceived.value()
154
+	if cReceived != 2 {
155
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
156
+	}
157
+	mReceived := modifyReceived.value()
158
+	if mReceived != 1 {
159
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
160
+	}
161
+	dReceived := deleteReceived.value()
162
+	rReceived := renameReceived.value()
163
+	if dReceived+rReceived != 1 {
164
+		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", rReceived+dReceived, 1)
165
+	}
166
+
167
+	// Try closing the fsnotify instance
168
+	t.Log("calling Close()")
169
+	watcher.Close()
170
+	t.Log("waiting for the event channel to become closed...")
171
+	select {
172
+	case <-done:
173
+		t.Log("event channel closed")
174
+	case <-time.After(2 * time.Second):
175
+		t.Fatal("event stream was not closed after 2 seconds")
176
+	}
177
+}
178
+
179
+func TestFsnotifyMultipleCreates(t *testing.T) {
180
+	watcher := newWatcher(t)
181
+
182
+	// Receive errors on the error channel on a separate goroutine
183
+	go func() {
184
+		for err := range watcher.Errors {
185
+			t.Fatalf("error received: %s", err)
186
+		}
187
+	}()
188
+
189
+	// Create directory to watch
190
+	testDir := tempMkdir(t)
191
+	defer os.RemoveAll(testDir)
192
+
193
+	testFile := filepath.Join(testDir, "TestFsnotifySeq.testfile")
194
+
195
+	addWatch(t, watcher, testDir)
196
+
197
+	// Receive events on the event channel on a separate goroutine
198
+	eventstream := watcher.Events
199
+	var createReceived, modifyReceived, deleteReceived counter
200
+	done := make(chan bool)
201
+	go func() {
202
+		for event := range eventstream {
203
+			// Only count relevant events
204
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
205
+				t.Logf("event received: %s", event)
206
+				if event.Op&Remove == Remove {
207
+					deleteReceived.increment()
208
+				}
209
+				if event.Op&Create == Create {
210
+					createReceived.increment()
211
+				}
212
+				if event.Op&Write == Write {
213
+					modifyReceived.increment()
214
+				}
215
+			} else {
216
+				t.Logf("unexpected event received: %s", event)
217
+			}
218
+		}
219
+		done <- true
220
+	}()
221
+
222
+	// Create a file
223
+	// This should add at least one event to the fsnotify event queue
224
+	var f *os.File
225
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
226
+	if err != nil {
227
+		t.Fatalf("creating test file failed: %s", err)
228
+	}
229
+	f.Sync()
230
+
231
+	time.Sleep(time.Millisecond)
232
+	f.WriteString("data")
233
+	f.Sync()
234
+	f.Close()
235
+
236
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
237
+
238
+	os.Remove(testFile)
239
+
240
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
241
+
242
+	// Recreate the file
243
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
244
+	if err != nil {
245
+		t.Fatalf("creating test file failed: %s", err)
246
+	}
247
+	f.Close()
248
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
249
+
250
+	// Modify
251
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
252
+	if err != nil {
253
+		t.Fatalf("creating test file failed: %s", err)
254
+	}
255
+	f.Sync()
256
+
257
+	time.Sleep(time.Millisecond)
258
+	f.WriteString("data")
259
+	f.Sync()
260
+	f.Close()
261
+
262
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
263
+
264
+	// Modify
265
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0666)
266
+	if err != nil {
267
+		t.Fatalf("creating test file failed: %s", err)
268
+	}
269
+	f.Sync()
270
+
271
+	time.Sleep(time.Millisecond)
272
+	f.WriteString("data")
273
+	f.Sync()
274
+	f.Close()
275
+
276
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
277
+
278
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
279
+	time.Sleep(500 * time.Millisecond)
280
+	cReceived := createReceived.value()
281
+	if cReceived != 2 {
282
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
283
+	}
284
+	mReceived := modifyReceived.value()
285
+	if mReceived < 3 {
286
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs atleast %d)", mReceived, 3)
287
+	}
288
+	dReceived := deleteReceived.value()
289
+	if dReceived != 1 {
290
+		t.Fatalf("incorrect number of rename+delete events received after 500 ms (%d vs %d)", dReceived, 1)
291
+	}
292
+
293
+	// Try closing the fsnotify instance
294
+	t.Log("calling Close()")
295
+	watcher.Close()
296
+	t.Log("waiting for the event channel to become closed...")
297
+	select {
298
+	case <-done:
299
+		t.Log("event channel closed")
300
+	case <-time.After(2 * time.Second):
301
+		t.Fatal("event stream was not closed after 2 seconds")
302
+	}
303
+}
304
+
305
+func TestFsnotifyDirOnly(t *testing.T) {
306
+	watcher := newWatcher(t)
307
+
308
+	// Create directory to watch
309
+	testDir := tempMkdir(t)
310
+	defer os.RemoveAll(testDir)
311
+
312
+	// Create a file before watching directory
313
+	// This should NOT add any events to the fsnotify event queue
314
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
315
+	{
316
+		var f *os.File
317
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
318
+		if err != nil {
319
+			t.Fatalf("creating test file failed: %s", err)
320
+		}
321
+		f.Sync()
322
+		f.Close()
323
+	}
324
+
325
+	addWatch(t, watcher, testDir)
326
+
327
+	// Receive errors on the error channel on a separate goroutine
328
+	go func() {
329
+		for err := range watcher.Errors {
330
+			t.Fatalf("error received: %s", err)
331
+		}
332
+	}()
333
+
334
+	testFile := filepath.Join(testDir, "TestFsnotifyDirOnly.testfile")
335
+
336
+	// Receive events on the event channel on a separate goroutine
337
+	eventstream := watcher.Events
338
+	var createReceived, modifyReceived, deleteReceived counter
339
+	done := make(chan bool)
340
+	go func() {
341
+		for event := range eventstream {
342
+			// Only count relevant events
343
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileAlreadyExists) {
344
+				t.Logf("event received: %s", event)
345
+				if event.Op&Remove == Remove {
346
+					deleteReceived.increment()
347
+				}
348
+				if event.Op&Write == Write {
349
+					modifyReceived.increment()
350
+				}
351
+				if event.Op&Create == Create {
352
+					createReceived.increment()
353
+				}
354
+			} else {
355
+				t.Logf("unexpected event received: %s", event)
356
+			}
357
+		}
358
+		done <- true
359
+	}()
360
+
361
+	// Create a file
362
+	// This should add at least one event to the fsnotify event queue
363
+	var f *os.File
364
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
365
+	if err != nil {
366
+		t.Fatalf("creating test file failed: %s", err)
367
+	}
368
+	f.Sync()
369
+
370
+	time.Sleep(time.Millisecond)
371
+	f.WriteString("data")
372
+	f.Sync()
373
+	f.Close()
374
+
375
+	time.Sleep(50 * time.Millisecond) // give system time to sync write change before delete
376
+
377
+	os.Remove(testFile)
378
+	os.Remove(testFileAlreadyExists)
379
+
380
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
381
+	time.Sleep(500 * time.Millisecond)
382
+	cReceived := createReceived.value()
383
+	if cReceived != 1 {
384
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 1)
385
+	}
386
+	mReceived := modifyReceived.value()
387
+	if mReceived != 1 {
388
+		t.Fatalf("incorrect number of modify events received after 500 ms (%d vs %d)", mReceived, 1)
389
+	}
390
+	dReceived := deleteReceived.value()
391
+	if dReceived != 2 {
392
+		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
393
+	}
394
+
395
+	// Try closing the fsnotify instance
396
+	t.Log("calling Close()")
397
+	watcher.Close()
398
+	t.Log("waiting for the event channel to become closed...")
399
+	select {
400
+	case <-done:
401
+		t.Log("event channel closed")
402
+	case <-time.After(2 * time.Second):
403
+		t.Fatal("event stream was not closed after 2 seconds")
404
+	}
405
+}
406
+
407
+func TestFsnotifyDeleteWatchedDir(t *testing.T) {
408
+	watcher := newWatcher(t)
409
+	defer watcher.Close()
410
+
411
+	// Create directory to watch
412
+	testDir := tempMkdir(t)
413
+	defer os.RemoveAll(testDir)
414
+
415
+	// Create a file before watching directory
416
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
417
+	{
418
+		var f *os.File
419
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
420
+		if err != nil {
421
+			t.Fatalf("creating test file failed: %s", err)
422
+		}
423
+		f.Sync()
424
+		f.Close()
425
+	}
426
+
427
+	addWatch(t, watcher, testDir)
428
+
429
+	// Add a watch for testFile
430
+	addWatch(t, watcher, testFileAlreadyExists)
431
+
432
+	// Receive errors on the error channel on a separate goroutine
433
+	go func() {
434
+		for err := range watcher.Errors {
435
+			t.Fatalf("error received: %s", err)
436
+		}
437
+	}()
438
+
439
+	// Receive events on the event channel on a separate goroutine
440
+	eventstream := watcher.Events
441
+	var deleteReceived counter
442
+	go func() {
443
+		for event := range eventstream {
444
+			// Only count relevant events
445
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFileAlreadyExists) {
446
+				t.Logf("event received: %s", event)
447
+				if event.Op&Remove == Remove {
448
+					deleteReceived.increment()
449
+				}
450
+			} else {
451
+				t.Logf("unexpected event received: %s", event)
452
+			}
453
+		}
454
+	}()
455
+
456
+	os.RemoveAll(testDir)
457
+
458
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
459
+	time.Sleep(500 * time.Millisecond)
460
+	dReceived := deleteReceived.value()
461
+	if dReceived < 2 {
462
+		t.Fatalf("did not receive at least %d delete events, received %d after 500 ms", 2, dReceived)
463
+	}
464
+}
465
+
466
+func TestFsnotifySubDir(t *testing.T) {
467
+	watcher := newWatcher(t)
468
+
469
+	// Create directory to watch
470
+	testDir := tempMkdir(t)
471
+	defer os.RemoveAll(testDir)
472
+
473
+	testFile1 := filepath.Join(testDir, "TestFsnotifyFile1.testfile")
474
+	testSubDir := filepath.Join(testDir, "sub")
475
+	testSubDirFile := filepath.Join(testDir, "sub/TestFsnotifyFile1.testfile")
476
+
477
+	// Receive errors on the error channel on a separate goroutine
478
+	go func() {
479
+		for err := range watcher.Errors {
480
+			t.Fatalf("error received: %s", err)
481
+		}
482
+	}()
483
+
484
+	// Receive events on the event channel on a separate goroutine
485
+	eventstream := watcher.Events
486
+	var createReceived, deleteReceived counter
487
+	done := make(chan bool)
488
+	go func() {
489
+		for event := range eventstream {
490
+			// Only count relevant events
491
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testSubDir) || event.Name == filepath.Clean(testFile1) {
492
+				t.Logf("event received: %s", event)
493
+				if event.Op&Create == Create {
494
+					createReceived.increment()
495
+				}
496
+				if event.Op&Remove == Remove {
497
+					deleteReceived.increment()
498
+				}
499
+			} else {
500
+				t.Logf("unexpected event received: %s", event)
501
+			}
502
+		}
503
+		done <- true
504
+	}()
505
+
506
+	addWatch(t, watcher, testDir)
507
+
508
+	// Create sub-directory
509
+	if err := os.Mkdir(testSubDir, 0777); err != nil {
510
+		t.Fatalf("failed to create test sub-directory: %s", err)
511
+	}
512
+
513
+	// Create a file
514
+	var f *os.File
515
+	f, err := os.OpenFile(testFile1, os.O_WRONLY|os.O_CREATE, 0666)
516
+	if err != nil {
517
+		t.Fatalf("creating test file failed: %s", err)
518
+	}
519
+	f.Sync()
520
+	f.Close()
521
+
522
+	// Create a file (Should not see this! we are not watching subdir)
523
+	var fs *os.File
524
+	fs, err = os.OpenFile(testSubDirFile, os.O_WRONLY|os.O_CREATE, 0666)
525
+	if err != nil {
526
+		t.Fatalf("creating test file failed: %s", err)
527
+	}
528
+	fs.Sync()
529
+	fs.Close()
530
+
531
+	time.Sleep(200 * time.Millisecond)
532
+
533
+	// Make sure receive deletes for both file and sub-directory
534
+	os.RemoveAll(testSubDir)
535
+	os.Remove(testFile1)
536
+
537
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
538
+	time.Sleep(500 * time.Millisecond)
539
+	cReceived := createReceived.value()
540
+	if cReceived != 2 {
541
+		t.Fatalf("incorrect number of create events received after 500 ms (%d vs %d)", cReceived, 2)
542
+	}
543
+	dReceived := deleteReceived.value()
544
+	if dReceived != 2 {
545
+		t.Fatalf("incorrect number of delete events received after 500 ms (%d vs %d)", dReceived, 2)
546
+	}
547
+
548
+	// Try closing the fsnotify instance
549
+	t.Log("calling Close()")
550
+	watcher.Close()
551
+	t.Log("waiting for the event channel to become closed...")
552
+	select {
553
+	case <-done:
554
+		t.Log("event channel closed")
555
+	case <-time.After(2 * time.Second):
556
+		t.Fatal("event stream was not closed after 2 seconds")
557
+	}
558
+}
559
+
560
+func TestFsnotifyRename(t *testing.T) {
561
+	watcher := newWatcher(t)
562
+
563
+	// Create directory to watch
564
+	testDir := tempMkdir(t)
565
+	defer os.RemoveAll(testDir)
566
+
567
+	addWatch(t, watcher, testDir)
568
+
569
+	// Receive errors on the error channel on a separate goroutine
570
+	go func() {
571
+		for err := range watcher.Errors {
572
+			t.Fatalf("error received: %s", err)
573
+		}
574
+	}()
575
+
576
+	testFile := filepath.Join(testDir, "TestFsnotifyEvents.testfile")
577
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
578
+
579
+	// Receive events on the event channel on a separate goroutine
580
+	eventstream := watcher.Events
581
+	var renameReceived counter
582
+	done := make(chan bool)
583
+	go func() {
584
+		for event := range eventstream {
585
+			// Only count relevant events
586
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
587
+				if event.Op&Rename == Rename {
588
+					renameReceived.increment()
589
+				}
590
+				t.Logf("event received: %s", event)
591
+			} else {
592
+				t.Logf("unexpected event received: %s", event)
593
+			}
594
+		}
595
+		done <- true
596
+	}()
597
+
598
+	// Create a file
599
+	// This should add at least one event to the fsnotify event queue
600
+	var f *os.File
601
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
602
+	if err != nil {
603
+		t.Fatalf("creating test file failed: %s", err)
604
+	}
605
+	f.Sync()
606
+
607
+	f.WriteString("data")
608
+	f.Sync()
609
+	f.Close()
610
+
611
+	// Add a watch for testFile
612
+	addWatch(t, watcher, testFile)
613
+
614
+	if err := testRename(testFile, testFileRenamed); err != nil {
615
+		t.Fatalf("rename failed: %s", err)
616
+	}
617
+
618
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
619
+	time.Sleep(500 * time.Millisecond)
620
+	if renameReceived.value() == 0 {
621
+		t.Fatal("fsnotify rename events have not been received after 500 ms")
622
+	}
623
+
624
+	// Try closing the fsnotify instance
625
+	t.Log("calling Close()")
626
+	watcher.Close()
627
+	t.Log("waiting for the event channel to become closed...")
628
+	select {
629
+	case <-done:
630
+		t.Log("event channel closed")
631
+	case <-time.After(2 * time.Second):
632
+		t.Fatal("event stream was not closed after 2 seconds")
633
+	}
634
+
635
+	os.Remove(testFileRenamed)
636
+}
637
+
638
+func TestFsnotifyRenameToCreate(t *testing.T) {
639
+	watcher := newWatcher(t)
640
+
641
+	// Create directory to watch
642
+	testDir := tempMkdir(t)
643
+	defer os.RemoveAll(testDir)
644
+
645
+	// Create directory to get file
646
+	testDirFrom := tempMkdir(t)
647
+	defer os.RemoveAll(testDirFrom)
648
+
649
+	addWatch(t, watcher, testDir)
650
+
651
+	// Receive errors on the error channel on a separate goroutine
652
+	go func() {
653
+		for err := range watcher.Errors {
654
+			t.Fatalf("error received: %s", err)
655
+		}
656
+	}()
657
+
658
+	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
659
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
660
+
661
+	// Receive events on the event channel on a separate goroutine
662
+	eventstream := watcher.Events
663
+	var createReceived counter
664
+	done := make(chan bool)
665
+	go func() {
666
+		for event := range eventstream {
667
+			// Only count relevant events
668
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) || event.Name == filepath.Clean(testFileRenamed) {
669
+				if event.Op&Create == Create {
670
+					createReceived.increment()
671
+				}
672
+				t.Logf("event received: %s", event)
673
+			} else {
674
+				t.Logf("unexpected event received: %s", event)
675
+			}
676
+		}
677
+		done <- true
678
+	}()
679
+
680
+	// Create a file
681
+	// This should add at least one event to the fsnotify event queue
682
+	var f *os.File
683
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
684
+	if err != nil {
685
+		t.Fatalf("creating test file failed: %s", err)
686
+	}
687
+	f.Sync()
688
+	f.Close()
689
+
690
+	if err := testRename(testFile, testFileRenamed); err != nil {
691
+		t.Fatalf("rename failed: %s", err)
692
+	}
693
+
694
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
695
+	time.Sleep(500 * time.Millisecond)
696
+	if createReceived.value() == 0 {
697
+		t.Fatal("fsnotify create events have not been received after 500 ms")
698
+	}
699
+
700
+	// Try closing the fsnotify instance
701
+	t.Log("calling Close()")
702
+	watcher.Close()
703
+	t.Log("waiting for the event channel to become closed...")
704
+	select {
705
+	case <-done:
706
+		t.Log("event channel closed")
707
+	case <-time.After(2 * time.Second):
708
+		t.Fatal("event stream was not closed after 2 seconds")
709
+	}
710
+
711
+	os.Remove(testFileRenamed)
712
+}
713
+
714
+func TestFsnotifyRenameToOverwrite(t *testing.T) {
715
+	switch runtime.GOOS {
716
+	case "plan9", "windows":
717
+		t.Skipf("skipping test on %q (os.Rename over existing file does not create event).", runtime.GOOS)
718
+	}
719
+
720
+	watcher := newWatcher(t)
721
+
722
+	// Create directory to watch
723
+	testDir := tempMkdir(t)
724
+	defer os.RemoveAll(testDir)
725
+
726
+	// Create directory to get file
727
+	testDirFrom := tempMkdir(t)
728
+	defer os.RemoveAll(testDirFrom)
729
+
730
+	testFile := filepath.Join(testDirFrom, "TestFsnotifyEvents.testfile")
731
+	testFileRenamed := filepath.Join(testDir, "TestFsnotifyEvents.testfileRenamed")
732
+
733
+	// Create a file
734
+	var fr *os.File
735
+	fr, err := os.OpenFile(testFileRenamed, os.O_WRONLY|os.O_CREATE, 0666)
736
+	if err != nil {
737
+		t.Fatalf("creating test file failed: %s", err)
738
+	}
739
+	fr.Sync()
740
+	fr.Close()
741
+
742
+	addWatch(t, watcher, testDir)
743
+
744
+	// Receive errors on the error channel on a separate goroutine
745
+	go func() {
746
+		for err := range watcher.Errors {
747
+			t.Fatalf("error received: %s", err)
748
+		}
749
+	}()
750
+
751
+	// Receive events on the event channel on a separate goroutine
752
+	eventstream := watcher.Events
753
+	var eventReceived counter
754
+	done := make(chan bool)
755
+	go func() {
756
+		for event := range eventstream {
757
+			// Only count relevant events
758
+			if event.Name == filepath.Clean(testFileRenamed) {
759
+				eventReceived.increment()
760
+				t.Logf("event received: %s", event)
761
+			} else {
762
+				t.Logf("unexpected event received: %s", event)
763
+			}
764
+		}
765
+		done <- true
766
+	}()
767
+
768
+	// Create a file
769
+	// This should add at least one event to the fsnotify event queue
770
+	var f *os.File
771
+	f, err = os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
772
+	if err != nil {
773
+		t.Fatalf("creating test file failed: %s", err)
774
+	}
775
+	f.Sync()
776
+	f.Close()
777
+
778
+	if err := testRename(testFile, testFileRenamed); err != nil {
779
+		t.Fatalf("rename failed: %s", err)
780
+	}
781
+
782
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
783
+	time.Sleep(500 * time.Millisecond)
784
+	if eventReceived.value() == 0 {
785
+		t.Fatal("fsnotify events have not been received after 500 ms")
786
+	}
787
+
788
+	// Try closing the fsnotify instance
789
+	t.Log("calling Close()")
790
+	watcher.Close()
791
+	t.Log("waiting for the event channel to become closed...")
792
+	select {
793
+	case <-done:
794
+		t.Log("event channel closed")
795
+	case <-time.After(2 * time.Second):
796
+		t.Fatal("event stream was not closed after 2 seconds")
797
+	}
798
+
799
+	os.Remove(testFileRenamed)
800
+}
801
+
802
+func TestRemovalOfWatch(t *testing.T) {
803
+	// Create directory to watch
804
+	testDir := tempMkdir(t)
805
+	defer os.RemoveAll(testDir)
806
+
807
+	// Create a file before watching directory
808
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
809
+	{
810
+		var f *os.File
811
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
812
+		if err != nil {
813
+			t.Fatalf("creating test file failed: %s", err)
814
+		}
815
+		f.Sync()
816
+		f.Close()
817
+	}
818
+
819
+	watcher := newWatcher(t)
820
+	defer watcher.Close()
821
+
822
+	addWatch(t, watcher, testDir)
823
+	if err := watcher.Remove(testDir); err != nil {
824
+		t.Fatalf("Could not remove the watch: %v\n", err)
825
+	}
826
+
827
+	go func() {
828
+		select {
829
+		case ev := <-watcher.Events:
830
+			t.Fatalf("We received event: %v\n", ev)
831
+		case <-time.After(500 * time.Millisecond):
832
+			t.Log("No event received, as expected.")
833
+		}
834
+	}()
835
+
836
+	time.Sleep(200 * time.Millisecond)
837
+	// Modify the file outside of the watched dir
838
+	f, err := os.Open(testFileAlreadyExists)
839
+	if err != nil {
840
+		t.Fatalf("Open test file failed: %s", err)
841
+	}
842
+	f.WriteString("data")
843
+	f.Sync()
844
+	f.Close()
845
+	if err := os.Chmod(testFileAlreadyExists, 0700); err != nil {
846
+		t.Fatalf("chmod failed: %s", err)
847
+	}
848
+	time.Sleep(400 * time.Millisecond)
849
+}
850
+
851
+func TestFsnotifyAttrib(t *testing.T) {
852
+	if runtime.GOOS == "windows" {
853
+		t.Skip("attributes don't work on Windows.")
854
+	}
855
+
856
+	watcher := newWatcher(t)
857
+
858
+	// Create directory to watch
859
+	testDir := tempMkdir(t)
860
+	defer os.RemoveAll(testDir)
861
+
862
+	// Receive errors on the error channel on a separate goroutine
863
+	go func() {
864
+		for err := range watcher.Errors {
865
+			t.Fatalf("error received: %s", err)
866
+		}
867
+	}()
868
+
869
+	testFile := filepath.Join(testDir, "TestFsnotifyAttrib.testfile")
870
+
871
+	// Receive events on the event channel on a separate goroutine
872
+	eventstream := watcher.Events
873
+	// The modifyReceived counter counts IsModify events that are not IsAttrib,
874
+	// and the attribReceived counts IsAttrib events (which are also IsModify as
875
+	// a consequence).
876
+	var modifyReceived counter
877
+	var attribReceived counter
878
+	done := make(chan bool)
879
+	go func() {
880
+		for event := range eventstream {
881
+			// Only count relevant events
882
+			if event.Name == filepath.Clean(testDir) || event.Name == filepath.Clean(testFile) {
883
+				if event.Op&Write == Write {
884
+					modifyReceived.increment()
885
+				}
886
+				if event.Op&Chmod == Chmod {
887
+					attribReceived.increment()
888
+				}
889
+				t.Logf("event received: %s", event)
890
+			} else {
891
+				t.Logf("unexpected event received: %s", event)
892
+			}
893
+		}
894
+		done <- true
895
+	}()
896
+
897
+	// Create a file
898
+	// This should add at least one event to the fsnotify event queue
899
+	var f *os.File
900
+	f, err := os.OpenFile(testFile, os.O_WRONLY|os.O_CREATE, 0666)
901
+	if err != nil {
902
+		t.Fatalf("creating test file failed: %s", err)
903
+	}
904
+	f.Sync()
905
+
906
+	f.WriteString("data")
907
+	f.Sync()
908
+	f.Close()
909
+
910
+	// Add a watch for testFile
911
+	addWatch(t, watcher, testFile)
912
+
913
+	if err := os.Chmod(testFile, 0700); err != nil {
914
+		t.Fatalf("chmod failed: %s", err)
915
+	}
916
+
917
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
918
+	// Creating/writing a file changes also the mtime, so IsAttrib should be set to true here
919
+	time.Sleep(500 * time.Millisecond)
920
+	if modifyReceived.value() != 0 {
921
+		t.Fatal("received an unexpected modify event when creating a test file")
922
+	}
923
+	if attribReceived.value() == 0 {
924
+		t.Fatal("fsnotify attribute events have not received after 500 ms")
925
+	}
926
+
927
+	// Modifying the contents of the file does not set the attrib flag (although eg. the mtime
928
+	// might have been modified).
929
+	modifyReceived.reset()
930
+	attribReceived.reset()
931
+
932
+	f, err = os.OpenFile(testFile, os.O_WRONLY, 0)
933
+	if err != nil {
934
+		t.Fatalf("reopening test file failed: %s", err)
935
+	}
936
+
937
+	f.WriteString("more data")
938
+	f.Sync()
939
+	f.Close()
940
+
941
+	time.Sleep(500 * time.Millisecond)
942
+
943
+	if modifyReceived.value() != 1 {
944
+		t.Fatal("didn't receive a modify event after changing test file contents")
945
+	}
946
+
947
+	if attribReceived.value() != 0 {
948
+		t.Fatal("did receive an unexpected attrib event after changing test file contents")
949
+	}
950
+
951
+	modifyReceived.reset()
952
+	attribReceived.reset()
953
+
954
+	// Doing a chmod on the file should trigger an event with the "attrib" flag set (the contents
955
+	// of the file are not changed though)
956
+	if err := os.Chmod(testFile, 0600); err != nil {
957
+		t.Fatalf("chmod failed: %s", err)
958
+	}
959
+
960
+	time.Sleep(500 * time.Millisecond)
961
+
962
+	if attribReceived.value() != 1 {
963
+		t.Fatal("didn't receive an attribute change after 500ms")
964
+	}
965
+
966
+	// Try closing the fsnotify instance
967
+	t.Log("calling Close()")
968
+	watcher.Close()
969
+	t.Log("waiting for the event channel to become closed...")
970
+	select {
971
+	case <-done:
972
+		t.Log("event channel closed")
973
+	case <-time.After(1e9):
974
+		t.Fatal("event stream was not closed after 1 second")
975
+	}
976
+
977
+	os.Remove(testFile)
978
+}
979
+
980
+func TestFsnotifyClose(t *testing.T) {
981
+	watcher := newWatcher(t)
982
+	watcher.Close()
983
+
984
+	var done int32
985
+	go func() {
986
+		watcher.Close()
987
+		atomic.StoreInt32(&done, 1)
988
+	}()
989
+
990
+	time.Sleep(50e6) // 50 ms
991
+	if atomic.LoadInt32(&done) == 0 {
992
+		t.Fatal("double Close() test failed: second Close() call didn't return")
993
+	}
994
+
995
+	testDir := tempMkdir(t)
996
+	defer os.RemoveAll(testDir)
997
+
998
+	if err := watcher.Add(testDir); err == nil {
999
+		t.Fatal("expected error on Watch() after Close(), got nil")
1000
+	}
1001
+}
1002
+
1003
+func TestFsnotifyFakeSymlink(t *testing.T) {
1004
+	if runtime.GOOS == "windows" {
1005
+		t.Skip("symlinks don't work on Windows.")
1006
+	}
1007
+
1008
+	watcher := newWatcher(t)
1009
+
1010
+	// Create directory to watch
1011
+	testDir := tempMkdir(t)
1012
+	defer os.RemoveAll(testDir)
1013
+
1014
+	var errorsReceived counter
1015
+	// Receive errors on the error channel on a separate goroutine
1016
+	go func() {
1017
+		for errors := range watcher.Errors {
1018
+			t.Logf("Received error: %s", errors)
1019
+			errorsReceived.increment()
1020
+		}
1021
+	}()
1022
+
1023
+	// Count the CREATE events received
1024
+	var createEventsReceived, otherEventsReceived counter
1025
+	go func() {
1026
+		for ev := range watcher.Events {
1027
+			t.Logf("event received: %s", ev)
1028
+			if ev.Op&Create == Create {
1029
+				createEventsReceived.increment()
1030
+			} else {
1031
+				otherEventsReceived.increment()
1032
+			}
1033
+		}
1034
+	}()
1035
+
1036
+	addWatch(t, watcher, testDir)
1037
+
1038
+	if err := os.Symlink(filepath.Join(testDir, "zzz"), filepath.Join(testDir, "zzznew")); err != nil {
1039
+		t.Fatalf("Failed to create bogus symlink: %s", err)
1040
+	}
1041
+	t.Logf("Created bogus symlink")
1042
+
1043
+	// We expect this event to be received almost immediately, but let's wait 500 ms to be sure
1044
+	time.Sleep(500 * time.Millisecond)
1045
+
1046
+	// Should not be error, just no events for broken links (watching nothing)
1047
+	if errorsReceived.value() > 0 {
1048
+		t.Fatal("fsnotify errors have been received.")
1049
+	}
1050
+	if otherEventsReceived.value() > 0 {
1051
+		t.Fatal("fsnotify other events received on the broken link")
1052
+	}
1053
+
1054
+	// Except for 1 create event (for the link itself)
1055
+	if createEventsReceived.value() == 0 {
1056
+		t.Fatal("fsnotify create events were not received after 500 ms")
1057
+	}
1058
+	if createEventsReceived.value() > 1 {
1059
+		t.Fatal("fsnotify more create events received than expected")
1060
+	}
1061
+
1062
+	// Try closing the fsnotify instance
1063
+	t.Log("calling Close()")
1064
+	watcher.Close()
1065
+}
1066
+
1067
+// TestConcurrentRemovalOfWatch tests that concurrent calls to RemoveWatch do not race.
1068
+// See https://codereview.appspot.com/103300045/
1069
+// go test -test.run=TestConcurrentRemovalOfWatch -test.cpu=1,1,1,1,1 -race
1070
+func TestConcurrentRemovalOfWatch(t *testing.T) {
1071
+	if runtime.GOOS != "darwin" {
1072
+		t.Skip("regression test for race only present on darwin")
1073
+	}
1074
+
1075
+	// Create directory to watch
1076
+	testDir := tempMkdir(t)
1077
+	defer os.RemoveAll(testDir)
1078
+
1079
+	// Create a file before watching directory
1080
+	testFileAlreadyExists := filepath.Join(testDir, "TestFsnotifyEventsExisting.testfile")
1081
+	{
1082
+		var f *os.File
1083
+		f, err := os.OpenFile(testFileAlreadyExists, os.O_WRONLY|os.O_CREATE, 0666)
1084
+		if err != nil {
1085
+			t.Fatalf("creating test file failed: %s", err)
1086
+		}
1087
+		f.Sync()
1088
+		f.Close()
1089
+	}
1090
+
1091
+	watcher := newWatcher(t)
1092
+	defer watcher.Close()
1093
+
1094
+	addWatch(t, watcher, testDir)
1095
+
1096
+	// Test that RemoveWatch can be invoked concurrently, with no data races.
1097
+	removed1 := make(chan struct{})
1098
+	go func() {
1099
+		defer close(removed1)
1100
+		watcher.Remove(testDir)
1101
+	}()
1102
+	removed2 := make(chan struct{})
1103
+	go func() {
1104
+		close(removed2)
1105
+		watcher.Remove(testDir)
1106
+	}()
1107
+	<-removed1
1108
+	<-removed2
1109
+}
1110
+
1111
+func testRename(file1, file2 string) error {
1112
+	switch runtime.GOOS {
1113
+	case "windows", "plan9":
1114
+		return os.Rename(file1, file2)
1115
+	default:
1116
+		cmd := exec.Command("mv", file1, file2)
1117
+		return cmd.Run()
1118
+	}
1119
+}
0 1120
new file mode 100644
... ...
@@ -0,0 +1,479 @@
0
+// Copyright 2010 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build freebsd openbsd netbsd dragonfly darwin
5
+
6
+package fsnotify
7
+
8
+import (
9
+	"errors"
10
+	"fmt"
11
+	"io/ioutil"
12
+	"os"
13
+	"path/filepath"
14
+	"sync"
15
+	"syscall"
16
+)
17
+
18
+// Watcher watches a set of files, delivering events to a channel.
19
+type Watcher struct {
20
+	Events          chan Event
21
+	Errors          chan error
22
+	mu              sync.Mutex          // Mutex for the Watcher itself.
23
+	kq              int                 // File descriptor (as returned by the kqueue() syscall).
24
+	watches         map[string]int      // Map of watched file descriptors (key: path).
25
+	wmut            sync.Mutex          // Protects access to watches.
26
+	enFlags         map[string]uint32   // Map of watched files to evfilt note flags used in kqueue.
27
+	enmut           sync.Mutex          // Protects access to enFlags.
28
+	paths           map[int]string      // Map of watched paths (key: watch descriptor).
29
+	finfo           map[int]os.FileInfo // Map of file information (isDir, isReg; key: watch descriptor).
30
+	pmut            sync.Mutex          // Protects access to paths and finfo.
31
+	fileExists      map[string]bool     // Keep track of if we know this file exists (to stop duplicate create events).
32
+	femut           sync.Mutex          // Protects access to fileExists.
33
+	externalWatches map[string]bool     // Map of watches added by user of the library.
34
+	ewmut           sync.Mutex          // Protects access to externalWatches.
35
+	done            chan bool           // Channel for sending a "quit message" to the reader goroutine
36
+	isClosed        bool                // Set to true when Close() is first called
37
+}
38
+
39
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
40
+func NewWatcher() (*Watcher, error) {
41
+	fd, errno := syscall.Kqueue()
42
+	if fd == -1 {
43
+		return nil, os.NewSyscallError("kqueue", errno)
44
+	}
45
+	w := &Watcher{
46
+		kq:              fd,
47
+		watches:         make(map[string]int),
48
+		enFlags:         make(map[string]uint32),
49
+		paths:           make(map[int]string),
50
+		finfo:           make(map[int]os.FileInfo),
51
+		fileExists:      make(map[string]bool),
52
+		externalWatches: make(map[string]bool),
53
+		Events:          make(chan Event),
54
+		Errors:          make(chan error),
55
+		done:            make(chan bool, 1),
56
+	}
57
+
58
+	go w.readEvents()
59
+	return w, nil
60
+}
61
+
62
+// Close removes all watches and closes the events channel.
63
+func (w *Watcher) Close() error {
64
+	w.mu.Lock()
65
+	if w.isClosed {
66
+		w.mu.Unlock()
67
+		return nil
68
+	}
69
+	w.isClosed = true
70
+	w.mu.Unlock()
71
+
72
+	// Send "quit" message to the reader goroutine:
73
+	w.done <- true
74
+	w.wmut.Lock()
75
+	ws := w.watches
76
+	w.wmut.Unlock()
77
+	for name := range ws {
78
+		w.Remove(name)
79
+	}
80
+
81
+	return nil
82
+}
83
+
84
+// Add starts watching the named file or directory (non-recursively).
85
+func (w *Watcher) Add(name string) error {
86
+	w.ewmut.Lock()
87
+	w.externalWatches[name] = true
88
+	w.ewmut.Unlock()
89
+	return w.addWatch(name, noteAllEvents)
90
+}
91
+
92
+// Remove stops watching the the named file or directory (non-recursively).
93
+func (w *Watcher) Remove(name string) error {
94
+	name = filepath.Clean(name)
95
+	w.wmut.Lock()
96
+	watchfd, ok := w.watches[name]
97
+	w.wmut.Unlock()
98
+	if !ok {
99
+		return fmt.Errorf("can't remove non-existent kevent watch for: %s", name)
100
+	}
101
+	var kbuf [1]syscall.Kevent_t
102
+	watchEntry := &kbuf[0]
103
+	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_DELETE)
104
+	entryFlags := watchEntry.Flags
105
+	success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
106
+	if success == -1 {
107
+		return os.NewSyscallError("kevent_rm_watch", errno)
108
+	} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
109
+		return errors.New("kevent rm error")
110
+	}
111
+	syscall.Close(watchfd)
112
+	w.wmut.Lock()
113
+	delete(w.watches, name)
114
+	w.wmut.Unlock()
115
+	w.enmut.Lock()
116
+	delete(w.enFlags, name)
117
+	w.enmut.Unlock()
118
+	w.pmut.Lock()
119
+	delete(w.paths, watchfd)
120
+	fInfo := w.finfo[watchfd]
121
+	delete(w.finfo, watchfd)
122
+	w.pmut.Unlock()
123
+
124
+	// Find all watched paths that are in this directory that are not external.
125
+	if fInfo.IsDir() {
126
+		var pathsToRemove []string
127
+		w.pmut.Lock()
128
+		for _, wpath := range w.paths {
129
+			wdir, _ := filepath.Split(wpath)
130
+			if filepath.Clean(wdir) == filepath.Clean(name) {
131
+				w.ewmut.Lock()
132
+				if !w.externalWatches[wpath] {
133
+					pathsToRemove = append(pathsToRemove, wpath)
134
+				}
135
+				w.ewmut.Unlock()
136
+			}
137
+		}
138
+		w.pmut.Unlock()
139
+		for _, name := range pathsToRemove {
140
+			// Since these are internal, not much sense in propagating error
141
+			// to the user, as that will just confuse them with an error about
142
+			// a path they did not explicitly watch themselves.
143
+			w.Remove(name)
144
+		}
145
+	}
146
+
147
+	return nil
148
+}
149
+
150
+const (
151
+	// Watch all events (except NOTE_EXTEND, NOTE_LINK, NOTE_REVOKE)
152
+	noteAllEvents = syscall.NOTE_DELETE | syscall.NOTE_WRITE | syscall.NOTE_ATTRIB | syscall.NOTE_RENAME
153
+
154
+	// Block for 100 ms on each call to kevent
155
+	keventWaitTime = 100e6
156
+)
157
+
158
+// addWatch adds path to the watched file set.
159
+// The flags are interpreted as described in kevent(2).
160
+func (w *Watcher) addWatch(path string, flags uint32) error {
161
+	path = filepath.Clean(path)
162
+	w.mu.Lock()
163
+	if w.isClosed {
164
+		w.mu.Unlock()
165
+		return errors.New("kevent instance already closed")
166
+	}
167
+	w.mu.Unlock()
168
+
169
+	watchDir := false
170
+
171
+	w.wmut.Lock()
172
+	watchfd, found := w.watches[path]
173
+	w.wmut.Unlock()
174
+	if !found {
175
+		fi, errstat := os.Lstat(path)
176
+		if errstat != nil {
177
+			return errstat
178
+		}
179
+
180
+		// don't watch socket
181
+		if fi.Mode()&os.ModeSocket == os.ModeSocket {
182
+			return nil
183
+		}
184
+
185
+		// Follow Symlinks
186
+		// Unfortunately, Linux can add bogus symlinks to watch list without
187
+		// issue, and Windows can't do symlinks period (AFAIK). To  maintain
188
+		// consistency, we will act like everything is fine. There will simply
189
+		// be no file events for broken symlinks.
190
+		// Hence the returns of nil on errors.
191
+		if fi.Mode()&os.ModeSymlink == os.ModeSymlink {
192
+			path, err := filepath.EvalSymlinks(path)
193
+			if err != nil {
194
+				return nil
195
+			}
196
+
197
+			fi, errstat = os.Lstat(path)
198
+			if errstat != nil {
199
+				return nil
200
+			}
201
+		}
202
+
203
+		fd, errno := syscall.Open(path, openMode, 0700)
204
+		if fd == -1 {
205
+			return os.NewSyscallError("Open", errno)
206
+		}
207
+		watchfd = fd
208
+
209
+		w.wmut.Lock()
210
+		w.watches[path] = watchfd
211
+		w.wmut.Unlock()
212
+
213
+		w.pmut.Lock()
214
+		w.paths[watchfd] = path
215
+		w.finfo[watchfd] = fi
216
+		w.pmut.Unlock()
217
+	}
218
+	// Watch the directory if it has not been watched before.
219
+	w.pmut.Lock()
220
+	w.enmut.Lock()
221
+	if w.finfo[watchfd].IsDir() &&
222
+		(flags&syscall.NOTE_WRITE) == syscall.NOTE_WRITE &&
223
+		(!found || (w.enFlags[path]&syscall.NOTE_WRITE) != syscall.NOTE_WRITE) {
224
+		watchDir = true
225
+	}
226
+	w.enmut.Unlock()
227
+	w.pmut.Unlock()
228
+
229
+	w.enmut.Lock()
230
+	w.enFlags[path] = flags
231
+	w.enmut.Unlock()
232
+
233
+	var kbuf [1]syscall.Kevent_t
234
+	watchEntry := &kbuf[0]
235
+	watchEntry.Fflags = flags
236
+	syscall.SetKevent(watchEntry, watchfd, syscall.EVFILT_VNODE, syscall.EV_ADD|syscall.EV_CLEAR)
237
+	entryFlags := watchEntry.Flags
238
+	success, errno := syscall.Kevent(w.kq, kbuf[:], nil, nil)
239
+	if success == -1 {
240
+		return errno
241
+	} else if (entryFlags & syscall.EV_ERROR) == syscall.EV_ERROR {
242
+		return errors.New("kevent add error")
243
+	}
244
+
245
+	if watchDir {
246
+		errdir := w.watchDirectoryFiles(path)
247
+		if errdir != nil {
248
+			return errdir
249
+		}
250
+	}
251
+	return nil
252
+}
253
+
254
+// readEvents reads from the kqueue file descriptor, converts the
255
+// received events into Event objects and sends them via the Events channel
256
+func (w *Watcher) readEvents() {
257
+	var (
258
+		keventbuf [10]syscall.Kevent_t // Event buffer
259
+		kevents   []syscall.Kevent_t   // Received events
260
+		twait     *syscall.Timespec    // Time to block waiting for events
261
+		n         int                  // Number of events returned from kevent
262
+		errno     error                // Syscall errno
263
+	)
264
+	kevents = keventbuf[0:0]
265
+	twait = new(syscall.Timespec)
266
+	*twait = syscall.NsecToTimespec(keventWaitTime)
267
+
268
+	for {
269
+		// See if there is a message on the "done" channel
270
+		var done bool
271
+		select {
272
+		case done = <-w.done:
273
+		default:
274
+		}
275
+
276
+		// If "done" message is received
277
+		if done {
278
+			errno := syscall.Close(w.kq)
279
+			if errno != nil {
280
+				w.Errors <- os.NewSyscallError("close", errno)
281
+			}
282
+			close(w.Events)
283
+			close(w.Errors)
284
+			return
285
+		}
286
+
287
+		// Get new events
288
+		if len(kevents) == 0 {
289
+			n, errno = syscall.Kevent(w.kq, nil, keventbuf[:], twait)
290
+
291
+			// EINTR is okay, basically the syscall was interrupted before
292
+			// timeout expired.
293
+			if errno != nil && errno != syscall.EINTR {
294
+				w.Errors <- os.NewSyscallError("kevent", errno)
295
+				continue
296
+			}
297
+
298
+			// Received some events
299
+			if n > 0 {
300
+				kevents = keventbuf[0:n]
301
+			}
302
+		}
303
+
304
+		// Flush the events we received to the Events channel
305
+		for len(kevents) > 0 {
306
+			watchEvent := &kevents[0]
307
+			mask := uint32(watchEvent.Fflags)
308
+			w.pmut.Lock()
309
+			name := w.paths[int(watchEvent.Ident)]
310
+			fileInfo := w.finfo[int(watchEvent.Ident)]
311
+			w.pmut.Unlock()
312
+
313
+			event := newEvent(name, mask, false)
314
+
315
+			if fileInfo != nil && fileInfo.IsDir() && !(event.Op&Remove == Remove) {
316
+				// Double check to make sure the directory exist. This can happen when
317
+				// we do a rm -fr on a recursively watched folders and we receive a
318
+				// modification event first but the folder has been deleted and later
319
+				// receive the delete event
320
+				if _, err := os.Lstat(event.Name); os.IsNotExist(err) {
321
+					// mark is as delete event
322
+					event.Op |= Remove
323
+				}
324
+			}
325
+
326
+			if fileInfo != nil && fileInfo.IsDir() && event.Op&Write == Write && !(event.Op&Remove == Remove) {
327
+				w.sendDirectoryChangeEvents(event.Name)
328
+			} else {
329
+				// Send the event on the Events channel
330
+				w.Events <- event
331
+			}
332
+
333
+			// Move to next event
334
+			kevents = kevents[1:]
335
+
336
+			if event.Op&Rename == Rename {
337
+				w.Remove(event.Name)
338
+				w.femut.Lock()
339
+				delete(w.fileExists, event.Name)
340
+				w.femut.Unlock()
341
+			}
342
+			if event.Op&Remove == Remove {
343
+				w.Remove(event.Name)
344
+				w.femut.Lock()
345
+				delete(w.fileExists, event.Name)
346
+				w.femut.Unlock()
347
+
348
+				// Look for a file that may have overwritten this
349
+				// (ie mv f1 f2 will delete f2 then create f2)
350
+				fileDir, _ := filepath.Split(event.Name)
351
+				fileDir = filepath.Clean(fileDir)
352
+				w.wmut.Lock()
353
+				_, found := w.watches[fileDir]
354
+				w.wmut.Unlock()
355
+				if found {
356
+					// make sure the directory exist before we watch for changes. When we
357
+					// do a recursive watch and perform rm -fr, the parent directory might
358
+					// have gone missing, ignore the missing directory and let the
359
+					// upcoming delete event remove the watch form the parent folder
360
+					if _, err := os.Lstat(fileDir); !os.IsNotExist(err) {
361
+						w.sendDirectoryChangeEvents(fileDir)
362
+					}
363
+				}
364
+			}
365
+		}
366
+	}
367
+}
368
+
369
+// newEvent returns an platform-independent Event based on kqueue Fflags.
370
+func newEvent(name string, mask uint32, create bool) Event {
371
+	e := Event{Name: name}
372
+	if create {
373
+		e.Op |= Create
374
+	}
375
+	if mask&syscall.NOTE_DELETE == syscall.NOTE_DELETE {
376
+		e.Op |= Remove
377
+	}
378
+	if mask&syscall.NOTE_WRITE == syscall.NOTE_WRITE {
379
+		e.Op |= Write
380
+	}
381
+	if mask&syscall.NOTE_RENAME == syscall.NOTE_RENAME {
382
+		e.Op |= Rename
383
+	}
384
+	if mask&syscall.NOTE_ATTRIB == syscall.NOTE_ATTRIB {
385
+		e.Op |= Chmod
386
+	}
387
+	return e
388
+}
389
+
390
+func (w *Watcher) watchDirectoryFiles(dirPath string) error {
391
+	// Get all files
392
+	files, err := ioutil.ReadDir(dirPath)
393
+	if err != nil {
394
+		return err
395
+	}
396
+
397
+	// Search for new files
398
+	for _, fileInfo := range files {
399
+		filePath := filepath.Join(dirPath, fileInfo.Name())
400
+
401
+		if fileInfo.IsDir() == false {
402
+			// Watch file to mimic linux fsnotify
403
+			e := w.addWatch(filePath, noteAllEvents)
404
+			if e != nil {
405
+				return e
406
+			}
407
+		} else {
408
+			// If the user is currently watching directory
409
+			// we want to preserve the flags used
410
+			w.enmut.Lock()
411
+			currFlags, found := w.enFlags[filePath]
412
+			w.enmut.Unlock()
413
+			var newFlags uint32 = syscall.NOTE_DELETE
414
+			if found {
415
+				newFlags |= currFlags
416
+			}
417
+
418
+			// Linux gives deletes if not explicitly watching
419
+			e := w.addWatch(filePath, newFlags)
420
+			if e != nil {
421
+				return e
422
+			}
423
+		}
424
+		w.femut.Lock()
425
+		w.fileExists[filePath] = true
426
+		w.femut.Unlock()
427
+	}
428
+
429
+	return nil
430
+}
431
+
432
+// sendDirectoryEvents searches the directory for newly created files
433
+// and sends them over the event channel. This functionality is to have
434
+// the BSD version of fsnotify match linux fsnotify which provides a
435
+// create event for files created in a watched directory.
436
+func (w *Watcher) sendDirectoryChangeEvents(dirPath string) {
437
+	// Get all files
438
+	files, err := ioutil.ReadDir(dirPath)
439
+	if err != nil {
440
+		w.Errors <- err
441
+	}
442
+
443
+	// Search for new files
444
+	for _, fileInfo := range files {
445
+		filePath := filepath.Join(dirPath, fileInfo.Name())
446
+		w.femut.Lock()
447
+		_, doesExist := w.fileExists[filePath]
448
+		w.femut.Unlock()
449
+		if !doesExist {
450
+			// Send create event (mask=0)
451
+			event := newEvent(filePath, 0, true)
452
+			w.Events <- event
453
+		}
454
+
455
+		// watchDirectoryFiles (but without doing another ReadDir)
456
+		if fileInfo.IsDir() == false {
457
+			// Watch file to mimic linux fsnotify
458
+			w.addWatch(filePath, noteAllEvents)
459
+		} else {
460
+			// If the user is currently watching directory
461
+			// we want to preserve the flags used
462
+			w.enmut.Lock()
463
+			currFlags, found := w.enFlags[filePath]
464
+			w.enmut.Unlock()
465
+			var newFlags uint32 = syscall.NOTE_DELETE
466
+			if found {
467
+				newFlags |= currFlags
468
+			}
469
+
470
+			// Linux gives deletes if not explicitly watching
471
+			w.addWatch(filePath, newFlags)
472
+		}
473
+
474
+		w.femut.Lock()
475
+		w.fileExists[filePath] = true
476
+		w.femut.Unlock()
477
+	}
478
+}
0 479
new file mode 100644
... ...
@@ -0,0 +1,11 @@
0
+// Copyright 2013 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build freebsd openbsd netbsd dragonfly
5
+
6
+package fsnotify
7
+
8
+import "syscall"
9
+
10
+const openMode = syscall.O_NONBLOCK | syscall.O_RDONLY
0 11
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+// Copyright 2013 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build darwin
5
+
6
+package fsnotify
7
+
8
+import "syscall"
9
+
10
+// note: this constant is not defined on BSD
11
+const openMode = syscall.O_EVTONLY
0 12
new file mode 100644
... ...
@@ -0,0 +1,561 @@
0
+// Copyright 2011 The Go Authors. All rights reserved.
1
+// Use of this source code is governed by a BSD-style
2
+// license that can be found in the LICENSE file.
3
+
4
+// +build windows
5
+
6
+package fsnotify
7
+
8
+import (
9
+	"errors"
10
+	"fmt"
11
+	"os"
12
+	"path/filepath"
13
+	"runtime"
14
+	"sync"
15
+	"syscall"
16
+	"unsafe"
17
+)
18
+
19
+// Watcher watches a set of files, delivering events to a channel.
20
+type Watcher struct {
21
+	Events   chan Event
22
+	Errors   chan error
23
+	isClosed bool           // Set to true when Close() is first called
24
+	mu       sync.Mutex     // Map access
25
+	port     syscall.Handle // Handle to completion port
26
+	watches  watchMap       // Map of watches (key: i-number)
27
+	input    chan *input    // Inputs to the reader are sent on this channel
28
+	quit     chan chan<- error
29
+}
30
+
31
+// NewWatcher establishes a new watcher with the underlying OS and begins waiting for events.
32
+func NewWatcher() (*Watcher, error) {
33
+	port, e := syscall.CreateIoCompletionPort(syscall.InvalidHandle, 0, 0, 0)
34
+	if e != nil {
35
+		return nil, os.NewSyscallError("CreateIoCompletionPort", e)
36
+	}
37
+	w := &Watcher{
38
+		port:    port,
39
+		watches: make(watchMap),
40
+		input:   make(chan *input, 1),
41
+		Events:  make(chan Event, 50),
42
+		Errors:  make(chan error),
43
+		quit:    make(chan chan<- error, 1),
44
+	}
45
+	go w.readEvents()
46
+	return w, nil
47
+}
48
+
49
+// Close removes all watches and closes the events channel.
50
+func (w *Watcher) Close() error {
51
+	if w.isClosed {
52
+		return nil
53
+	}
54
+	w.isClosed = true
55
+
56
+	// Send "quit" message to the reader goroutine
57
+	ch := make(chan error)
58
+	w.quit <- ch
59
+	if err := w.wakeupReader(); err != nil {
60
+		return err
61
+	}
62
+	return <-ch
63
+}
64
+
65
+// Add starts watching the named file or directory (non-recursively).
66
+func (w *Watcher) Add(name string) error {
67
+	if w.isClosed {
68
+		return errors.New("watcher already closed")
69
+	}
70
+	in := &input{
71
+		op:    opAddWatch,
72
+		path:  filepath.Clean(name),
73
+		flags: sys_FS_ALL_EVENTS,
74
+		reply: make(chan error),
75
+	}
76
+	w.input <- in
77
+	if err := w.wakeupReader(); err != nil {
78
+		return err
79
+	}
80
+	return <-in.reply
81
+}
82
+
83
+// Remove stops watching the the named file or directory (non-recursively).
84
+func (w *Watcher) Remove(name string) error {
85
+	in := &input{
86
+		op:    opRemoveWatch,
87
+		path:  filepath.Clean(name),
88
+		reply: make(chan error),
89
+	}
90
+	w.input <- in
91
+	if err := w.wakeupReader(); err != nil {
92
+		return err
93
+	}
94
+	return <-in.reply
95
+}
96
+
97
+const (
98
+	// Options for AddWatch
99
+	sys_FS_ONESHOT = 0x80000000
100
+	sys_FS_ONLYDIR = 0x1000000
101
+
102
+	// Events
103
+	sys_FS_ACCESS      = 0x1
104
+	sys_FS_ALL_EVENTS  = 0xfff
105
+	sys_FS_ATTRIB      = 0x4
106
+	sys_FS_CLOSE       = 0x18
107
+	sys_FS_CREATE      = 0x100
108
+	sys_FS_DELETE      = 0x200
109
+	sys_FS_DELETE_SELF = 0x400
110
+	sys_FS_MODIFY      = 0x2
111
+	sys_FS_MOVE        = 0xc0
112
+	sys_FS_MOVED_FROM  = 0x40
113
+	sys_FS_MOVED_TO    = 0x80
114
+	sys_FS_MOVE_SELF   = 0x800
115
+
116
+	// Special events
117
+	sys_FS_IGNORED    = 0x8000
118
+	sys_FS_Q_OVERFLOW = 0x4000
119
+)
120
+
121
+func newEvent(name string, mask uint32) Event {
122
+	e := Event{Name: name}
123
+	if mask&sys_FS_CREATE == sys_FS_CREATE || mask&sys_FS_MOVED_TO == sys_FS_MOVED_TO {
124
+		e.Op |= Create
125
+	}
126
+	if mask&sys_FS_DELETE == sys_FS_DELETE || mask&sys_FS_DELETE_SELF == sys_FS_DELETE_SELF {
127
+		e.Op |= Remove
128
+	}
129
+	if mask&sys_FS_MODIFY == sys_FS_MODIFY {
130
+		e.Op |= Write
131
+	}
132
+	if mask&sys_FS_MOVE == sys_FS_MOVE || mask&sys_FS_MOVE_SELF == sys_FS_MOVE_SELF || mask&sys_FS_MOVED_FROM == sys_FS_MOVED_FROM {
133
+		e.Op |= Rename
134
+	}
135
+	if mask&sys_FS_ATTRIB == sys_FS_ATTRIB {
136
+		e.Op |= Chmod
137
+	}
138
+	return e
139
+}
140
+
141
+const (
142
+	opAddWatch = iota
143
+	opRemoveWatch
144
+)
145
+
146
+const (
147
+	provisional uint64 = 1 << (32 + iota)
148
+)
149
+
150
+type input struct {
151
+	op    int
152
+	path  string
153
+	flags uint32
154
+	reply chan error
155
+}
156
+
157
+type inode struct {
158
+	handle syscall.Handle
159
+	volume uint32
160
+	index  uint64
161
+}
162
+
163
+type watch struct {
164
+	ov     syscall.Overlapped
165
+	ino    *inode            // i-number
166
+	path   string            // Directory path
167
+	mask   uint64            // Directory itself is being watched with these notify flags
168
+	names  map[string]uint64 // Map of names being watched and their notify flags
169
+	rename string            // Remembers the old name while renaming a file
170
+	buf    [4096]byte
171
+}
172
+
173
+type indexMap map[uint64]*watch
174
+type watchMap map[uint32]indexMap
175
+
176
+func (w *Watcher) wakeupReader() error {
177
+	e := syscall.PostQueuedCompletionStatus(w.port, 0, 0, nil)
178
+	if e != nil {
179
+		return os.NewSyscallError("PostQueuedCompletionStatus", e)
180
+	}
181
+	return nil
182
+}
183
+
184
+func getDir(pathname string) (dir string, err error) {
185
+	attr, e := syscall.GetFileAttributes(syscall.StringToUTF16Ptr(pathname))
186
+	if e != nil {
187
+		return "", os.NewSyscallError("GetFileAttributes", e)
188
+	}
189
+	if attr&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 {
190
+		dir = pathname
191
+	} else {
192
+		dir, _ = filepath.Split(pathname)
193
+		dir = filepath.Clean(dir)
194
+	}
195
+	return
196
+}
197
+
198
+func getIno(path string) (ino *inode, err error) {
199
+	h, e := syscall.CreateFile(syscall.StringToUTF16Ptr(path),
200
+		syscall.FILE_LIST_DIRECTORY,
201
+		syscall.FILE_SHARE_READ|syscall.FILE_SHARE_WRITE|syscall.FILE_SHARE_DELETE,
202
+		nil, syscall.OPEN_EXISTING,
203
+		syscall.FILE_FLAG_BACKUP_SEMANTICS|syscall.FILE_FLAG_OVERLAPPED, 0)
204
+	if e != nil {
205
+		return nil, os.NewSyscallError("CreateFile", e)
206
+	}
207
+	var fi syscall.ByHandleFileInformation
208
+	if e = syscall.GetFileInformationByHandle(h, &fi); e != nil {
209
+		syscall.CloseHandle(h)
210
+		return nil, os.NewSyscallError("GetFileInformationByHandle", e)
211
+	}
212
+	ino = &inode{
213
+		handle: h,
214
+		volume: fi.VolumeSerialNumber,
215
+		index:  uint64(fi.FileIndexHigh)<<32 | uint64(fi.FileIndexLow),
216
+	}
217
+	return ino, nil
218
+}
219
+
220
+// Must run within the I/O thread.
221
+func (m watchMap) get(ino *inode) *watch {
222
+	if i := m[ino.volume]; i != nil {
223
+		return i[ino.index]
224
+	}
225
+	return nil
226
+}
227
+
228
+// Must run within the I/O thread.
229
+func (m watchMap) set(ino *inode, watch *watch) {
230
+	i := m[ino.volume]
231
+	if i == nil {
232
+		i = make(indexMap)
233
+		m[ino.volume] = i
234
+	}
235
+	i[ino.index] = watch
236
+}
237
+
238
+// Must run within the I/O thread.
239
+func (w *Watcher) addWatch(pathname string, flags uint64) error {
240
+	dir, err := getDir(pathname)
241
+	if err != nil {
242
+		return err
243
+	}
244
+	if flags&sys_FS_ONLYDIR != 0 && pathname != dir {
245
+		return nil
246
+	}
247
+	ino, err := getIno(dir)
248
+	if err != nil {
249
+		return err
250
+	}
251
+	w.mu.Lock()
252
+	watchEntry := w.watches.get(ino)
253
+	w.mu.Unlock()
254
+	if watchEntry == nil {
255
+		if _, e := syscall.CreateIoCompletionPort(ino.handle, w.port, 0, 0); e != nil {
256
+			syscall.CloseHandle(ino.handle)
257
+			return os.NewSyscallError("CreateIoCompletionPort", e)
258
+		}
259
+		watchEntry = &watch{
260
+			ino:   ino,
261
+			path:  dir,
262
+			names: make(map[string]uint64),
263
+		}
264
+		w.mu.Lock()
265
+		w.watches.set(ino, watchEntry)
266
+		w.mu.Unlock()
267
+		flags |= provisional
268
+	} else {
269
+		syscall.CloseHandle(ino.handle)
270
+	}
271
+	if pathname == dir {
272
+		watchEntry.mask |= flags
273
+	} else {
274
+		watchEntry.names[filepath.Base(pathname)] |= flags
275
+	}
276
+	if err = w.startRead(watchEntry); err != nil {
277
+		return err
278
+	}
279
+	if pathname == dir {
280
+		watchEntry.mask &= ^provisional
281
+	} else {
282
+		watchEntry.names[filepath.Base(pathname)] &= ^provisional
283
+	}
284
+	return nil
285
+}
286
+
287
+// Must run within the I/O thread.
288
+func (w *Watcher) remWatch(pathname string) error {
289
+	dir, err := getDir(pathname)
290
+	if err != nil {
291
+		return err
292
+	}
293
+	ino, err := getIno(dir)
294
+	if err != nil {
295
+		return err
296
+	}
297
+	w.mu.Lock()
298
+	watch := w.watches.get(ino)
299
+	w.mu.Unlock()
300
+	if watch == nil {
301
+		return fmt.Errorf("can't remove non-existent watch for: %s", pathname)
302
+	}
303
+	if pathname == dir {
304
+		w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
305
+		watch.mask = 0
306
+	} else {
307
+		name := filepath.Base(pathname)
308
+		w.sendEvent(watch.path+"\\"+name, watch.names[name]&sys_FS_IGNORED)
309
+		delete(watch.names, name)
310
+	}
311
+	return w.startRead(watch)
312
+}
313
+
314
+// Must run within the I/O thread.
315
+func (w *Watcher) deleteWatch(watch *watch) {
316
+	for name, mask := range watch.names {
317
+		if mask&provisional == 0 {
318
+			w.sendEvent(watch.path+"\\"+name, mask&sys_FS_IGNORED)
319
+		}
320
+		delete(watch.names, name)
321
+	}
322
+	if watch.mask != 0 {
323
+		if watch.mask&provisional == 0 {
324
+			w.sendEvent(watch.path, watch.mask&sys_FS_IGNORED)
325
+		}
326
+		watch.mask = 0
327
+	}
328
+}
329
+
330
+// Must run within the I/O thread.
331
+func (w *Watcher) startRead(watch *watch) error {
332
+	if e := syscall.CancelIo(watch.ino.handle); e != nil {
333
+		w.Errors <- os.NewSyscallError("CancelIo", e)
334
+		w.deleteWatch(watch)
335
+	}
336
+	mask := toWindowsFlags(watch.mask)
337
+	for _, m := range watch.names {
338
+		mask |= toWindowsFlags(m)
339
+	}
340
+	if mask == 0 {
341
+		if e := syscall.CloseHandle(watch.ino.handle); e != nil {
342
+			w.Errors <- os.NewSyscallError("CloseHandle", e)
343
+		}
344
+		w.mu.Lock()
345
+		delete(w.watches[watch.ino.volume], watch.ino.index)
346
+		w.mu.Unlock()
347
+		return nil
348
+	}
349
+	e := syscall.ReadDirectoryChanges(watch.ino.handle, &watch.buf[0],
350
+		uint32(unsafe.Sizeof(watch.buf)), false, mask, nil, &watch.ov, 0)
351
+	if e != nil {
352
+		err := os.NewSyscallError("ReadDirectoryChanges", e)
353
+		if e == syscall.ERROR_ACCESS_DENIED && watch.mask&provisional == 0 {
354
+			// Watched directory was probably removed
355
+			if w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF) {
356
+				if watch.mask&sys_FS_ONESHOT != 0 {
357
+					watch.mask = 0
358
+				}
359
+			}
360
+			err = nil
361
+		}
362
+		w.deleteWatch(watch)
363
+		w.startRead(watch)
364
+		return err
365
+	}
366
+	return nil
367
+}
368
+
369
+// readEvents reads from the I/O completion port, converts the
370
+// received events into Event objects and sends them via the Events channel.
371
+// Entry point to the I/O thread.
372
+func (w *Watcher) readEvents() {
373
+	var (
374
+		n, key uint32
375
+		ov     *syscall.Overlapped
376
+	)
377
+	runtime.LockOSThread()
378
+
379
+	for {
380
+		e := syscall.GetQueuedCompletionStatus(w.port, &n, &key, &ov, syscall.INFINITE)
381
+		watch := (*watch)(unsafe.Pointer(ov))
382
+
383
+		if watch == nil {
384
+			select {
385
+			case ch := <-w.quit:
386
+				w.mu.Lock()
387
+				var indexes []indexMap
388
+				for _, index := range w.watches {
389
+					indexes = append(indexes, index)
390
+				}
391
+				w.mu.Unlock()
392
+				for _, index := range indexes {
393
+					for _, watch := range index {
394
+						w.deleteWatch(watch)
395
+						w.startRead(watch)
396
+					}
397
+				}
398
+				var err error
399
+				if e := syscall.CloseHandle(w.port); e != nil {
400
+					err = os.NewSyscallError("CloseHandle", e)
401
+				}
402
+				close(w.Events)
403
+				close(w.Errors)
404
+				ch <- err
405
+				return
406
+			case in := <-w.input:
407
+				switch in.op {
408
+				case opAddWatch:
409
+					in.reply <- w.addWatch(in.path, uint64(in.flags))
410
+				case opRemoveWatch:
411
+					in.reply <- w.remWatch(in.path)
412
+				}
413
+			default:
414
+			}
415
+			continue
416
+		}
417
+
418
+		switch e {
419
+		case syscall.ERROR_MORE_DATA:
420
+			if watch == nil {
421
+				w.Errors <- errors.New("ERROR_MORE_DATA has unexpectedly null lpOverlapped buffer")
422
+			} else {
423
+				// The i/o succeeded but the buffer is full.
424
+				// In theory we should be building up a full packet.
425
+				// In practice we can get away with just carrying on.
426
+				n = uint32(unsafe.Sizeof(watch.buf))
427
+			}
428
+		case syscall.ERROR_ACCESS_DENIED:
429
+			// Watched directory was probably removed
430
+			w.sendEvent(watch.path, watch.mask&sys_FS_DELETE_SELF)
431
+			w.deleteWatch(watch)
432
+			w.startRead(watch)
433
+			continue
434
+		case syscall.ERROR_OPERATION_ABORTED:
435
+			// CancelIo was called on this handle
436
+			continue
437
+		default:
438
+			w.Errors <- os.NewSyscallError("GetQueuedCompletionPort", e)
439
+			continue
440
+		case nil:
441
+		}
442
+
443
+		var offset uint32
444
+		for {
445
+			if n == 0 {
446
+				w.Events <- newEvent("", sys_FS_Q_OVERFLOW)
447
+				w.Errors <- errors.New("short read in readEvents()")
448
+				break
449
+			}
450
+
451
+			// Point "raw" to the event in the buffer
452
+			raw := (*syscall.FileNotifyInformation)(unsafe.Pointer(&watch.buf[offset]))
453
+			buf := (*[syscall.MAX_PATH]uint16)(unsafe.Pointer(&raw.FileName))
454
+			name := syscall.UTF16ToString(buf[:raw.FileNameLength/2])
455
+			fullname := watch.path + "\\" + name
456
+
457
+			var mask uint64
458
+			switch raw.Action {
459
+			case syscall.FILE_ACTION_REMOVED:
460
+				mask = sys_FS_DELETE_SELF
461
+			case syscall.FILE_ACTION_MODIFIED:
462
+				mask = sys_FS_MODIFY
463
+			case syscall.FILE_ACTION_RENAMED_OLD_NAME:
464
+				watch.rename = name
465
+			case syscall.FILE_ACTION_RENAMED_NEW_NAME:
466
+				if watch.names[watch.rename] != 0 {
467
+					watch.names[name] |= watch.names[watch.rename]
468
+					delete(watch.names, watch.rename)
469
+					mask = sys_FS_MOVE_SELF
470
+				}
471
+			}
472
+
473
+			sendNameEvent := func() {
474
+				if w.sendEvent(fullname, watch.names[name]&mask) {
475
+					if watch.names[name]&sys_FS_ONESHOT != 0 {
476
+						delete(watch.names, name)
477
+					}
478
+				}
479
+			}
480
+			if raw.Action != syscall.FILE_ACTION_RENAMED_NEW_NAME {
481
+				sendNameEvent()
482
+			}
483
+			if raw.Action == syscall.FILE_ACTION_REMOVED {
484
+				w.sendEvent(fullname, watch.names[name]&sys_FS_IGNORED)
485
+				delete(watch.names, name)
486
+			}
487
+			if w.sendEvent(fullname, watch.mask&toFSnotifyFlags(raw.Action)) {
488
+				if watch.mask&sys_FS_ONESHOT != 0 {
489
+					watch.mask = 0
490
+				}
491
+			}
492
+			if raw.Action == syscall.FILE_ACTION_RENAMED_NEW_NAME {
493
+				fullname = watch.path + "\\" + watch.rename
494
+				sendNameEvent()
495
+			}
496
+
497
+			// Move to the next event in the buffer
498
+			if raw.NextEntryOffset == 0 {
499
+				break
500
+			}
501
+			offset += raw.NextEntryOffset
502
+
503
+			// Error!
504
+			if offset >= n {
505
+				w.Errors <- errors.New("Windows system assumed buffer larger than it is, events have likely been missed.")
506
+				break
507
+			}
508
+		}
509
+
510
+		if err := w.startRead(watch); err != nil {
511
+			w.Errors <- err
512
+		}
513
+	}
514
+}
515
+
516
+func (w *Watcher) sendEvent(name string, mask uint64) bool {
517
+	if mask == 0 {
518
+		return false
519
+	}
520
+	event := newEvent(name, uint32(mask))
521
+	select {
522
+	case ch := <-w.quit:
523
+		w.quit <- ch
524
+	case w.Events <- event:
525
+	}
526
+	return true
527
+}
528
+
529
+func toWindowsFlags(mask uint64) uint32 {
530
+	var m uint32
531
+	if mask&sys_FS_ACCESS != 0 {
532
+		m |= syscall.FILE_NOTIFY_CHANGE_LAST_ACCESS
533
+	}
534
+	if mask&sys_FS_MODIFY != 0 {
535
+		m |= syscall.FILE_NOTIFY_CHANGE_LAST_WRITE
536
+	}
537
+	if mask&sys_FS_ATTRIB != 0 {
538
+		m |= syscall.FILE_NOTIFY_CHANGE_ATTRIBUTES
539
+	}
540
+	if mask&(sys_FS_MOVE|sys_FS_CREATE|sys_FS_DELETE) != 0 {
541
+		m |= syscall.FILE_NOTIFY_CHANGE_FILE_NAME | syscall.FILE_NOTIFY_CHANGE_DIR_NAME
542
+	}
543
+	return m
544
+}
545
+
546
+func toFSnotifyFlags(action uint32) uint64 {
547
+	switch action {
548
+	case syscall.FILE_ACTION_ADDED:
549
+		return sys_FS_CREATE
550
+	case syscall.FILE_ACTION_REMOVED:
551
+		return sys_FS_DELETE
552
+	case syscall.FILE_ACTION_MODIFIED:
553
+		return sys_FS_MODIFY
554
+	case syscall.FILE_ACTION_RENAMED_OLD_NAME:
555
+		return sys_FS_MOVED_FROM
556
+	case syscall.FILE_ACTION_RENAMED_NEW_NAME:
557
+		return sys_FS_MOVED_TO
558
+	}
559
+	return 0
560
+}