"veyron/lib/filelocker": TryLock

Adds TryLock function to the filelocker. This will be used in trying
to lock the veyron credentials directory during rt.Init/rt.New.

Change-Id: I7bb4f4585f550186c125c9b0b3fd0ba656c7a83b
diff --git a/lib/filelocker/locker.go b/lib/filelocker/locker.go
index 5e6674f..43dfd5f 100644
--- a/lib/filelocker/locker.go
+++ b/lib/filelocker/locker.go
@@ -18,7 +18,7 @@
 // Lock locks the provided file.
 //
 // If the file is already locked then the calling goroutine
-// blocks until the lock can be acquired.
+// blocks until the lock can be grabbed.
 //
 // The file must exist otherwise an error is returned.
 func Lock(filepath string) (Unlocker, error) {
@@ -32,6 +32,21 @@
 	return &unlocker{file}, nil
 }
 
+// TryLock tries to grab a lock on the provided file and
+// returns an error if it fails. This function is non-blocking.
+//
+// The file must exist otherwise an error is returned.
+func TryLock(filepath string) (Unlocker, error) {
+	file, err := os.Open(filepath)
+	if err != nil {
+		return nil, err
+	}
+	if err = syscall.Flock(int(file.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
+		return nil, err
+	}
+	return &unlocker{file}, nil
+}
+
 // unlocker implements Unlocker.
 type unlocker struct {
 	file *os.File
diff --git a/lib/filelocker/locker_test.go b/lib/filelocker/locker_test.go
index 0060210..f3ce658 100644
--- a/lib/filelocker/locker_test.go
+++ b/lib/filelocker/locker_test.go
@@ -6,6 +6,7 @@
 	"io"
 	"io/ioutil"
 	"os"
+	"syscall"
 	"testing"
 	"time"
 
@@ -14,7 +15,7 @@
 )
 
 func init() {
-	modules.RegisterChild("testLockUnlockChild", "", testLockUnlockChild)
+	modules.RegisterChild("testLockChild", "", testLockChild)
 }
 
 func TestHelperProcess(t *testing.T) {
@@ -39,7 +40,7 @@
 	}
 }
 
-func testLockUnlockChild(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
+func testLockChild(stdin io.Reader, stdout, stderr io.Writer, env map[string]string, args ...string) error {
 	// Lock the file
 	unlocker, err := Lock(args[1])
 	if err != nil {
@@ -58,13 +59,13 @@
 	return nil
 }
 
-func TestLockUnlockInterProcess(t *testing.T) {
+func TestLockInterProcess(t *testing.T) {
 	filepath := newFile()
 	defer os.Remove(filepath)
 
 	sh := modules.NewShell()
 	defer sh.Cleanup(os.Stderr, os.Stderr)
-	h, err := sh.Start("testLockUnlockChild", nil, filepath)
+	h, err := sh.Start("testLockChild", nil, filepath)
 	if err != nil {
 		t.Fatalf("sh.Start failed: %v", err)
 	}
@@ -97,7 +98,7 @@
 	s.ExpectEOF()
 }
 
-func TestLockUnlockIntraProcess(t *testing.T) {
+func TestLockIntraProcess(t *testing.T) {
 	filepath := newFile()
 	defer os.Remove(filepath)
 
@@ -127,3 +128,33 @@
 		t.Fatal("Another goroutine failed to grab the lock after this goroutine released it")
 	}
 }
+
+func TestTryLock(t *testing.T) {
+	filepath := newFile()
+	defer os.Remove(filepath)
+
+	sh := modules.NewShell()
+	defer sh.Cleanup(os.Stderr, os.Stderr)
+	h, err := sh.Start("testLockChild", nil, filepath)
+	if err != nil {
+		t.Fatalf("sh.Start failed: %v", err)
+	}
+	s := expect.NewSession(t, h.Stdout(), time.Minute)
+
+	// Test that child grabbed the lock.
+	s.Expect("locked")
+
+	// Test that parent cannot grab the lock, and then send a message
+	// to the child to release the lock.
+	if _, err := TryLock(filepath); err != syscall.EWOULDBLOCK {
+		t.Fatal("TryLock returned error: %v, want: %v", err, syscall.EWOULDBLOCK)
+	}
+
+	// Test that the parent can grab the lock after the child has released it.
+	h.Stdin().Write([]byte("unlock\n"))
+	s.Expect("unlocked")
+	if _, err = TryLock(filepath); err != nil {
+		t.Fatalf("TryLock failed: %v", err)
+	}
+	s.ExpectEOF()
+}