diff --git a/fs/dir.go b/fs/dir.go
index c55bcea23..5830c6f38 100644
--- a/fs/dir.go
+++ b/fs/dir.go
@@ -7,6 +7,7 @@ import (
 
 // Dir describes an unspecialized directory for directory/container/bucket lists
 type Dir struct {
+	f       Info      // Fs this directory is part of
 	remote  string    // name of the directory
 	modTime time.Time // modification or creation time - IsZero for unknown
 	size    int64     // size of directory and contents or -1 if unknown
@@ -20,6 +21,7 @@ type Dir struct {
 // If the modTime is unknown pass in time.Time{}
 func NewDir(remote string, modTime time.Time) *Dir {
 	return &Dir{
+		f:       Unknown,
 		remote:  remote,
 		modTime: modTime,
 		size:    -1,
@@ -30,6 +32,7 @@ func NewDir(remote string, modTime time.Time) *Dir {
 // NewDirCopy creates an unspecialized copy of the Directory object passed in
 func NewDirCopy(ctx context.Context, d Directory) *Dir {
 	return &Dir{
+		f:       d.Fs(),
 		remote:  d.Remote(),
 		modTime: d.ModTime(ctx),
 		size:    d.Size(),
@@ -38,6 +41,11 @@ func NewDirCopy(ctx context.Context, d Directory) *Dir {
 	}
 }
 
+// Fs returns the Fs that this directory is part of
+func (d *Dir) Fs() Info {
+	return d.f
+}
+
 // String returns the name
 func (d *Dir) String() string {
 	return d.remote
diff --git a/fs/list/list_test.go b/fs/list/list_test.go
index 59312d898..616271746 100644
--- a/fs/list/list_test.go
+++ b/fs/list/list_test.go
@@ -87,6 +87,7 @@ func TestFilterAndSortCheckDirRoot(t *testing.T) {
 
 type unknownDirEntry string
 
+func (o unknownDirEntry) Fs() fs.Info                               { return fs.Unknown }
 func (o unknownDirEntry) String() string                            { return string(o) }
 func (o unknownDirEntry) Remote() string                            { return string(o) }
 func (o unknownDirEntry) ModTime(ctx context.Context) (t time.Time) { return t }
diff --git a/fs/metadata.go b/fs/metadata.go
index 19b07cb63..919e7862a 100644
--- a/fs/metadata.go
+++ b/fs/metadata.go
@@ -63,10 +63,10 @@ func (m *Metadata) MergeOptions(options []OpenOption) {
 	}
 }
 
-// GetMetadata from an ObjectInfo
+// GetMetadata from an DirEntry
 //
 // If the object has no metadata then metadata will be nil
-func GetMetadata(ctx context.Context, o ObjectInfo) (metadata Metadata, err error) {
+func GetMetadata(ctx context.Context, o DirEntry) (metadata Metadata, err error) {
 	do, ok := o.(Metadataer)
 	if !ok {
 		return nil, nil
@@ -91,7 +91,7 @@ type mapItem struct {
 
 // This runs an external program on the metadata which can be used to
 // map it from one form to another.
-func metadataMapper(ctx context.Context, cmdLine SpaceSepList, dstFs Fs, o ObjectInfo, metadata Metadata) (newMetadata Metadata, err error) {
+func metadataMapper(ctx context.Context, cmdLine SpaceSepList, dstFs Fs, o DirEntry, metadata Metadata) (newMetadata Metadata, err error) {
 	ci := GetConfig(ctx)
 	cmd := exec.Command(cmdLine[0], cmdLine[1:]...)
 	in := mapItem{
@@ -145,14 +145,14 @@ func metadataMapper(ctx context.Context, cmdLine SpaceSepList, dstFs Fs, o Objec
 	return out.Metadata, nil
 }
 
-// GetMetadataOptions from an ObjectInfo and merge it with any in options
+// GetMetadataOptions from an DirEntry and merge it with any in options
 //
 // If --metadata isn't in use it will return nil.
 //
 // If the object has no metadata then metadata will be nil.
 //
 // This should be passed the destination Fs for the metadata mapper
-func GetMetadataOptions(ctx context.Context, dstFs Fs, o ObjectInfo, options []OpenOption) (metadata Metadata, err error) {
+func GetMetadataOptions(ctx context.Context, dstFs Fs, o DirEntry, options []OpenOption) (metadata Metadata, err error) {
 	ci := GetConfig(ctx)
 	if !ci.Metadata {
 		return nil, nil
diff --git a/fs/mimetype.go b/fs/mimetype.go
index 4ea9aab2a..a0bfcd9fe 100644
--- a/fs/mimetype.go
+++ b/fs/mimetype.go
@@ -54,7 +54,7 @@ func MimeTypeFromName(remote string) (mimeType string) {
 
 // MimeType returns the MimeType from the object, either by calling
 // the MimeTyper interface or using MimeTypeFromName
-func MimeType(ctx context.Context, o ObjectInfo) (mimeType string) {
+func MimeType(ctx context.Context, o DirEntry) (mimeType string) {
 	// Read the MimeType from the optional interface if available
 	if do, ok := o.(MimeTyper); ok {
 		mimeType = do.MimeType(ctx)
diff --git a/fs/types.go b/fs/types.go
index 16afe79d8..a709f628b 100644
--- a/fs/types.go
+++ b/fs/types.go
@@ -102,9 +102,6 @@ type Object interface {
 type ObjectInfo interface {
 	DirEntry
 
-	// Fs returns read only access to the Fs that this object is part of
-	Fs() Info
-
 	// Hash returns the selected checksum of the file
 	// If no checksum is available it returns ""
 	Hash(ctx context.Context, ty hash.Type) (string, error)
@@ -117,6 +114,9 @@ type ObjectInfo interface {
 // a Dir or Object.  These are returned from directory listings - type
 // assert them into the correct type.
 type DirEntry interface {
+	// Fs returns read only access to the Fs that this object is part of
+	Fs() Info
+
 	// String returns a description of the Object
 	String() string
 
@@ -332,3 +332,28 @@ type WriterAtCloser interface {
 	io.WriterAt
 	io.Closer
 }
+
+type unknownFs struct{}
+
+// Name of the remote (as passed into NewFs)
+func (unknownFs) Name() string { return "unknown" }
+
+// Root of the remote (as passed into NewFs)
+func (unknownFs) Root() string { return "" }
+
+// String returns a description of the FS
+func (unknownFs) String() string { return "unknown" }
+
+// Precision of the ModTimes in this Fs
+func (unknownFs) Precision() time.Duration { return ModTimeNotSupported }
+
+// Returns the supported hash types of the filesystem
+func (unknownFs) Hashes() hash.Set { return hash.Set(hash.None) }
+
+// Features returns the optional features of this Fs
+func (unknownFs) Features() *Features { return &Features{} }
+
+// Unknown holds an Info for an unknown Fs
+//
+// This is used when we need an Fs but don't have one.
+var Unknown Info = unknownFs{}