Skip to content

Commit

Permalink
request-server: Introduce ReadlinkFileLister
Browse files Browse the repository at this point in the history
ReadlinkFileLister with its Readlink method allows returning paths without
misusing the os.FileInfo interface, whose Name() method should only return
the base name of a file.

By implementing ReadlinkFileLister, it is possible to easily return
symlinks of any kind (absolute, relative, multiple directory levels)
  • Loading branch information
georgmu committed Oct 14, 2022
1 parent b772dbe commit 6f60892
Show file tree
Hide file tree
Showing 4 changed files with 42 additions and 18 deletions.
17 changes: 1 addition & 16 deletions request-example.go
Original file line number Diff line number Diff line change
Expand Up @@ -391,21 +391,6 @@ func (fs *root) Filelist(r *Request) (ListerAt, error) {
return nil, err
}
return listerat{file}, nil

case "Readlink":
symlink, err := fs.readlink(r.Filepath)
if err != nil {
return nil, err
}

// SFTP-v2: The server will respond with a SSH_FXP_NAME packet containing only
// one name and a dummy attributes value.
return listerat{
&memFile{
name: symlink,
err: os.ErrNotExist, // prevent accidental use as a reader/writer.
},
}, nil
}

return nil, errors.New("unsupported")
Expand Down Expand Up @@ -434,7 +419,7 @@ func (fs *root) readdir(pathname string) ([]os.FileInfo, error) {
return files, nil
}

func (fs *root) readlink(pathname string) (string, error) {
func (fs *root) Readlink(pathname string) (string, error) {
file, err := fs.lfetch(pathname)
if err != nil {
return "", err
Expand Down
15 changes: 14 additions & 1 deletion request-interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ type StatVFSFileCmder interface {
// FileLister should return an object that fulfils the ListerAt interface
// Note in cases of an error, the error text will be sent to the client.
// Called for Methods: List, Stat, Readlink
//
// Since Filelist returns an os.FileInfo, this can make it non-ideal for impelmenting Readlink.
// This is because the Name receiver method defined by that interface defines that it should only return the base name.
// However, Readlink is required to be capable of returning essentially any arbitrary valid path relative or absolute.
// In order to implement this more expressive requirement, implement [ReadlinkFileLister] which will then be used instead.
type FileLister interface {
Filelist(*Request) (ListerAt, error)
}
Expand All @@ -94,7 +99,7 @@ type LstatFileLister interface {
//
// Up to v1.13.5 the signature for the RealPath method was:
//
// RealPath(string) string
// # RealPath(string) string
//
// we have added a legacyRealPathFileLister that implements the old method
// to ensure that your code does not break.
Expand All @@ -104,6 +109,14 @@ type RealPathFileLister interface {
RealPath(string) (string, error)
}

// ReadlinkFileLister is a FileLister that implements the Readlink method.
// By implementing the Readlink method, it is possible to return any arbitrary valid path relative or absolute.
// This allows giving a better response than via the default FileLister (which is limited to os.FileInfo, whose Name method should only return the base name of a file)
type ReadlinkFileLister interface {
FileLister
Readlink(string) (string, error)
}

// This interface is here for backward compatibility only
type legacyRealPathFileLister interface {
FileLister
Expand Down
4 changes: 4 additions & 0 deletions request-server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -607,6 +607,10 @@ func TestRequestSymlink(t *testing.T) {
for _, s := range symlinks {
err := p.cli.Symlink(s.target, s.name)
require.NoError(t, err, "Creating symlink %q with target %q failed", s.name, s.target)

rl, err := p.cli.ReadLink(s.name)
require.NoError(t, err, "ReadLink('%s') failed", s.name)
require.Equal(t, s.target, rl, "Unexpected result when reading symlink '%s'", s.name)
}

// test fetching via symlink
Expand Down
24 changes: 23 additions & 1 deletion request.go
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,12 @@ func (r *Request) call(handlers Handlers, pkt requestPacket, alloc *allocator, o
return filecmd(handlers.FileCmd, r, pkt)
case "List":
return filelist(handlers.FileList, r, pkt)
case "Stat", "Lstat", "Readlink":
case "Stat", "Lstat":
return filestat(handlers.FileList, r, pkt)
case "Readlink":
if readlinkFileLister, ok := handlers.FileList.(ReadlinkFileLister); ok {
return readlink(readlinkFileLister, r, pkt)
}
return filestat(handlers.FileList, r, pkt)
default:
return statusFromError(pkt.id(), fmt.Errorf("unexpected method: %s", r.Method))
Expand Down Expand Up @@ -599,6 +604,23 @@ func filestat(h FileLister, r *Request, pkt requestPacket) responsePacket {
}
}

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

// init attributes of request object from packet data
func requestMethod(p requestPacket) (method string) {
switch p.(type) {
Expand Down

0 comments on commit 6f60892

Please sign in to comment.