package sftp

import (
	
	
	
	
	
	
	
	
)

// MaxFilelist is the max number of files to return in a readdir batch.
var MaxFilelist int64 = 100

// state encapsulates the reader/writer/readdir from handlers.
type state struct {
	mu sync.RWMutex

	writerAt         io.WriterAt
	readerAt         io.ReaderAt
	writerAtReaderAt WriterAtReaderAt
	listerAt         ListerAt
	lsoffset         int64
}

// copy returns a shallow copy the state.
// This is broken out to specific fields,
// because we have to copy around the mutex in state.
func ( *state) () state {
	.mu.RLock()
	defer .mu.RUnlock()

	return state{
		writerAt:         .writerAt,
		readerAt:         .readerAt,
		writerAtReaderAt: .writerAtReaderAt,
		listerAt:         .listerAt,
		lsoffset:         .lsoffset,
	}
}

func ( *state) ( io.ReaderAt) {
	.mu.Lock()
	defer .mu.Unlock()

	.readerAt = 
}

func ( *state) () io.ReaderAt {
	.mu.RLock()
	defer .mu.RUnlock()

	return .readerAt
}

func ( *state) ( io.WriterAt) {
	.mu.Lock()
	defer .mu.Unlock()

	.writerAt = 
}

func ( *state) () io.WriterAt {
	.mu.RLock()
	defer .mu.RUnlock()

	return .writerAt
}

func ( *state) ( WriterAtReaderAt) {
	.mu.Lock()
	defer .mu.Unlock()

	.writerAtReaderAt = 
}

func ( *state) () WriterAtReaderAt {
	.mu.RLock()
	defer .mu.RUnlock()

	return .writerAtReaderAt
}

func ( *state) () (io.ReaderAt, io.WriterAt, WriterAtReaderAt) {
	.mu.RLock()
	defer .mu.RUnlock()

	return .readerAt, .writerAt, .writerAtReaderAt
}

// Returns current offset for file list
func ( *state) () int64 {
	.mu.RLock()
	defer .mu.RUnlock()

	return .lsoffset
}

// Increases next offset
func ( *state) ( int64) {
	.mu.Lock()
	defer .mu.Unlock()

	.lsoffset += 
}

// manage file read/write state
func ( *state) ( ListerAt) {
	.mu.Lock()
	defer .mu.Unlock()

	.listerAt = 
}

func ( *state) () ListerAt {
	.mu.RLock()
	defer .mu.RUnlock()

	return .listerAt
}

// Request contains the data and state for the incoming service request.
type Request struct {
	// Get, Put, Setstat, Stat, Rename, Remove
	// Rmdir, Mkdir, List, Readlink, Link, Symlink
	Method   string
	Filepath string
	Flags    uint32
	Attrs    []byte // convert to sub-struct
	Target   string // for renames and sym-links
	handle   string

	// reader/writer/readdir from handlers
	state

	// context lasts duration of request
	ctx       context.Context
	cancelCtx context.CancelFunc
}

// NewRequest creates a new Request object.
func (,  string) *Request {
	return &Request{
		Method:   ,
		Filepath: cleanPath(),
	}
}

// copy returns a shallow copy of existing request.
// This is broken out to specific fields,
// because we have to copy around the mutex in state.
func ( *Request) () *Request {
	return &Request{
		Method:   .Method,
		Filepath: .Filepath,
		Flags:    .Flags,
		Attrs:    .Attrs,
		Target:   .Target,
		handle:   .handle,

		state: .state.copy(),

		ctx:       .ctx,
		cancelCtx: .cancelCtx,
	}
}

