more specification polishing, document specification points better
diff --git a/request-example.go b/request-example.go
index 7ef4a60..0afaefb 100644
--- a/request-example.go
+++ b/request-example.go
@@ -58,29 +58,24 @@
 	return fs.openfile(r.Filepath, r.Flags)
 }
 
-func (fs *root) newfile(pathname string) (*memFile, error) {
+func (fs *root) putfile(pathname string, file *memFile) error {
 	pathname, err := fs.canonName(pathname)
 	if err != nil {
-		return nil, err
+		return err
 	}
 
-	switch pathname {
-	case "", "/":
-		// sanity check protection.
-		return nil, os.ErrInvalid
+	if !strings.HasPrefix(pathname, "/") {
+		return os.ErrInvalid
 	}
 
-	if fs.files[pathname] != nil {
-		return nil, os.ErrExist
+	if _, err := fs.lfetch(pathname); err != os.ErrNotExist {
+		return os.ErrExist
 	}
 
-	file := &memFile{
-		name:    pathname,
-		modtime: time.Now(),
-	}
+	file.name = pathname
 	fs.files[pathname] = file
 
-	return file, nil
+	return nil
 }
 
 func (fs *root) openfile(pathname string, flags uint32) (*memFile, error) {
@@ -96,6 +91,7 @@
 		link, err := fs.lfetch(pathname)
 		for err == nil && link.symlink != "" {
 			if pflags.Excl {
+				// unless you also passed in O_EXCL
 				return nil, os.ErrInvalid
 			}
 
@@ -103,7 +99,15 @@
 			link, err = fs.lfetch(pathname)
 		}
 
-		return fs.newfile(pathname)
+		file := &memFile{
+			modtime: time.Now(),
+		}
+
+		if err := fs.putfile(pathname, file); err != nil {
+			return nil, err
+		}
+
+		return file, nil
 	}
 
 	if err != nil {
@@ -150,15 +154,10 @@
 		return nil
 
 	case "Rename":
-		// SSH SPEC: "It is an error if there already exists a file with the name specified by newpath."
-		// This varies from the POSIX specification, that says it should replace the new file.
+		// SFTP-v2: "It is an error if there already exists a file with the name specified by newpath."
+		// This varies from the POSIX specification, which allows limited replacement of target files.
 		if fs.exists(r.Target) {
-			return &os.LinkError{
-				Op:  "rename",
-				Old: r.Filepath,
-				New: r.Target,
-				Err: os.ErrExist,
-			}
+			return os.ErrExist
 		}
 
 		return fs.rename(r.Filepath, r.Target)
@@ -167,7 +166,9 @@
 		return fs.rmdir(r.Filepath)
 
 	case "Remove":
-		return fs.remove(r.Filepath)
+		// IEEE 1003.1 remove explicitly can unlink files and remove empty directories.
+		// We use instead here the semantics of unlink, which is allowed to be restricted against directories.
+		return fs.unlink(r.Filepath)
 
 	case "Mkdir":
 		return fs.mkdir(r.Filepath)
@@ -194,10 +195,38 @@
 		return err
 	}
 
+	if !strings.HasPrefix(newpath, "/") {
+		return os.ErrInvalid
+	}
+
+	target, err := fs.lfetch(newpath)
+	if err != os.ErrNotExist {
+		if target == file {
+			// IEEE 1003.1: if oldpath and newpath are the same directory entry,
+			// then return no error, and perform no further action.
+			return nil
+		}
+
+		switch {
+		case file.IsDir():
+			// IEEE 1003.1: if oldpath is a directory, and newpath exists,
+			// then newpath must be a directory, and empty.
+			// It is to be removed prior to rename.
+			if err := fs.rmdir(newpath); err != nil {
+				return err
+			}
+
+		case target.IsDir():
+			// IEEE 1003.1: if oldpath is not a directory, and newpath exists,
+			// then newpath may not be a directory.
+			return syscall.EISDIR
+		}
+	}
+
 	fs.files[newpath] = file
 
-	if dir := file; dir.IsDir() {
-		dirprefix := dir.name + "/"
+	if file.IsDir() {
+		dirprefix := file.name + "/"
 
 		for name, file := range fs.files {
 			if strings.HasPrefix(name, dirprefix) {
@@ -229,18 +258,16 @@
 }
 
 func (fs *root) mkdir(pathname string) error {
-	dir, err := fs.newfile(pathname)
-	if err != nil {
-		return err
+	dir := &memFile{
+		modtime: time.Now(),
+		isdir:   true,
 	}
 
-	dir.isdir = true
-
-	return nil
+	return fs.putfile(pathname, dir)
 }
 
 func (fs *root) rmdir(pathname string) error {
-	// does not follow symlinks!
+	// IEEE 1003.1: If pathname is a symlink, then rmdir should fail with ENOTDIR.
 	dir, err := fs.lfetch(pathname)
 	if err != nil {
 		return err
@@ -256,11 +283,7 @@
 
 	for name := range fs.files {
 		if path.Dir(name) == pathname {
-			return &os.PathError{
-				Op:   "rmdir",
-				Path: pathname + "/",
-				Err:  errors.New("directory is not empty"),
-			}
+			return errors.New("directory not empty")
 		}
 	}
 
@@ -279,30 +302,21 @@
 		return errors.New("hard link not allowed for directory")
 	}
 
-	fs.files[newpath] = file
-
-	return nil
+	return fs.putfile(newpath, file)
 }
 
 // symlink() creates a symbolic link named `linkpath` which contains the string `target`.
 // NOTE! This would be called with `symlink(req.Filepath, req.Target)` due to different semantics.
 func (fs *root) symlink(target, linkpath string) error {
-	_, err := fs.lfetch(linkpath)
-	if err != os.ErrNotExist {
-		return os.ErrExist
+	link := &memFile{
+		modtime: time.Now(),
+		symlink: target,
 	}
 
-	link, err := fs.newfile(linkpath)
-	if err != nil {
-		return err
-	}
-
-	link.symlink = target
-
-	return nil
+	return fs.putfile(linkpath, link)
 }
 
-func (fs *root) remove(pathname string) error {
+func (fs *root) unlink(pathname string) error {
 	// does not follow symlinks!
 	file, err := fs.lfetch(pathname)
 	if err != nil {
@@ -310,6 +324,8 @@
 	}
 
 	if file.IsDir() {
+		// IEEE 1003.1: implementations may opt out of allowing the unlinking of directories.
+		// SFTP-v2: SSH_FXP_REMOVE may not remove directories.
 		return os.ErrInvalid
 	}
 
@@ -365,7 +381,7 @@
 			return nil, err
 		}
 
-		// SSH SPEC: The server will respond with a SSH_FXP_NAME packet containing only
+		// SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only
 		// one name and a dummy attributes value.
 		return listerat{
 			&memFile{