add Getwd, Fchdir, tests

R=r
DELTA=215  (186 added, 0 deleted, 29 changed)
OCL=28968
CL=28995
diff --git a/src/lib/os/Makefile b/src/lib/os/Makefile
index 02863cc..50a06d9 100644
--- a/src/lib/os/Makefile
+++ b/src/lib/os/Makefile
@@ -3,7 +3,7 @@
 # license that can be found in the LICENSE file.
 
 # DO NOT EDIT.  Automatically generated by gobuild.
-# gobuild -m dir_${GOARCH}_${GOOS}.go env.go error.go file.go path.go proc_${GOOS}.go stat_${GOARCH}_${GOOS}.go time.go types.go exec.go user.go >Makefile
+# gobuild -m dir_${GOARCH}_${GOOS}.go env.go error.go file.go path.go proc_${GOOS}.go stat_${GOARCH}_${GOOS}.go time.go types.go exec.go user.go getwd.go >Makefile
 
 D=
 
@@ -56,6 +56,7 @@
 O4=\
 	dir_$(GOARCH)_$(GOOS).$O\
 	exec.$O\
+	getwd.$O\
 	path.$O\
 
 
@@ -75,7 +76,7 @@
 	rm -f $(O3)
 
 a4: $(O4)
-	$(AR) grc _obj$D/os.a dir_$(GOARCH)_$(GOOS).$O exec.$O path.$O
+	$(AR) grc _obj$D/os.a dir_$(GOARCH)_$(GOOS).$O exec.$O getwd.$O path.$O
 	rm -f $(O4)
 
 
diff --git a/src/lib/os/error.go b/src/lib/os/error.go
index 53f58c9..7784656 100644
--- a/src/lib/os/error.go
+++ b/src/lib/os/error.go
@@ -79,5 +79,6 @@
 	ERANGE Error = Errno(syscall.ERANGE);
 	EADDRINUSE Error = Errno(syscall.EADDRINUSE);
 	ECONNREFUSED Error = Errno(syscall.ECONNREFUSED);
+	ENAMETOOLONG Error = Errno(syscall.ENAMETOOLONG);
 )
 
diff --git a/src/lib/os/file.go b/src/lib/os/file.go
index d658073..7aa6632 100644
--- a/src/lib/os/file.go
+++ b/src/lib/os/file.go
@@ -284,6 +284,13 @@
 	return ErrnoToError(e);
 }
 