// New Request initialized based on packet data
func requestFromPacket( context.Context,  hasPath,  string) *Request {
	 := &Request{
		Method:   requestMethod(),
		Filepath: cleanPathWithBase(, .getPath()),
	}
	.ctx, .cancelCtx = context.WithCancel()

	switch p := .(type) {
	case *sshFxpOpenPacket:
		.Flags = .Pflags
	case *sshFxpSetstatPacket:
		.Flags = .Flags
		.Attrs = .Attrs.([]byte)
	case *sshFxpRenamePacket:
		.Target = cleanPathWithBase(, .Newpath)
	case *sshFxpSymlinkPacket:
		// NOTE: given a POSIX compliant signature: symlink(target, linkpath string)
		// this makes Request.Target the linkpath, and Request.Filepath the target.
		.Target = cleanPathWithBase(, .Linkpath)
		.Filepath = .Targetpath
	case *sshFxpExtendedPacketHardlink:
		.Target = cleanPathWithBase(, .Newpath)
	}
	return 
}

// Context returns the request's context. To change the context,
// use WithContext.
//
// The returned context is always non-nil; it defaults to the
// background context.
//
// For incoming server requests, the context is canceled when the
// request is complete or the client's connection closes.
func ( *Request) () context.Context {
	if .ctx != nil {
		return .ctx
	}
	return context.Background()
}

// WithContext returns a copy of r with its context changed to ctx.
// The provided ctx must be non-nil.
func ( *Request) ( context.Context) *Request {
	if  == nil {
		panic("nil context")
	}
	 := .copy()
	.ctx = 
	.cancelCtx = nil
	return 
}

// Close reader/writer if possible
func ( *Request) () error {
	defer func() {
		if .cancelCtx != nil {
			.cancelCtx()
		}
	}()

	, ,  := .getAllReaderWriters()

	var  error

	// Close errors on a Writer are far more likely to be the important one.
	// As they can be information that there was a loss of data.
	if ,  := .(io.Closer);  {
		if  := .Close();  == nil {
			// update error if it is still nil
			 = 
		}
	}

	if ,  := .(io.Closer);  {
		if  := .Close();  == nil {
			// update error if it is still nil
			 = 

			.setWriterAtReaderAt(nil)
		}
	}

	if ,  := .(io.Closer);  {
		if  := .Close();  == nil {
			// update error if it is still nil
			 = 
		}
	}

	return 
}

// Notify transfer error if any
func ( *Request) ( error) {
	if  == nil {
		return
	}

	, ,  := .getAllReaderWriters()

	if ,  := .(TransferError);  {
		.TransferError()
	}

	if ,  := .(TransferError);  {
		.TransferError()
	}

	if ,  := .(TransferError);  {
		.TransferError()
	}
}

// called from worker to handle packet/request
func ( *Request) ( Handlers,  requestPacket,  *allocator,  uint32) responsePacket {
	switch .Method {
	case "Get":
		return fileget(.FileGet, , , , )
	case "Put":
		return fileput(.FilePut, , , , )
	case "Open":
		return fileputget(.FilePut, , , , )
	case "Setstat", "Rename", "Rmdir", "Mkdir", "Link", "Symlink", "Remove", "PosixRename", "StatVFS":
		return filecmd(.FileCmd, , )
	case "List":
		return filelist(.FileList, , )
	case "Stat", "Lstat":
		return filestat(.FileList, , )
	case "Readlink":
		if ,  := .FileList.(ReadlinkFileLister);  {
			return readlink(, , )
		}
		return filestat(.FileList, , )
	default:
		return statusFromError(.id(), fmt.Errorf("unexpected method: %s", .Method))
	}
}

// Additional initialization for Open packets
func ( *Request) ( Handlers,  requestPacket) responsePacket {
	 := .Pflags()

	 := .id()

	switch {
	case .Write, .Append, .Creat, .Trunc:
		if .Read {
			if ,  := .FilePut.(OpenFileWriter);  {
				.Method = "Open"
				,  := .OpenFile()
				if  != nil {
					return statusFromError(, )
				}

				.setWriterAtReaderAt()

				return &sshFxpHandlePacket{
					ID:     ,
					Handle: .handle,
				}
			}
		}

		.Method = "Put"
		,  := .FilePut.Filewrite()
		if  != nil {
			return statusFromError(, )
		}

		.setWriterAt()

	case .Read:
		.Method = "Get"
		,  := .FileGet.Fileread()
		if  != nil {
			return statusFromError(, )
		}

		.setReaderAt()

	default:
		return statusFromError(, errors.New("bad file flags"))
	}

	return &sshFxpHandlePacket{
		ID:     ,
		Handle: .handle,
	}
}

