Merge branch 'master' into master
diff --git a/example_test.go b/example_test.go
index c6c9009..483f68f 100644
--- a/example_test.go
+++ b/example_test.go
@@ -104,14 +104,26 @@
 	}
 	defer client.Close()
 
-	ssh_fx_failure := uint32(4)
+	sshFxFailure := uint32(4)
 	mkdirParents := func(client *sftp.Client, dir string) (err error) {
 		var parents string
+		
+		if path.IsAbs(dir) {
+			// Otherwise, an absolute path given below would be turned in to a relative one
+			// by splitting on "/"
+			parents = "/"
+		}
+		
 		for _, name := range strings.Split(dir, "/") {
+			if name == "" {
+				// Paths with double-/ in them should just move along
+				// this will also catch the case of the first character being a "/", i.e. an absolute path
+				continue
+			}
 			parents = path.Join(parents, name)
 			err = client.Mkdir(parents)
 			if status, ok := err.(*sftp.StatusError); ok {
-				if status.Code == ssh_fx_failure {
+				if status.Code == sshFxFailure {
 					var fi os.FileInfo
 					fi, err = client.Stat(parents)
 					if err == nil {
diff --git a/packet-manager.go b/packet-manager.go
index 367d75e..8c6691c 100644
--- a/packet-manager.go
+++ b/packet-manager.go
@@ -23,8 +23,8 @@
 	working   *sync.WaitGroup
 }
 
-func newPktMgr(sender packetSender) packetManager {
-	s := packetManager{
+func newPktMgr(sender packetSender) *packetManager {
+	s := &packetManager{
 		requests:  make(chan requestPacket, SftpServerWorkerCount),
 		responses: make(chan responsePacket, SftpServerWorkerCount),
 		fini:      make(chan struct{}),
@@ -39,19 +39,19 @@
 
 // register incoming packets to be handled
 // send id of 0 for packets without id
-func (s packetManager) incomingPacket(pkt requestPacket) {
+func (s *packetManager) incomingPacket(pkt requestPacket) {
 	s.working.Add(1)
 	s.requests <- pkt // buffer == SftpServerWorkerCount
 }
 
 // register outgoing packets as being ready
-func (s packetManager) readyPacket(pkt responsePacket) {
+func (s *packetManager) readyPacket(pkt responsePacket) {
 	s.responses <- pkt
 	s.working.Done()
 }
 
 // shut down packetManager controller
-func (s packetManager) close() {
+func (s *packetManager) close() {
 	// pause until current packets are processed
 	s.working.Wait()
 	close(s.fini)
@@ -147,10 +147,10 @@
 	}
 }
 
-func outfilter(o []responsePacket) []uint32 {
-	res := make([]uint32, 0, len(o))
-	for _, v := range o {
-		res = append(res, v.id())
-	}
-	return res
-}
+//func outfilter(o []responsePacket) []uint32 {
+//	res := make([]uint32, 0, len(o))
+//	for _, v := range o {
+//		res = append(res, v.id())
+//	}
+//	return res
+//}
diff --git a/request-example.go b/request-example.go
index c355fbd..f66b060 100644
--- a/request-example.go
+++ b/request-example.go
@@ -25,7 +25,7 @@
 }
 
 // Handlers
-func (fs *root) Fileread(r Request) (io.ReaderAt, error) {
+func (fs *root) Fileread(r *Request) (io.ReaderAt, error) {
 	fs.filesLock.Lock()
 	defer fs.filesLock.Unlock()
 	file, err := fs.fetch(r.Filepath)
@@ -41,7 +41,7 @@
 	return file.ReaderAt()
 }
 
-func (fs *root) Filewrite(r Request) (io.WriterAt, error) {
+func (fs *root) Filewrite(r *Request) (io.WriterAt, error) {
 	fs.filesLock.Lock()
 	defer fs.filesLock.Unlock()
 	file, err := fs.fetch(r.Filepath)
@@ -59,7 +59,7 @@
 	return file.WriterAt()
 }
 
-func (fs *root) Filecmd(r Request) error {
+func (fs *root) Filecmd(r *Request) error {
 	fs.filesLock.Lock()
 	defer fs.filesLock.Unlock()
 	switch r.Method {
@@ -115,7 +115,7 @@
 	return n, nil
 }
 
-func (fs *root) Filelist(r Request) (ListerAt, error) {
+func (fs *root) Filelist(r *Request) (ListerAt, error) {
 	fs.filesLock.Lock()
 	defer fs.filesLock.Unlock()
 
diff --git a/request-interfaces.go b/request-interfaces.go
index f7afb14..05a9dac 100644
--- a/request-interfaces.go
+++ b/request-interfaces.go
@@ -10,22 +10,22 @@
 
 // FileReader should return an io.Reader for the filepath
 type FileReader interface {
-	Fileread(Request) (io.ReaderAt, error)
+	Fileread(*Request) (io.ReaderAt, error)
 }
 
 // FileWriter should return an io.Writer for the filepath
 type FileWriter interface {
-	Filewrite(Request) (io.WriterAt, error)
+	Filewrite(*Request) (io.WriterAt, error)
 }
 
 // FileCmder should return an error (rename, remove, setstate, etc.)
 type FileCmder interface {
-	Filecmd(Request) error
+	Filecmd(*Request) error
 }
 
 // FileLister should return file info interface and errors (readdir, stat)
 type FileLister interface {
-	Filelist(Request) (ListerAt, error)
+	Filelist(*Request) (ListerAt, error)
 }
 
 // ListerAt does for file lists what io.ReaderAt does for files.
diff --git a/request-server.go b/request-server.go
index b0cad92..73899fa 100644
--- a/request-server.go
+++ b/request-server.go
@@ -3,9 +3,9 @@
 import (
 	"encoding"
 	"io"
-	"os"
 	"path/filepath"
 	"strconv"
+	"strings"
 	"sync"
 	"syscall"
 
@@ -28,7 +28,7 @@
 type RequestServer struct {
 	*serverConn
 	Handlers        Handlers
-	pktMgr          packetManager
+	pktMgr          *packetManager
 	openRequests    map[string]Request
 	openRequestLock sync.RWMutex
 	handleCount     int
@@ -51,20 +51,22 @@
 	}
 }
 
-func (rs *RequestServer) nextRequest(r Request) string {
+// Note that we are explicitly saving the Request as a value.
+func (rs *RequestServer) nextRequest(r *Request) string {
 	rs.openRequestLock.Lock()
 	defer rs.openRequestLock.Unlock()
 	rs.handleCount++
 	handle := strconv.Itoa(rs.handleCount)
-	rs.openRequests[handle] = r
+	rs.openRequests[handle] = *r
 	return handle
 }
 
-func (rs *RequestServer) getRequest(handle string) (Request, bool) {
+// Returns pointer to new copy of Request object
+func (rs *RequestServer) getRequest(handle string) (*Request, bool) {
 	rs.openRequestLock.RLock()
 	defer rs.openRequestLock.RUnlock()
 	r, ok := rs.openRequests[handle]
-	return r, ok
+	return &r, ok
 }
 
 func (rs *RequestServer) closeRequest(handle string) {
@@ -130,7 +132,7 @@
 			rs.closeRequest(handle)
 			rpkt = statusFromError(pkt, nil)
 		case *sshFxpRealpathPacket:
-			rpkt = cleanPath(pkt)
+			rpkt = cleanPacketPath(pkt)
 		case isOpener:
 			handle := rs.nextRequest(requestFromPacket(pkt))
 			rpkt = sshFxpHandlePacket{pkt.id(), handle}
@@ -142,7 +144,7 @@
 			} else {
 				request = requestFromPacket(
 					&sshFxpStatPacket{ID: pkt.id(), Path: request.Filepath})
-				rpkt = rs.handle(request, pkt)
+				rpkt = request.handle(rs.Handlers)
 			}
 		case *sshFxpFsetstatPacket:
 			handle := pkt.getHandle()
@@ -154,7 +156,7 @@
 					&sshFxpSetstatPacket{ID: pkt.id(), Path: request.Filepath,
 						Flags: pkt.Flags, Attrs: pkt.Attrs,
 					})
-				rpkt = rs.handle(request, pkt)
+				rpkt = request.handle(rs.Handlers)
 			}
 		case hasHandle:
 			handle := pkt.getHandle()
@@ -163,11 +165,11 @@
 			if !ok {
 				rpkt = statusFromError(pkt, syscall.EBADF)
 			} else {
-				rpkt = rs.handle(request, pkt)
+				rpkt = request.handle(rs.Handlers)
 			}
 		case hasPath:
 			request := requestFromPacket(pkt)
-			rpkt = rs.handle(request, pkt)
+			rpkt = request.handle(rs.Handlers)
 		default:
 			return errors.Errorf("unexpected packet type %T", pkt)
 		}
@@ -180,31 +182,24 @@
 	return nil
 }
 
-func cleanPath(pkt *sshFxpRealpathPacket) responsePacket {
-	path := pkt.getPath()
-	if !filepath.IsAbs(path) {
-		path = "/" + path
-	} // all paths are absolute
-
-	cleaned_path := filepath.Clean(path)
+func cleanPacketPath(pkt *sshFxpRealpathPacket) responsePacket {
+	path := cleanPath(pkt.getPath())
 	return &sshFxpNamePacket{
 		ID: pkt.id(),
 		NameAttrs: []sshFxpNameAttr{{
-			Name:     cleaned_path,
-			LongName: cleaned_path,
+			Name:     path,
+			LongName: path,
 			Attrs:    emptyFileStat,
 		}},
 	}
 }
 
-func (rs *RequestServer) handle(request Request, pkt requestPacket) responsePacket {
-	// fmt.Println("Request Method: ", request.Method)
-	rpkt, err := request.handle(rs.Handlers)
-	if err != nil {
-		err = errorAdapter(err)
-		rpkt = statusFromError(pkt, err)
+func cleanPath(path string) string {
+	cleanSlashPath := filepath.ToSlash(filepath.Clean(path))
+	if !strings.HasPrefix(cleanSlashPath, "/") {
+		return "/" + cleanSlashPath
 	}
-	return rpkt
+	return cleanSlashPath
 }
 
 // Wrap underlying connection methods to use packetManager
@@ -220,12 +215,3 @@
 func (rs *RequestServer) sendError(p ider, err error) error {
 	return rs.sendPacket(statusFromError(p, err))
 }
-
-// os.ErrNotExist should convert to ssh_FX_NO_SUCH_FILE, but is not recognized
-// by statusFromError. So we convert to syscall.ENOENT which it does.
-func errorAdapter(err error) error {
-	if err == os.ErrNotExist {
-		return syscall.ENOENT
-	}
-	return err
-}
diff --git a/request-server_test.go b/request-server_test.go
index 60aed2a..0c25ecf 100644
--- a/request-server_test.go
+++ b/request-server_test.go
@@ -328,3 +328,20 @@
 	names := []string{di[18].Name(), di[81].Name()}
 	assert.Equal(t, []string{"foo_18", "foo_81"}, names)
 }
+
+func TestCleanPath(t *testing.T) {
+	assert.Equal(t, "/", cleanPath("/"))
+	assert.Equal(t, "/", cleanPath("//"))
+	assert.Equal(t, "/a", cleanPath("/a/"))
+	assert.Equal(t, "/a", cleanPath("a/"))
+	assert.Equal(t, "/a/b/c", cleanPath("/a//b//c/"))
+
+	// filepath.ToSlash does not touch \ as char on unix systems, so os.PathSeparator is used for windows compatible tests
+	bslash := string(os.PathSeparator)
+	assert.Equal(t, "/", cleanPath(bslash))
+	assert.Equal(t, "/", cleanPath(bslash+bslash))
+	assert.Equal(t, "/a", cleanPath(bslash+"a"+bslash))
+	assert.Equal(t, "/a", cleanPath("a"+bslash))
+	assert.Equal(t, "/a/b/c", cleanPath(bslash+"a"+bslash+bslash+"b"+bslash+bslash+"c"+bslash))
+
+}
diff --git a/request.go b/request.go
index e6bdf13..c8af66f 100644
--- a/request.go
+++ b/request.go
@@ -40,14 +40,18 @@
 }
 
 type packet_data struct {
-	id     uint32
+	_id    uint32
 	data   []byte
 	length uint32
 	offset int64
 }
 
+func (pd packet_data) id() uint32 {
+	return pd._id
+}
+
 // New Request initialized based on packet data
-func requestFromPacket(pkt hasPath) Request {
+func requestFromPacket(pkt hasPath) *Request {
 	method := requestMethod(pkt)
 	request := NewRequest(method, pkt.getPath())
 	request.pkt_id = pkt.id()
@@ -56,38 +60,42 @@
 		request.Flags = p.Flags
 		request.Attrs = p.Attrs.([]byte)
 	case *sshFxpRenamePacket:
-		request.Target = filepath.Clean(p.Newpath)
+		request.Target = cleanPath(p.Newpath)
 	case *sshFxpSymlinkPacket:
-		request.Target = filepath.Clean(p.Linkpath)
+		request.Target = cleanPath(p.Linkpath)
 	}
 	return request
 }
 
 // NewRequest creates a new Request object.
-func NewRequest(method, path string) Request {
-	request := Request{Method: method, Filepath: filepath.Clean(path)}
+func NewRequest(method, path string) *Request {
+	request := &Request{Method: method, Filepath: cleanPath(path)}
 	request.packets = make(chan packet_data, SftpServerWorkerCount)
 	request.state = &state{}
 	request.stateLock = &sync.RWMutex{}
 	return request
 }
 
+func (r *Request) id() uint32 {
+	return r.pkt_id
+}
+
 // Returns current offset for file list
-func (r Request) lsNext() int64 {
+func (r *Request) lsNext() int64 {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	return r.state.lsoffset
 }
 
 // Increases next offset
-func (r Request) lsInc(offset int64) {
+func (r *Request) lsInc(offset int64) {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	r.state.lsoffset = r.state.lsoffset + offset
 }
 
 // manage file read/write state
-func (r Request) setFileState(s interface{}) {
+func (r *Request) setFileState(s interface{}) {
 	r.stateLock.Lock()
 	defer r.stateLock.Unlock()
 	switch s := s.(type) {
@@ -102,19 +110,19 @@
 	}
 }
 
-func (r Request) getWriter() io.WriterAt {
+func (r *Request) getWriter() io.WriterAt {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	return r.state.writerAt
 }
 
-func (r Request) getReader() io.ReaderAt {
+func (r *Request) getReader() io.ReaderAt {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	return r.state.readerAt
 }
 
-func (r Request) getLister() ListerAt {
+func (r *Request) getLister() ListerAt {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	return r.state.listerAt
@@ -122,20 +130,20 @@
 
 // For backwards compatibility. The Handler didn't have batch handling at
 // first, and just always assumed 1 batch. This preserves that behavior.
-func (r Request) setEOD(eod bool) {
+func (r *Request) setEOD(eod bool) {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	r.state.endofdir = eod
 }
 
-func (r Request) getEOD() bool {
+func (r *Request) getEOD() bool {
 	r.stateLock.RLock()
 	defer r.stateLock.RUnlock()
 	return r.state.endofdir
 }
 
 // Close reader/writer if possible
-func (r Request) close() {
+func (r *Request) close() {
 	rd := r.getReader()
 	if c, ok := rd.(io.Closer); ok {
 		c.Close()
@@ -147,7 +155,7 @@
 }
 
 // push packet_data into fifo
-func (r Request) pushPacket(pd packet_data) {
+func (r *Request) pushPacket(pd packet_data) {
 	r.packets <- pd
 }
 
@@ -157,58 +165,56 @@
 }
 
 // called from worker to handle packet/request
-func (r Request) handle(handlers Handlers) (responsePacket, error) {
-	var err error
-	var rpkt responsePacket
+func (r *Request) handle(handlers Handlers) responsePacket {
 	switch r.Method {
 	case "Get":
-		rpkt, err = fileget(handlers.FileGet, r)
+		return fileget(handlers.FileGet, r)
 	case "Put": // add "Append" to this to handle append only file writes
-		rpkt, err = fileput(handlers.FilePut, r)
+		return fileput(handlers.FilePut, r)
 	case "Setstat", "Rename", "Rmdir", "Mkdir", "Symlink", "Remove":
-		rpkt, err = filecmd(handlers.FileCmd, r)
+		return filecmd(handlers.FileCmd, r)
 	case "List", "Stat", "Readlink":
-		rpkt, err = filelist(handlers.FileList, r)
+		return filelist(handlers.FileList, r)
 	default:
-		return rpkt, errors.Errorf("unexpected method: %s", r.Method)
+		return statusFromError(r,
+			errors.Errorf("unexpected method: %s", r.Method))
 	}
-	return rpkt, err
 }
 
 // wrap FileReader handler
-func fileget(h FileReader, r Request) (responsePacket, error) {
+func fileget(h FileReader, r *Request) responsePacket {
 	var err error
 	reader := r.getReader()
+	pd := r.popPacket()
 	if reader == nil {
 		reader, err = h.Fileread(r)
 		if err != nil {
-			return nil, err
+			return statusFromError(pd, err)
 		}
 		r.setFileState(reader)
 	}
 
-	pd := r.popPacket()
 	data := make([]byte, clamp(pd.length, maxTxPacket))
 	n, err := reader.ReadAt(data, pd.offset)
 	// only return EOF erro if no data left to read
 	if err != nil && (err != io.EOF || n == 0) {
-		return nil, err
+		return statusFromError(pd, err)
 	}
 	return &sshFxpDataPacket{
-		ID:     pd.id,
+		ID:     pd.id(),
 		Length: uint32(n),
 		Data:   data[:n],
-	}, nil
+	}
 }
 
 // wrap FileWriter handler
-func fileput(h FileWriter, r Request) (responsePacket, error) {
+func fileput(h FileWriter, r *Request) responsePacket {
 	var err error
 	writer := r.getWriter()
 	if writer == nil {
 		writer, err = h.Filewrite(r)
 		if err != nil {
-			return nil, err
+			return statusFromError(r, err)
 		}
 		r.setFileState(writer)
 	}
@@ -216,36 +222,36 @@
 	pd := r.popPacket()
 	_, err = writer.WriteAt(pd.data, pd.offset)
 	if err != nil {
-		return nil, err
+		return statusFromError(pd, err)
 	}
 	return &sshFxpStatusPacket{
-		ID: pd.id,
+		ID: pd.id(),
 		StatusError: StatusError{
 			Code: ssh_FX_OK,
-		}}, nil
+		}}
 }
 
 // wrap FileCmder handler
-func filecmd(h FileCmder, r Request) (responsePacket, error) {
+func filecmd(h FileCmder, r *Request) responsePacket {
 	err := h.Filecmd(r)
 	if err != nil {
-		return nil, err
+		return statusFromError(r, err)
 	}
 	return &sshFxpStatusPacket{
 		ID: r.pkt_id,
 		StatusError: StatusError{
 			Code: ssh_FX_OK,
-		}}, nil
+		}}
 }
 
 // wrap FileLister handler
-func filelist(h FileLister, r Request) (responsePacket, error) {
+func filelist(h FileLister, r *Request) responsePacket {
 	var err error
 	lister := r.getLister()
 	if lister == nil {
 		lister, err = h.Filelist(r)
 		if err != nil {
-			return nil, err
+			return statusFromError(r, err)
 		}
 		r.setFileState(lister)
 	}
@@ -255,28 +261,19 @@
 	n, err := lister.ListAt(finfo, offset)
 	r.lsInc(int64(n))
 	// ignore EOF as we only return it when there are no results
-	if err != nil && err != io.EOF {
-		return nil, err
-	}
 	finfo = finfo[:n] // avoid need for nil tests below
 
-	// no results
-	if n == 0 {
-		switch r.Method {
-		case "List":
-			return nil, io.EOF
-		case "Stat", "Readlink":
-			err = &os.PathError{Op: "readlink", Path: r.Filepath,
-				Err: syscall.ENOENT}
-			return nil, err
-		}
-	}
-
 	switch r.Method {
 	case "List":
 		pd := r.popPacket()
-		dirname := path.Base(r.Filepath)
-		ret := &sshFxpNamePacket{ID: pd.id}
+		if err != nil && err != io.EOF {
+			return statusFromError(pd, err)
+		}
+		if n == 0 {
+			return statusFromError(pd, io.EOF)
+		}
+		dirname := filepath.ToSlash(path.Base(r.Filepath))
+		ret := &sshFxpNamePacket{ID: pd.id()}
 
 		for _, fi := range finfo {
 			ret.NameAttrs = append(ret.NameAttrs, sshFxpNameAttr{
@@ -285,13 +282,29 @@
 				Attrs:    []interface{}{fi},
 			})
 		}
-		return ret, nil
+		return ret
 	case "Stat":
+		if err != nil && err != io.EOF {
+			return statusFromError(r, err)
+		}
+		if n == 0 {
+			err = &os.PathError{Op: "stat", Path: r.Filepath,
+				Err: syscall.ENOENT}
+			return statusFromError(r, err)
+		}
 		return &sshFxpStatResponse{
 			ID:   r.pkt_id,
 			info: finfo[0],
-		}, nil
+		}
 	case "Readlink":
+		if err != nil && err != io.EOF {
+			return statusFromError(r, err)
+		}
+		if n == 0 {
+			err = &os.PathError{Op: "readlink", Path: r.Filepath,
+				Err: syscall.ENOENT}
+			return statusFromError(r, err)
+		}
 		filename := finfo[0].Name()
 		return &sshFxpNamePacket{
 			ID: r.pkt_id,
@@ -300,15 +313,16 @@
 				LongName: filename,
 				Attrs:    emptyFileStat,
 			}},
-		}, nil
+		}
 	default:
-		return nil, errors.Errorf("unexpected method: %s", r.Method)
+		err = errors.Errorf("unexpected method: %s", r.Method)
+		return statusFromError(r, err)
 	}
 }
 
 // file data for additional read/write packets
 func (r *Request) update(p hasHandle) error {
-	pd := packet_data{id: p.id()}
+	pd := packet_data{_id: p.id()}
 	switch p := p.(type) {
 	case *sshFxpReadPacket:
 		r.Method = "Get"
diff --git a/request_test.go b/request_test.go
index efcedcd..aff257e 100644
--- a/request_test.go
+++ b/request_test.go
@@ -18,28 +18,25 @@
 	err          error       // dummy error, should be file related
 }
 
-func (t *testHandler) Fileread(r Request) (io.ReaderAt, error) {
+func (t *testHandler) Fileread(r *Request) (io.ReaderAt, error) {
 	if t.err != nil {
 		return nil, t.err
 	}
 	return bytes.NewReader(t.filecontents), nil
 }
 
-func (t *testHandler) Filewrite(r Request) (io.WriterAt, error) {
+func (t *testHandler) Filewrite(r *Request) (io.WriterAt, error) {
 	if t.err != nil {
 		return nil, t.err
 	}
 	return io.WriterAt(t.output), nil
 }
 
-func (t *testHandler) Filecmd(r Request) error {
-	if t.err != nil {
-		return t.err
-	}
-	return nil
+func (t *testHandler) Filecmd(r *Request) error {
+	return t.err
 }
 
-func (t *testHandler) Filelist(r Request) (ListerAt, error) {
+func (t *testHandler) Filelist(r *Request) (ListerAt, error) {
 	if t.err != nil {
 		return nil, t.err
 	}
@@ -59,8 +56,8 @@
 
 var filecontents = []byte("file-data.")
 
-func testRequest(method string) Request {
-	request := Request{
+func testRequest(method string) *Request {
+	request := &Request{
 		Filepath:  "./request_test.go",
 		Method:    method,
 		Attrs:     []byte("foo"),
@@ -70,8 +67,8 @@
 		stateLock: &sync.RWMutex{},
 	}
 	for _, p := range []packet_data{
-		packet_data{id: 1, data: filecontents[:5], length: 5},
-		packet_data{id: 2, data: filecontents[5:], length: 5, offset: 5}} {
+		{_id: 1, data: filecontents[:5], length: 5},
+		{_id: 2, data: filecontents[5:], length: 5, offset: 5}} {
 		request.packets <- p
 	}
 	return request
@@ -125,8 +122,7 @@
 	request := testRequest("Get")
 	// req.length is 5, so we test reads in 5 byte chunks
 	for i, txt := range []string{"file-", "data."} {
-		pkt, err := request.handle(handlers)
-		assert.Nil(t, err)
+		pkt := request.handle(handlers)
 		dpkt := pkt.(*sshFxpDataPacket)
 		assert.Equal(t, dpkt.id(), uint32(i+1))
 		assert.Equal(t, string(dpkt.Data), txt)
@@ -136,11 +132,9 @@
 func TestRequestPut(t *testing.T) {
 	handlers := newTestHandlers()
 	request := testRequest("Put")
-	pkt, err := request.handle(handlers)
-	assert.Nil(t, err)
+	pkt := request.handle(handlers)
 	statusOk(t, pkt)
-	pkt, err = request.handle(handlers)
-	assert.Nil(t, err)
+	pkt = request.handle(handlers)
 	statusOk(t, pkt)
 	assert.Equal(t, "file-data.", handlers.getOutString())
 }
@@ -148,14 +142,12 @@
 func TestRequestCmdr(t *testing.T) {
 	handlers := newTestHandlers()
 	request := testRequest("Mkdir")
-	pkt, err := request.handle(handlers)
-	assert.Nil(t, err)
+	pkt := request.handle(handlers)
 	statusOk(t, pkt)
 
 	handlers.returnError()
-	pkt, err = request.handle(handlers)
-	assert.Nil(t, pkt)
-	assert.Equal(t, err, errTest)
+	pkt = request.handle(handlers)
+	assert.Equal(t, pkt, statusFromError(pkt, errTest))
 }
 
 func TestRequestInfoList(t *testing.T)     { testInfoMethod(t, "List") }
@@ -163,8 +155,7 @@
 func TestRequestInfoStat(t *testing.T) {
 	handlers := newTestHandlers()
 	request := testRequest("Stat")
-	pkt, err := request.handle(handlers)
-	assert.Nil(t, err)
+	pkt := request.handle(handlers)
 	spkt, ok := pkt.(*sshFxpStatResponse)
 	assert.True(t, ok)
 	assert.Equal(t, spkt.info.Name(), "request_test.go")
@@ -173,8 +164,7 @@
 func testInfoMethod(t *testing.T, method string) {
 	handlers := newTestHandlers()
 	request := testRequest(method)
-	pkt, err := request.handle(handlers)
-	assert.Nil(t, err)
+	pkt := request.handle(handlers)
 	npkt, ok := pkt.(*sshFxpNamePacket)
 	assert.True(t, ok)
 	assert.IsType(t, sshFxpNameAttr{}, npkt.NameAttrs[0])
diff --git a/server.go b/server.go
index 13fd7ff..be14f62 100644
--- a/server.go
+++ b/server.go
@@ -29,7 +29,7 @@
 	*serverConn
 	debugStream   io.Writer
 	readOnly      bool
-	pktMgr        packetManager
+	pktMgr        *packetManager
 	openFiles     map[string]*os.File
 	openFilesLock sync.RWMutex
 	handleCount   int
@@ -230,8 +230,7 @@
 		if err != nil {
 			return s.sendError(p, err)
 		}
-		f = filepath.Clean(f)
-		f = filepath.ToSlash(f) // make path more Unix like on windows servers
+		f = cleanPath(f)
 		return s.sendPacket(sshFxpNamePacket{
 			ID: p.ID,
 			NameAttrs: []sshFxpNameAttr{{
@@ -555,6 +554,8 @@
 		ret.StatusError.msg = err.Error()
 		if err == io.EOF {
 			ret.StatusError.Code = ssh_FX_EOF
+		} else if err == os.ErrNotExist {
+			ret.StatusError.Code = ssh_FX_NO_SUCH_FILE
 		} else if errno, ok := err.(syscall.Errno); ok {
 			ret.StatusError.Code = translateErrno(errno)
 		} else if pathError, ok := err.(*os.PathError); ok {
@@ -573,3 +574,92 @@
 	}
 	return v
 }
+
+func runLsTypeWord(dirent os.FileInfo) string {
+	// find first character, the type char
+	// b     Block special file.
+	// c     Character special file.
+	// d     Directory.
+	// l     Symbolic link.
+	// s     Socket link.
+	// p     FIFO.
+	// -     Regular file.
+	tc := '-'
+	mode := dirent.Mode()
+	if (mode & os.ModeDir) != 0 {
+		tc = 'd'
+	} else if (mode & os.ModeDevice) != 0 {
+		tc = 'b'
+		if (mode & os.ModeCharDevice) != 0 {
+			tc = 'c'
+		}
+	} else if (mode & os.ModeSymlink) != 0 {
+		tc = 'l'
+	} else if (mode & os.ModeSocket) != 0 {
+		tc = 's'
+	} else if (mode & os.ModeNamedPipe) != 0 {
+		tc = 'p'
+	}
+
+	// owner
+	orc := '-'
+	if (mode & 0400) != 0 {
+		orc = 'r'
+	}
+	owc := '-'
+	if (mode & 0200) != 0 {
+		owc = 'w'
+	}
+	oxc := '-'
+	ox := (mode & 0100) != 0
+	setuid := (mode & os.ModeSetuid) != 0
+	if ox && setuid {
+		oxc = 's'
+	} else if setuid {
+		oxc = 'S'
+	} else if ox {
+		oxc = 'x'
+	}
+
+	// group
+	grc := '-'
+	if (mode & 040) != 0 {
+		grc = 'r'
+	}
+	gwc := '-'
+	if (mode & 020) != 0 {
+		gwc = 'w'
+	}
+	gxc := '-'
+	gx := (mode & 010) != 0
+	setgid := (mode & os.ModeSetgid) != 0
+	if gx && setgid {
+		gxc = 's'
+	} else if setgid {
+		gxc = 'S'
+	} else if gx {
+		gxc = 'x'
+	}
+
+	// all / others
+	arc := '-'
+	if (mode & 04) != 0 {
+		arc = 'r'
+	}
+	awc := '-'
+	if (mode & 02) != 0 {
+		awc = 'w'
+	}
+	axc := '-'
+	ax := (mode & 01) != 0
+	sticky := (mode & os.ModeSticky) != 0
+	if ax && sticky {
+		axc = 't'
+	} else if sticky {
+		axc = 'T'
+	} else if ax {
+		axc = 'x'
+	}
+
+	return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
+}
diff --git a/server_stubs.go b/server_stubs.go
index 3b1ddbd..a14c734 100644
--- a/server_stubs.go
+++ b/server_stubs.go
@@ -4,9 +4,29 @@
 
 import (
 	"os"
-	"path"
+	"time"
+	"fmt"
 )
 
 func runLs(dirname string, dirent os.FileInfo) string {
-	return path.Join(dirname, dirent.Name())
+	typeword := runLsTypeWord(dirent)
+	numLinks := 1
+	if dirent.IsDir() {
+		numLinks = 0
+	}
+	username := "root"
+	groupname := "root"
+	mtime := dirent.ModTime()
+	monthStr := mtime.Month().String()[0:3]
+	day := mtime.Day()
+	year := mtime.Year()
+	now := time.Now()
+	isOld := mtime.Before(now.Add(-time.Hour * 24 * 365 / 2))
+
+	yearOrTime := fmt.Sprintf("%02d:%02d", mtime.Hour(), mtime.Minute())
+	if isOld {
+		yearOrTime = fmt.Sprintf("%d", year)
+	}
+
+	return fmt.Sprintf("%s %4d %-8s %-8s %8d %s %2d %5s %s", typeword, numLinks, username, groupname, dirent.Size(), monthStr, day, yearOrTime, dirent.Name())
 }
diff --git a/server_test.go b/server_test.go
index 721acc6..f2f034a 100644
--- a/server_test.go
+++ b/server_test.go
@@ -1,11 +1,159 @@
 package sftp
 
 import (
+	"testing"
+	"os"
+	"regexp"
+	"time"
 	"io"
 	"sync"
-	"testing"
 )
 
+const (
+	typeDirectory = "d"
+	typeFile      = "[^d]"
+)
+
+func TestRunLsWithExamplesDirectory(t *testing.T) {
+	path := "examples"
+	item, _ := os.Stat(path)
+	result := runLs(path, item)
+	runLsTestHelper(t, result, typeDirectory, path)
+}
+
+func TestRunLsWithLicensesFile(t *testing.T) {
+	path := "LICENSE"
+	item, _ := os.Stat(path)
+	result := runLs(path, item)
+	runLsTestHelper(t, result, typeFile, path)
+}
+
+/*
+   The format of the `longname' field is unspecified by this protocol.
+   It MUST be suitable for use in the output of a directory listing
+   command (in fact, the recommended operation for a directory listing
+   command is to simply display this data).  However, clients SHOULD NOT
+   attempt to parse the longname field for file attributes; they SHOULD
+   use the attrs field instead.
+
+    The recommended format for the longname field is as follows:
+
+        -rwxr-xr-x   1 mjos     staff      348911 Mar 25 14:29 t-filexfer
+        1234567890 123 12345678 12345678 12345678 123456789012
+
+   Here, the first line is sample output, and the second field indicates
+   widths of the various fields.  Fields are separated by spaces.  The
+   first field lists file permissions for user, group, and others; the
+   second field is link count; the third field is the name of the user
+   who owns the file; the fourth field is the name of the group that
+   owns the file; the fifth field is the size of the file in bytes; the
+   sixth field (which actually may contain spaces, but is fixed to 12
+   characters) is the file modification time, and the seventh field is
+   the file name.  Each field is specified to be a minimum of certain
+   number of character positions (indicated by the second line above),
+   but may also be longer if the data does not fit in the specified
+   length.
+
+    The SSH_FXP_ATTRS response has the following format:
+
+        uint32     id
+        ATTRS      attrs
+
+   where `id' is the request identifier, and `attrs' is the returned
+   file attributes as described in Section ``File Attributes''.
+ */
+func runLsTestHelper(t *testing.T, result, expectedType, path string) {
+	// using regular expressions to make tests work on all systems
+	// a virtual file system (like afero) would be needed to mock valid filesystem checks
+	// expected layout is:
+	// drwxr-xr-x   8 501      20            272 Aug  9 19:46 examples
+
+	// permissions (len 10, "drwxr-xr-x")
+	got := result[0:10]
+	if ok, err := regexp.MatchString("^"+expectedType+"[rwx-]{9}$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): permission field mismatch, expected dir, got: %#v, err: %#v", path, got, err)
+	}
+
+	// space
+	got = result[10:11]
+	if ok, err := regexp.MatchString("^\\s$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): spacer 1 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
+	}
+
+	// link count (len 3, number)
+	got = result[12:15]
+	if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): link count field mismatch, got: %#v, err: %#v", path, got, err)
+	}
+
+	// spacer
+	got = result[15:16]
+	if ok, err := regexp.MatchString("^\\s$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): spacer 2 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
+	}
+
+	// username / uid (len 8, number or string)
+	got = result[16:24]
+	if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): username / uid mismatch, expected user, got: %#v, err: %#v", path, got, err)
+	}
+
+	// spacer
+	got = result[24:25]
+	if ok, err := regexp.MatchString("^\\s$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): spacer 3 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
+	}
+
+	// groupname / gid (len 8, number or string)
+	got = result[25:33]
+	if ok, err := regexp.MatchString("^[^\\s]{1,8}\\s*$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): groupname / gid mismatch, expected group, got: %#v, err: %#v", path, got, err)
+	}
+
+	// spacer
+	got = result[33:34]
+	if ok, err := regexp.MatchString("^\\s$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): spacer 4 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
+	}
+
+	// filesize (len 8)
+	got = result[34:42]
+	if ok, err := regexp.MatchString("^\\s*[0-9]+$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): filesize field mismatch, expected size in bytes, got: %#v, err: %#v", path, got, err)
+	}
+
+	// spacer
+	got = result[42:43]
+	if ok, err := regexp.MatchString("^\\s$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): spacer 5 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
+	}
+
+	// mod time (len 12, e.g. Aug  9 19:46)
+	got = result[43:55]
+	layout := "Jan  2 15:04"
+	_, err := time.Parse(layout, got)
+
+	if err != nil {
+		layout = "Jan  2 2006"
+		_, err = time.Parse(layout, got)
+	}
+	if err != nil {
+		t.Errorf("runLs(%#v, *FileInfo): mod time field mismatch, expected date layout %s, got: %#v, err: %#v", path, layout, got, err)
+	}
+
+	// spacer
+	got = result[55:56]
+	if ok, err := regexp.MatchString("^\\s$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): spacer 6 mismatch, expected whitespace, got: %#v, err: %#v", path, got, err)
+	}
+
+	// filename
+	got = result[56:]
+	if ok, err := regexp.MatchString("^"+path+"$", got); !ok {
+		t.Errorf("runLs(%#v, *FileInfo): name field mismatch, expected examples, got: %#v, err: %#v", path, got, err)
+	}
+}
+
 func clientServerPair(t *testing.T) (*Client, *Server) {
 	cr, sw := io.Pipe()
 	sr, cw := io.Pipe()
diff --git a/server_unix.go b/server_unix.go
index 8c3f0b4..8d0c138 100644
--- a/server_unix.go
+++ b/server_unix.go
@@ -11,96 +11,7 @@
 	"time"
 )
 
-func runLsTypeWord(dirent os.FileInfo) string {
-	// find first character, the type char
-	// b     Block special file.
-	// c     Character special file.
-	// d     Directory.
-	// l     Symbolic link.
-	// s     Socket link.
-	// p     FIFO.
-	// -     Regular file.
-	tc := '-'
-	mode := dirent.Mode()
-	if (mode & os.ModeDir) != 0 {
-		tc = 'd'
-	} else if (mode & os.ModeDevice) != 0 {
-		tc = 'b'
-		if (mode & os.ModeCharDevice) != 0 {
-			tc = 'c'
-		}
-	} else if (mode & os.ModeSymlink) != 0 {
-		tc = 'l'
-	} else if (mode & os.ModeSocket) != 0 {
-		tc = 's'
-	} else if (mode & os.ModeNamedPipe) != 0 {
-		tc = 'p'
-	}
-
-	// owner
-	orc := '-'
-	if (mode & 0400) != 0 {
-		orc = 'r'
-	}
-	owc := '-'
-	if (mode & 0200) != 0 {
-		owc = 'w'
-	}
-	oxc := '-'
-	ox := (mode & 0100) != 0
-	setuid := (mode & os.ModeSetuid) != 0
-	if ox && setuid {
-		oxc = 's'
-	} else if setuid {
-		oxc = 'S'
-	} else if ox {
-		oxc = 'x'
-	}
-
-	// group
-	grc := '-'
-	if (mode & 040) != 0 {
-		grc = 'r'
-	}
-	gwc := '-'
-	if (mode & 020) != 0 {
-		gwc = 'w'
-	}
-	gxc := '-'
-	gx := (mode & 010) != 0
-	setgid := (mode & os.ModeSetgid) != 0
-	if gx && setgid {
-		gxc = 's'
-	} else if setgid {
-		gxc = 'S'
-	} else if gx {
-		gxc = 'x'
-	}
-
-	// all / others
-	arc := '-'
-	if (mode & 04) != 0 {
-		arc = 'r'
-	}
-	awc := '-'
-	if (mode & 02) != 0 {
-		awc = 'w'
-	}
-	axc := '-'
-	ax := (mode & 01) != 0
-	sticky := (mode & os.ModeSticky) != 0
-	if ax && sticky {
-		axc = 't'
-	} else if sticky {
-		axc = 'T'
-	} else if ax {
-		axc = 'x'
-	}
-
-	return fmt.Sprintf("%c%c%c%c%c%c%c%c%c%c", tc, orc, owc, oxc, grc, gwc, gxc, arc, awc, axc)
-}
-
-func runLsStatt(dirname string, dirent os.FileInfo, statt *syscall.Stat_t) string {
+func runLsStatt(dirent os.FileInfo, statt *syscall.Stat_t) string {
 	// example from openssh sftp server:
 	// crw-rw-rw-    1 root     wheel           0 Jul 31 20:52 ttyvd
 	// format:
@@ -136,7 +47,7 @@
 	if dsys == nil {
 	} else if statt, ok := dsys.(*syscall.Stat_t); !ok {
 	} else {
-		return runLsStatt(dirname, dirent, statt)
+		return runLsStatt(dirent, statt)
 	}
 
 	return path.Join(dirname, dirent.Name())