+// Chdir changes the current working directory to the file,
+// which must be a directory.
+func (f *File) Chdir() Error {
+	r, e := syscall.Fchdir(f.fd);
+	return ErrnoToError(e);
+}
+
 // Remove removes the named file or directory.
 func Remove(name string) Error {
 	// System call interface forces us to know
diff --git a/src/lib/os/getwd.go b/src/lib/os/getwd.go
new file mode 100644
index 0000000..2d7b754
--- /dev/null
+++ b/src/lib/os/getwd.go
@@ -0,0 +1,94 @@
+// Copyright 2009 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package os
+
+import (
+	"os";
+	"syscall"
+)
+
+// Getwd returns a rooted path name corresponding to the
+// current directory.  If the current directory can be
+// reached via multiple paths (due to symbolic links),
+// Getwd may return any one of them.
+func Getwd() (string, Error) {
+	// If the operating system provides a Getwd call, use it.
+	if syscall.ImplementsGetwd {
+		s, e := syscall.Getwd();
+		return s, ErrnoToError(e);
+	}
+
+	// Otherwise, we're trying to find our way back to ".".
+	dot, err := Stat(".");
+	if err != nil {
+		return "", err;
+	}
+
+	// Clumsy but widespread kludge:
+	// if $PWD is set and matches ".", use it.
+	pwd, _ := Getenv("PWD");
+	if len(pwd) > 0 && pwd[0] == '/' {
+		d, err := Stat(pwd);
+		if err == nil && d.Dev == dot.Dev && d.Ino == dot.Ino {
+			return pwd, nil
+		}
+	}
+
+	// Root is a special case because it has no parent
+	// and ends in a slash.
+	root, err := Stat("/");
+	if err != nil {
+		// Can't stat root - no hope.
+		return "", err;
+	}
+	if root.Dev == dot.Dev && root.Ino == dot.Ino {
+		return "/", nil
+	}
+
+	// General algorithm: find name in parent
+	// and then find name of parent.  Each iteration
+	// adds /name to the beginning of pwd.
+	elem := make([]string, 0, 16);
+	pwd = "";
+	for parent := "..";; parent = "../" + parent {
+		if len(parent) >= 1024 {	// Sanity check
+			return "", ENAMETOOLONG;
+		}
+		fd, err := Open(parent, O_RDONLY, 0);
+		if err != nil {
+			return "", err;
+		}
+
+		for {
+			names, err := fd.Readdirnames(100);
+			if err != nil {
+				fd.Close();
+				return "", err;
+			}
+			for i, name := range names {
+				d, err := Lstat(parent + "/" + name);
+				if d.Dev == dot.Dev && d.Ino == dot.Ino {
+					pwd = "/" + name + pwd;
+					goto Found;
+				}
+			}
+		}
+		fd.Close();
+		return "", ENOENT;
+
+	Found:
+		pd, err := fd.Stat();
+		if err != nil {
+			return "", err;
+		}
+		fd.Close();
+		if pd.Dev == root.Dev && pd.Ino == root.Ino {
+			break;
+		}
+		// Set up for next round.
+		dot = pd;
+	}
+	return pwd, nil
+}
diff --git a/src/lib/os/os_test.go b/src/lib/os/os_test.go
index b291fd8..e4d115d 100644
--- a/src/lib/os/os_test.go
+++ b/src/lib/os/os_test.go
@@ -316,7 +316,7 @@
 	if err != nil {
 		t.Fatalf("Pipe: %v", err);
 	}
-	pid, err := ForkExec("/bin/pwd", []string{"pwd"}, nil, "/", []*File{nil, w, os.Stderr});
+	pid, err := ForkExec("/bin/pwd", []string{"pwd"}, nil, "/", []*File{nil, w, Stderr});
 	if err != nil {
 		t.Fatalf("ForkExec: %v", err);
 	}
@@ -345,12 +345,12 @@
 func TestChmod(t *testing.T) {
 	MkdirAll("_obj", 0777);
 	const Path = "_obj/_TestChmod_";
-	fd, err := os.Open(Path, os.O_WRONLY | os.O_CREAT, 0666);
+	fd, err := Open(Path, O_WRONLY | O_CREAT, 0666);
 	if err != nil {
 		t.Fatalf("create %s: %s", Path, err);
 	}
 
-	if err = os.Chmod(Path, 0456); err != nil {
+	if err = Chmod(Path, 0456); err != nil {
 		t.Fatalf("chmod %s 0456: %s", Path, err);
 	}
 	checkMode(t, Path, 0456);
@@ -384,7 +384,7 @@
 	// basically useless.
 
 	const Path = "/tmp/_TestChown_";
-	fd, err := os.Open(Path, os.O_WRONLY | os.O_CREAT, 0666);
+	fd, err := Open(Path, O_WRONLY | O_CREAT, 0666);
 	if err != nil {
 		t.Fatalf("create %s: %s", Path, err);
 	}
@@ -398,7 +398,7 @@
 	// Can't change uid unless root, but can try
 	// changing the group id.  First try our current group.
 	gid := Getgid();
-	if err = os.Chown(Path, -1, gid); err != nil {
+	if err = Chown(Path, -1, gid); err != nil {
 		t.Fatalf("chown %s -1 %d: %s", Path, gid, err);
 	}
 	checkUidGid(t, Path, int(dir.Uid), gid);
@@ -409,7 +409,7 @@
 		t.Fatalf("getgroups: %s", err);
 	}
 	for i, g := range groups {
-		if err = os.Chown(Path, -1, g); err != nil {
+		if err = Chown(Path, -1, g); err != nil {
 			t.Fatalf("chown %s -1 %d: %s", Path, g, err);
 		}
 		checkUidGid(t, Path, int(dir.Uid), g);
@@ -435,7 +435,7 @@
 func TestTruncate(t *testing.T) {
 	MkdirAll("_obj", 0777);
 	const Path = "_obj/_TestTruncate_";
-	fd, err := os.Open(Path, os.O_WRONLY | os.O_CREAT, 0666);
+	fd, err := Open(Path, O_WRONLY | O_CREAT, 0666);
 	if err != nil {
 		t.Fatalf("create %s: %s", Path, err);
 	}
@@ -454,3 +454,50 @@
 	fd.Close();
 	Remove(Path);
 }
+
+func TestChdirAndGetwd(t *testing.T) {
+	fd, err := Open(".", O_RDONLY, 0);
+	if err != nil {
+		t.Fatalf("Open .: %s", err);
+	}
+	// These are chosen carefully not to be symlinks on a Mac
+	// (unlike, say, /var, /etc, and /tmp).
+	dirs := []string{ "/bin", "/", "/usr/local/bin" };
+	for mode := 0; mode < 2; mode++ {
+		for i, d := range dirs {
+			if mode == 0 {
+				err = Chdir(d);
+			} else {
+				fd1, err := os.Open(d, os.O_RDONLY, 0);
+				if err != nil {
+					t.Errorf("Open %s: %s", d, err);
+					continue;
+				}
+				err = fd1.Chdir();
+				fd1.Close();
+			}
+			pwd, err1 := Getwd();
+			err2 := fd.Chdir();
+			if err2 != nil {
+				// We changed the current directory and cannot go back.
+				// Don't let the tests continue; they'll scribble
+				// all over some other directory.
+				fmt.Fprintf(Stderr, "fchdir back to dot failed: %s\n", err2);
+				Exit(1);
+			}
+			if err != nil {
+				fd.Close();
+				t.Fatalf("Chdir %s: %s", d, err);
+			}
+			if err1 != nil {
+				fd.Close();
+				t.Fatalf("Getwd in %s: %s", d, err1);
+			}
+			if pwd != d {
+				fd.Close();
+				t.Fatalf("Getwd returned %q want %q", pwd, d);
+			}
+		}
+	}
+	fd.Close();
+}