func ( *Request) ( Handlers,  requestPacket) responsePacket {
	.Method = "List"
	,  := .FileList.Filelist()
	if  != nil {
		return statusFromError(.id(), wrapPathError(.Filepath, ))
	}

	.setListerAt()

	return &sshFxpHandlePacket{
		ID:     .id(),
		Handle: .handle,
	}
}

// wrap FileReader handler
func fileget( FileReader,  *Request,  requestPacket,  *allocator,  uint32) responsePacket {
	 := .getReaderAt()
	if  == nil {
		return statusFromError(.id(), errors.New("unexpected read packet"))
	}

	, ,  := packetData(, , )

	,  := .ReadAt(, )
	// only return EOF error if no data left to read
	if  != nil && ( != io.EOF ||  == 0) {
		return statusFromError(.id(), )
	}

	return &sshFxpDataPacket{
		ID:     .id(),
		Length: uint32(),
		Data:   [:],
	}
}

// wrap FileWriter handler
func fileput( FileWriter,  *Request,  requestPacket,  *allocator,  uint32) responsePacket {
	 := .getWriterAt()
	if  == nil {
		return statusFromError(.id(), errors.New("unexpected write packet"))
	}

	, ,  := packetData(, , )

	,  := .WriteAt(, )
	return statusFromError(.id(), )
}

// wrap OpenFileWriter handler
func fileputget( FileWriter,  *Request,  requestPacket,  *allocator,  uint32) responsePacket {
	 := .getWriterAtReaderAt()
	if  == nil {
		return statusFromError(.id(), errors.New("unexpected write and read packet"))
	}

	switch p := .(type) {
	case *sshFxpReadPacket:
		,  := .getDataSlice(, ), int64(.Offset)

		,  := .ReadAt(, )
		// only return EOF error if no data left to read
		if  != nil && ( != io.EOF ||  == 0) {
			return statusFromError(.id(), )
		}

		return &sshFxpDataPacket{
			ID:     .id(),
			Length: uint32(),
			Data:   [:],
		}

	case *sshFxpWritePacket:
		,  := .Data, int64(.Offset)

		,  := .WriteAt(, )
		return statusFromError(.id(), )

	default:
		return statusFromError(.id(), errors.New("unexpected packet type for read or write"))
	}
}

// file data for additional read/write packets
func packetData( requestPacket,  *allocator,  uint32) ( []byte,  int64,  uint32) {
	switch p := .(type) {
	case *sshFxpReadPacket:
		return .getDataSlice(, ), int64(.Offset), .Len
	case *sshFxpWritePacket:
		return .Data, int64(.Offset), .Length
	}
	return
}

// wrap FileCmder handler
func filecmd( FileCmder,  *Request,  requestPacket) responsePacket {
	switch p := .(type) {
	case *sshFxpFsetstatPacket:
		.Flags = .Flags
		.Attrs = .Attrs.([]byte)
	}

	switch .Method {
	case "PosixRename":
		if ,  := .(PosixRenameFileCmder);  {
			 := .PosixRename()
			return statusFromError(.id(), )
		}

		// PosixRenameFileCmder not implemented handle this request as a Rename
		.Method = "Rename"
		 := .Filecmd()
		return statusFromError(.id(), )

	case "StatVFS":
		if ,  := .(StatVFSFileCmder);  {
			,  := .StatVFS()
			if  != nil {
				return statusFromError(.id(), )
			}
			.ID = .id()
			return 
		}

		return statusFromError(.id(), ErrSSHFxOpUnsupported)
	}

	 := .Filecmd()
	return statusFromError(.id(), )
}

