Skip to content

Commit 2a997a9

Browse files
authored
Fs implementations (#329)
* move fs.FS into FileSystem interface definition Signed-off-by: Avi Deitcher <[email protected]> * implement fs.ReadDirFS Signed-off-by: Avi Deitcher <[email protected]> * implement fs.ReadFileFS Signed-off-by: Avi Deitcher <[email protected]> * implement statfs Signed-off-by: Avi Deitcher <[email protected]> --------- Signed-off-by: Avi Deitcher <[email protected]>
1 parent a2e2539 commit 2a997a9

File tree

13 files changed

+256
-78
lines changed

13 files changed

+256
-78
lines changed

examples/iso_info.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,11 @@ func fileInfoFor(path string, fs filesystem.FileSystem) error {
3434
}
3535

3636
for _, file := range files {
37+
fileInfo, err := file.Info()
38+
if err != nil {
39+
fmt.Printf("Failed to get info for file %s: %v\n", file.Name(), err)
40+
continue
41+
}
3742
fullPath := filepath.Join(path, file.Name())
3843
if file.IsDir() {
3944
err = fileInfoFor(fullPath, fs)
@@ -53,7 +58,7 @@ func fileInfoFor(path string, fs filesystem.FileSystem) error {
5358
fmt.Printf("Failed to cast to iso9660.File for %s\n", fullPath)
5459
continue
5560
}
56-
fmt.Printf("%s\n Size: %d\n Location: %d\n\n", fullPath, file.Size(), myFile.Location())
61+
fmt.Printf("%s\n Size: %d\n Location: %d\n\n", fullPath, fileInfo.Size(), myFile.Location())
5762
}
5863
return nil
5964
}

filesystem/compatibility.go

Lines changed: 5 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,10 @@ func (f *fsCompatible) Open(name string) (fs.File, error) {
7676
return &fsDirWrapper{name: name, compat: f, stat: &fakeRootDir{}}, nil
7777
}
7878
dirname := path.Dir(name)
79-
if info, err := f.fs.ReadDir(dirname); err == nil {
80-
for i := range info {
81-
if info[i].Name() == path.Base(name) {
82-
stat = info[i]
79+
if de, err := f.fs.ReadDir(dirname); err == nil {
80+
for i := range de {
81+
if de[i].Name() == path.Base(name) {
82+
stat, _ = de[i].Info()
8383
break
8484
}
8585
}
@@ -98,15 +98,7 @@ func (f *fsCompatible) Open(name string) (fs.File, error) {
9898
}
9999

100100
func (f *fsCompatible) ReadDir(name string) ([]fs.DirEntry, error) {
101-
entries, err := f.fs.ReadDir(name)
102-
if err != nil {
103-
return nil, err
104-
}
105-
direntries := make([]fs.DirEntry, len(entries))
106-
for i := range entries {
107-
direntries[i] = fs.FileInfoToDirEntry(entries[i])
108-
}
109-
return direntries, nil
101+
return f.fs.ReadDir(name)
110102
}
111103

112104
// FS converts a diskfs FileSystem to a fs.FS for compatibility with

filesystem/ext4/directoryentry.go

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package ext4
33
import (
44
"encoding/binary"
55
"fmt"
6+
iofs "io/fs"
67
)
78

89
// directoryFileType uses different constants than the file type property in the inode
@@ -23,7 +24,9 @@ const (
2324
dirFileTypeSymlink directoryFileType = 0x7
2425
)
2526

26-
// directoryEntry is a single directory entry
27+
// directoryEntry is a single directory entry, represents a low-level entry
28+
// per the data on the actual disk.
29+
// For higher-level directory entries, see Directory struct or directoryEntryInfo.
2730
type directoryEntry struct {
2831
inode uint32
2932
filename string
@@ -174,3 +177,32 @@ func parseDirEntriesHashed(b []byte, depth uint8, node dxNode, blocksize uint32,
174177
}
175178
return dirEntries, nil
176179
}
180+
181+
type directoryEntryInfo struct {
182+
*directoryEntry
183+
*inode
184+
}
185+
186+
func (de *directoryEntryInfo) Info() (iofs.FileInfo, error) {
187+
return &FileInfo{
188+
modTime: de.modifyTime,
189+
name: de.filename,
190+
size: int64(de.size),
191+
isDir: de.directoryEntry.fileType == dirFileTypeDirectory,
192+
}, nil
193+
}
194+
195+
func (de *directoryEntryInfo) Type() iofs.FileMode {
196+
if de.directoryEntry.fileType == dirFileTypeDirectory {
197+
return iofs.ModeDir
198+
}
199+
return 0
200+
}
201+
202+
func (de *directoryEntryInfo) IsDir() bool {
203+
return de.directoryEntry.fileType == dirFileTypeDirectory
204+
}
205+
206+
func (de *directoryEntryInfo) Name() string {
207+
return de.filename
208+
}

filesystem/ext4/ext4.go

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,6 @@ func Read(b backend.Storage, size, start, sectorsize int64) (*FileSystem, error)
709709

710710
// interface guard
711711
var _ filesystem.FileSystem = (*FileSystem)(nil)
712-
var _ iofs.FS = (*FileSystem)(nil)
713712

714713
// Do cleaning job for ext4. Note that ext4 does not have side-effects so we do not do anything.
715714
func (fs *FileSystem) Close() error {
@@ -792,28 +791,26 @@ func (fs *FileSystem) Chown(name string, uid, gid int) error {
792791

793792
// ReadDir return the contents of a given directory in a given filesystem.
794793
//
795-
// Returns a slice of os.FileInfo with all of the entries in the directory.
794+
// Returns a slice of iofs.DirEntry with all of the entries in the directory.
796795
//
797796
// Will return an error if the directory does not exist or is a regular file and not a directory
798-
func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) {
797+
func (fs *FileSystem) ReadDir(p string) ([]iofs.DirEntry, error) {
799798
dir, err := fs.readDirWithMkdir(p, false)
800799
if err != nil {
801800
return nil, fmt.Errorf("error reading directory %s: %v", p, err)
802801
}
803802
// once we have made it here, looping is done. We have found the final entry
804803
// we need to return all of the file info
805804
count := len(dir.entries)
806-
ret := make([]os.FileInfo, count)
805+
ret := make([]iofs.DirEntry, count)
807806
for i, e := range dir.entries {
808807
in, err := fs.readInode(e.inode)
809808
if err != nil {
810809
return nil, fmt.Errorf("could not read inode %d at position %d in directory: %v", e.inode, i, err)
811810
}
812-
ret[i] = &FileInfo{
813-
modTime: in.modifyTime,
814-
name: e.filename,
815-
size: int64(in.size),
816-
isDir: e.fileType == dirFileTypeDirectory,
811+
ret[i] = &directoryEntryInfo{
812+
inode: in,
813+
directoryEntry: e,
817814
}
818815
}
819816

@@ -898,6 +895,16 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) {
898895
}, nil
899896
}
900897

898+
// ReadFile implements ReadFileFS to read an entire file into memory
899+
func (fs *FileSystem) ReadFile(name string) ([]byte, error) {
900+
f, err := fs.Open(name)
901+
if err != nil {
902+
return nil, err
903+
}
904+
defer f.Close()
905+
return io.ReadAll(f)
906+
}
907+
901908
// Label read the volume label
902909
func (fs *FileSystem) Label() string {
903910
if fs.superblock == nil {

filesystem/fat32/directoryentry.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fat32
33
import (
44
"encoding/binary"
55
"fmt"
6+
iofs "io/fs"
67
"regexp"
78
"strings"
89
"time"
@@ -47,6 +48,34 @@ type directoryEntry struct {
4748
isNew bool
4849
}
4950

51+
func (de *directoryEntry) Info() (iofs.FileInfo, error) {
52+
return FileInfo{
53+
modTime: de.modifyTime,
54+
name: de.filenameLong,
55+
shortName: shortNameFromDirEntry(de),
56+
size: int64(de.fileSize),
57+
isDir: de.isSubdirectory,
58+
}, nil
59+
}
60+
61+
func (de *directoryEntry) IsDir() bool {
62+
return de.isSubdirectory
63+
}
64+
65+
func (de *directoryEntry) Type() iofs.FileMode {
66+
if de.isSubdirectory {
67+
return iofs.ModeDir
68+
}
69+
return 0
70+
}
71+
72+
func (de *directoryEntry) Name() string {
73+
if de.filenameLong != "" {
74+
return de.filenameLong
75+
}
76+
return shortNameFromDirEntry(de)
77+
}
78+
5079
func (de *directoryEntry) toBytes() ([]byte, error) {
5180
b := make([]byte, 0, bytesPerSlot)
5281

filesystem/fat32/fat32.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package fat32
33
import (
44
"errors"
55
"fmt"
6+
"io"
67
iofs "io/fs"
78
"os"
89
"path"
@@ -509,7 +510,6 @@ func (fs *FileSystem) writeFat() error {
509510

510511
// interface guard
511512
var _ filesystem.FileSystem = (*FileSystem)(nil)
512-
var _ iofs.FS = (*FileSystem)(nil)
513513

514514
// Do cleaning job for fat32. Note that fat32 does not have side-effects so we do not do anything.
515515
func (fs *FileSystem) Close() error {
@@ -598,29 +598,23 @@ func (fs *FileSystem) Chown(_ string, _, _ int) error {
598598

599599
// ReadDir return the contents of a given directory in a given filesystem.
600600
//
601-
// Returns a slice of os.FileInfo with all of the entries in the directory.
601+
// Returns a slice of iofs.DirEntry with all of the entries in the directory.
602602
//
603603
// Will return an error if the directory does not exist or is a regular file and not a directory
604-
func (fs *FileSystem) ReadDir(p string) ([]os.FileInfo, error) {
604+
func (fs *FileSystem) ReadDir(p string) ([]iofs.DirEntry, error) {
605605
_, entries, err := fs.readDirWithMkdir(p, false)
606606
if err != nil {
607607
return nil, fmt.Errorf("error reading directory %s: %w", p, err)
608608
}
609609
// once we have made it here, looping is done. We have found the final entry
610610
// we need to return all of the file info
611611
//nolint:prealloc // because the following loop may omit some entry
612-
var ret []os.FileInfo
612+
var ret []iofs.DirEntry
613613
for _, e := range entries {
614614
if e.isVolumeLabel {
615615
continue
616616
}
617-
ret = append(ret, FileInfo{
618-
modTime: e.modifyTime,
619-
name: e.filenameLong,
620-
shortName: shortNameFromDirEntry(e),
621-
size: int64(e.fileSize),
622-
isDir: e.isSubdirectory,
623-
})
617+
ret = append(ret, e)
624618
}
625619
return ret, nil
626620
}
@@ -713,6 +707,16 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) {
713707
}, nil
714708
}
715709

710+
// ReadFile implements ReadFileFS to read an entire file into memory
711+
func (fs *FileSystem) ReadFile(name string) ([]byte, error) {
712+
f, err := fs.Open(name)
713+
if err != nil {
714+
return nil, err
715+
}
716+
defer f.Close()
717+
return io.ReadAll(f)
718+
}
719+
716720
// removes the named file or (empty) directory.
717721
func (fs *FileSystem) Remove(pathname string) error {
718722
// get the path
@@ -837,6 +841,30 @@ func (fs *FileSystem) Rename(oldpath, newpath string) error {
837841
return nil
838842
}
839843

844+
// Stat returns a FileInfo describing the file.
845+
func (fs *FileSystem) Stat(name string) (iofs.FileInfo, error) {
846+
dir := path.Dir(name)
847+
basename := path.Base(name)
848+
des, err := fs.ReadDir(dir)
849+
if err != nil {
850+
return nil, fmt.Errorf("could not read directory %s: %v", dir, err)
851+
}
852+
// handle the root case
853+
if dir == basename && (basename == "/" || basename == ".") {
854+
rootDir, _, err := fs.readDirWithMkdir("/", false)
855+
if err != nil {
856+
return nil, fmt.Errorf("could not read root directory: %v", err)
857+
}
858+
return rootDir.Info()
859+
}
860+
for _, de := range des {
861+
if de.Name() == basename {
862+
return de.Info()
863+
}
864+
}
865+
return nil, &iofs.PathError{Op: "stat", Path: name, Err: fmt.Errorf("file %s not found in directory %s", basename, dir)}
866+
}
867+
840868
// Label get the label of the filesystem from the secial file in the root directory.
841869
// The label stored in the boot sector is ignored to mimic Windows behavior which
842870
// only stores and reads the label from the special file in the root directory.

filesystem/fat32/fat32_test.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -367,9 +367,14 @@ func TestFat32SourceDateEpoch(t *testing.T) {
367367
expected := time.Unix(epochInt, 0)
368368

369369
for _, entry := range entries {
370+
info, err := entry.Info()
371+
if err != nil {
372+
t.Errorf("getting info for %s failed: %v", entry.Name(), err)
373+
continue
374+
}
370375
if entry.Name() == "test.txt" || entry.Name() == "testdir" {
371-
if entry.ModTime().Unix() != expected.Unix() {
372-
t.Errorf("%s: timestamp mismatch, got %v, expected %v", entry.Name(), entry.ModTime().Unix(), expected.Unix())
376+
if info.ModTime().Unix() != expected.Unix() {
377+
t.Errorf("%s: timestamp mismatch, got %v, expected %v", entry.Name(), info.ModTime().Unix(), expected.Unix())
373378
}
374379
}
375380
}
@@ -687,10 +692,20 @@ func TestFat32ReadDir(t *testing.T) {
687692
t.Errorf("readDir(%s): Unexpected nil output", tt.path)
688693
case len(output) != tt.count:
689694
t.Errorf("readDir(%s): output gave %d entries instead of expected %d", tt.path, len(output), tt.count)
690-
case len(output) > 0 && output[0].IsDir() != tt.isDir:
691-
t.Errorf("readDir(%s): output gave directory %t expected %t", tt.path, output[0].IsDir(), tt.isDir)
692-
case len(output) > 0 && output[0].Name() != tt.name:
693-
t.Errorf("readDir(%s): output gave name %s expected %s", tt.path, output[0].Name(), tt.name)
695+
case len(output) > 0:
696+
if output[0].IsDir() != tt.isDir {
697+
t.Errorf("readDir(%s): output gave directory %t expected %t", tt.path, output[0].IsDir(), tt.isDir)
698+
}
699+
if output[0].Name() != tt.name {
700+
t.Errorf("readDir(%s): output gave name %s expected %s", tt.path, output[0].Name(), tt.name)
701+
}
702+
fi, err := output[0].Info()
703+
if err != nil {
704+
t.Fatalf("readDir(%s): Info() returned unexpected error: %v", tt.path, err)
705+
}
706+
if fi.Name() != tt.name {
707+
t.Errorf("readDir(%s): Info() returned name %s expected %s", tt.path, fi.Name(), tt.name)
708+
}
694709
}
695710
}
696711
}

filesystem/filesystem.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@ var (
1717

1818
// FileSystem is a reference to a single filesystem on a disk
1919
type FileSystem interface {
20+
fs.FS
21+
fs.ReadDirFS
22+
fs.ReadFileFS
23+
fs.StatFS
2024
// Type return the type of filesystem
2125
Type() Type
2226
// Mkdir make a directory
@@ -36,8 +40,6 @@ type FileSystem interface {
3640
Chown(name string, uid, gid int) error
3741
// Chtimes changes the file creation, access and modification times.
3842
Chtimes(pathname string, ctime, atime, mtime time.Time) error
39-
// ReadDir read the contents of a directory
40-
ReadDir(pathname string) ([]os.FileInfo, error)
4143
// Open open a handle to read a file, implements fs.FS
4244
Open(pathname string) (fs.File, error)
4345
// OpenFile open a handle to read or write to a file

filesystem/iso9660/directoryentry.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -514,6 +514,16 @@ func (de *directoryEntry) Sys() interface{} {
514514
return nil
515515
}
516516

517+
// Info returns the FileInfo structure, which directoryEntry already implements
518+
func (de *directoryEntry) Info() (os.FileInfo, error) {
519+
return de, nil
520+
}
521+
522+
// Type returns the type of the directory entry
523+
func (de *directoryEntry) Type() os.FileMode {
524+
return de.Mode()
525+
}
526+
517527
// utilities
518528

519529
func bytesToTime(b []byte) time.Time {

0 commit comments

Comments
 (0)