// wrap FileLister handler
func filelist( FileLister,  *Request,  requestPacket) responsePacket {
	 := .getListerAt()
	if  == nil {
		return statusFromError(.id(), errors.New("unexpected dir packet"))
	}

	 := .lsNext()
	 := make([]os.FileInfo, MaxFilelist)
	,  := .ListAt(, )
	.lsInc(int64())
	// ignore EOF as we only return it when there are no results
	 = [:] // avoid need for nil tests below

	switch .Method {
	case "List":
		if  != nil && ( != io.EOF ||  == 0) {
			return statusFromError(.id(), )
		}

		 := make([]*sshFxpNameAttr, 0, len())

		// If the type conversion fails, we get untyped `nil`,
		// which is handled by not looking up any names.
		,  := .(NameLookupFileLister)

		for ,  := range  {
			 = append(, &sshFxpNameAttr{
				Name:     .Name(),
				LongName: runLs(, ),
				Attrs:    []interface{}{},
			})
		}

		return &sshFxpNamePacket{
			ID:        .id(),
			NameAttrs: ,
		}

	default:
		 = fmt.Errorf("unexpected method: %s", .Method)
		return statusFromError(.id(), )
	}
}

func filestat( FileLister,  *Request,  requestPacket) responsePacket {
	var  ListerAt
	var  error

	if .Method == "Lstat" {
		if ,  := .(LstatFileLister);  {
			,  = .Lstat()
		} else {
			// LstatFileLister not implemented handle this request as a Stat
			.Method = "Stat"
			,  = .Filelist()
		}
	} else {
		,  = .Filelist()
	}
	if  != nil {
		return statusFromError(.id(), )
	}
	 := make([]os.FileInfo, 1)
	,  := .ListAt(, 0)
	 = [:] // avoid need for nil tests below

	switch .Method {
	case "Stat", "Lstat":
		if  != nil &&  != io.EOF {
			return statusFromError(.id(), )
		}
		if  == 0 {
			 = &os.PathError{
				Op:   strings.ToLower(.Method),
				Path: .Filepath,
				Err:  syscall.ENOENT,
			}
			return statusFromError(.id(), )
		}
		return &sshFxpStatResponse{
			ID:   .id(),
			info: [0],
		}
	case "Readlink":
		if  != nil &&  != io.EOF {
			return statusFromError(.id(), )
		}
		if  == 0 {
			 = &os.PathError{
				Op:   "readlink",
				Path: .Filepath,
				Err:  syscall.ENOENT,
			}
			return statusFromError(.id(), )
		}
		 := [0].Name()
		return &sshFxpNamePacket{
			ID: .id(),
			NameAttrs: []*sshFxpNameAttr{
				{
					Name:     ,
					LongName: ,
					Attrs:    emptyFileStat,
				},
			},
		}
	default:
		 = fmt.Errorf("unexpected method: %s", .Method)
		return statusFromError(.id(), )
	}
}

func readlink( ReadlinkFileLister,  *Request,  requestPacket) responsePacket {
	,  := .Readlink(.Filepath)
	if  != nil {
		return statusFromError(.id(), )
	}
	return &sshFxpNamePacket{
		ID: .id(),
		NameAttrs: []*sshFxpNameAttr{
			{
				Name:     ,
				LongName: ,
				Attrs:    emptyFileStat,
			},
		},
	}
}

// init attributes of request object from packet data
func requestMethod( requestPacket) ( string) {
	switch .(type) {
	case *sshFxpReadPacket, *sshFxpWritePacket, *sshFxpOpenPacket:
		// set in open() above
	case *sshFxpOpendirPacket, *sshFxpReaddirPacket:
		// set in opendir() above
	case *sshFxpSetstatPacket, *sshFxpFsetstatPacket:
		 = "Setstat"
	case *sshFxpRenamePacket:
		 = "Rename"
	case *sshFxpSymlinkPacket:
		 = "Symlink"
	case *sshFxpRemovePacket:
		 = "Remove"
	case *sshFxpStatPacket, *sshFxpFstatPacket:
		 = "Stat"
	case *sshFxpLstatPacket:
		 = "Lstat"
	case *sshFxpRmdirPacket:
		 = "Rmdir"
	case *sshFxpReadlinkPacket:
		 = "Readlink"
	case *sshFxpMkdirPacket:
		 = "Mkdir"
	case *sshFxpExtendedPacketHardlink:
		 = "Link"
	}
	return 
}