diff --git a/disk/disk.go b/disk/disk.go index 796565b8..25d41729 100644 --- a/disk/disk.go +++ b/disk/disk.go @@ -189,7 +189,7 @@ func (d *Disk) CreateFilesystem(spec FilesystemSpec) (filesystem.FileSystem, err case filesystem.TypeExt4: return ext4.Create(d.File, size, start, d.LogicalBlocksize, nil) case filesystem.TypeSquashfs: - return nil, errors.New("squashfs is a read-only filesystem") + return nil, filesystem.ErrReadonlyFilesystem default: return nil, errors.New("unknown filesystem type requested") } diff --git a/filesystem/ext4/ext4.go b/filesystem/ext4/ext4.go index 41872315..8f326e6b 100644 --- a/filesystem/ext4/ext4.go +++ b/filesystem/ext4/ext4.go @@ -684,6 +684,9 @@ func Read(file util.File, size, start, sectorsize int64) (*FileSystem, error) { }, nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + // Type returns the type code for the filesystem. Always returns filesystem.TypeExt4 func (fs *FileSystem) Type() filesystem.Type { return filesystem.TypeExt4 @@ -699,6 +702,44 @@ func (fs *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Mknod(pathname string, mode uint32, dev int) error { + return filesystem.ErrNotImplemented +} + +// creates a new link (also known as a hard link) to an existing file. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Link(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// creates a symbolic link named linkpath which contains the string target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Symlink(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chmod(name string, mode os.FileMode) error { + return filesystem.ErrNotImplemented +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chown(name string, uid, gid int) error { + return filesystem.ErrNotImplemented +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -808,12 +849,24 @@ func (fs *FileSystem) Label() string { return fs.superblock.volumeLabel } -// Rm remove file or directory at path. +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Rename(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// Deprecated: use filesystem.Remove(p string) instead +func (fs *FileSystem) Rm(p string) error { + return fs.Remove(p) +} + +// Removes file or directory at path. // If path is directory, it only will remove if it is empty. // If path is a file, it will remove the file. // Will not remove any parents. // Error if the file does not exist or is not an empty directory -func (fs *FileSystem) Rm(p string) error { +func (fs *FileSystem) Remove(p string) error { parentDir, entry, err := fs.getEntryAndParent(p) if err != nil { return err diff --git a/filesystem/fat32/fat32.go b/filesystem/fat32/fat32.go index 240b6632..fffb6027 100644 --- a/filesystem/fat32/fat32.go +++ b/filesystem/fat32/fat32.go @@ -470,6 +470,9 @@ func (fs *FileSystem) writeFat() error { return nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 func (fs *FileSystem) Type() filesystem.Type { return filesystem.TypeFat32 @@ -485,6 +488,34 @@ func (fs *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +func (fs *FileSystem) Mknod(_ string, _ uint32, _ int) error { + return filesystem.ErrNotSupported +} + +// creates a new link (also known as a hard link) to an existing file. +func (fs *FileSystem) Link(_, _ string) error { + return filesystem.ErrNotSupported +} + +// creates a symbolic link named linkpath which contains the string target. +func (fs *FileSystem) Symlink(_, _ string) error { + return filesystem.ErrNotSupported +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +func (fs *FileSystem) Chmod(_ string, _ os.FileMode) error { + return filesystem.ErrNotSupported +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +func (fs *FileSystem) Chown(_ string, _, _ int) error { + return filesystem.ErrNotSupported +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -606,6 +637,20 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { }, nil } +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Rename(oldpath, newpath string) error { + return filesystem.ErrNotImplemented +} + +// removes the named file or (empty) directory. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Remove(pathname string) error { + return filesystem.ErrNotImplemented +} + // Label get the label of the filesystem from the secial file in the root directory. // The label stored in the boot sector is ignored to mimic Windows behavior which // only stores and reads the label from the special file in the root directory. diff --git a/filesystem/fat32/fat32_test.go b/filesystem/fat32/fat32_test.go index 4e4150b0..99033c04 100644 --- a/filesystem/fat32/fat32_test.go +++ b/filesystem/fat32/fat32_test.go @@ -455,9 +455,9 @@ func TestFat32OpenFile(t *testing.T) { {"/CORTO1.TXT", os.O_RDWR, false, "This is a very long replacement string", "Tenemos un archivo corto\nThis is a very long replacement string", nil}, {"/CORTO1.TXT", os.O_RDWR, false, "Two", "Tenemos un archivo corto\nTwo", nil}, // - open for append file that does exist (write contents, check that appended) - {"/CORTO1.TXT", os.O_APPEND, false, "More", "", fmt.Errorf("cannot write to file opened read-only")}, + {"/CORTO1.TXT", os.O_APPEND, false, "More", "", filesystem.ErrReadonlyFilesystem}, {"/CORTO1.TXT", os.O_APPEND | os.O_RDWR, false, "More", "Tenemos un archivo corto\nMore", nil}, - {"/CORTO1.TXT", os.O_APPEND, true, "More", "", fmt.Errorf("cannot write to file opened read-only")}, + {"/CORTO1.TXT", os.O_APPEND, true, "More", "", filesystem.ErrReadonlyFilesystem}, {"/CORTO1.TXT", os.O_APPEND | os.O_RDWR, true, "More", "Moremos un archivo corto\n", nil}, } for _, t2 := range tests { diff --git a/filesystem/fat32/file.go b/filesystem/fat32/file.go index 73a9a6a7..d6cdb799 100644 --- a/filesystem/fat32/file.go +++ b/filesystem/fat32/file.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + + "github.com/diskfs/go-diskfs/filesystem" ) // File represents a single file in a FAT32 filesystem @@ -108,7 +110,7 @@ func (fl *File) Write(p []byte) (int, error) { fs := fl.filesystem // if the file was not opened RDWR, nothing we can do if !fl.isReadWrite { - return totalWritten, fmt.Errorf("cannot write to file opened read-only") + return totalWritten, filesystem.ErrReadonlyFilesystem } // what is the new file size? writeSize := len(p) diff --git a/filesystem/filesystem.go b/filesystem/filesystem.go index 2c1acfa6..bf309d39 100644 --- a/filesystem/filesystem.go +++ b/filesystem/filesystem.go @@ -3,25 +3,49 @@ package filesystem import ( + "errors" "os" ) +var ( + ErrNotSupported = errors.New("method not supported by this filesystem") + ErrNotImplemented = errors.New("method not implemented (patches are welcome)") + ErrReadonlyFilesystem = errors.New("read-only filesystem") +) + // FileSystem is a reference to a single filesystem on a disk type FileSystem interface { // Type return the type of filesystem Type() Type // Mkdir make a directory - Mkdir(string) error + Mkdir(pathname string) error + // creates a filesystem node (file, device special file, or named pipe) named pathname, + // with attributes specified by mode and dev + Mknod(pathname string, mode uint32, dev int) error + // creates a new link (also known as a hard link) to an existing file. + Link(oldpath, newpath string) error + // creates a symbolic link named linkpath which contains the string target. + Symlink(oldpath, newpath string) error + // Chmod changes the mode of the named file to mode. If the file is a symbolic link, + // it changes the mode of the link's target. + Chmod(name string, mode os.FileMode) error + // Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, + // it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value + Chown(name string, uid, gid int) error // ReadDir read the contents of a directory - ReadDir(string) ([]os.FileInfo, error) + ReadDir(pathname string) ([]os.FileInfo, error) // OpenFile open a handle to read or write to a file - OpenFile(string, int) (File, error) + OpenFile(pathname string, flag int) (File, error) + // Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. + Rename(oldpath, newpath string) error + // removes the named file or (empty) directory. + Remove(pathname string) error // Label get the label for the filesystem, or "" if none. Be careful to trim it, as it may contain // leading or following whitespace. The label is passed as-is and not cleaned up at all. Label() string // SetLabel changes the label on the writable filesystem. Different file system may hav different // length constraints. - SetLabel(string) error + SetLabel(label string) error } // Type represents the type of disk this is diff --git a/filesystem/iso9660/file.go b/filesystem/iso9660/file.go index 83dff8b7..0b10ff8b 100644 --- a/filesystem/iso9660/file.go +++ b/filesystem/iso9660/file.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + + "github.com/diskfs/go-diskfs/filesystem" ) // File represents a single file in an iso9660 filesystem @@ -65,7 +67,7 @@ func (fl *File) Read(b []byte) (int, error) { // // you cannot write to an iso, so this returns an error func (fl *File) Write(_ []byte) (int, error) { - return 0, fmt.Errorf("cannot write to a read-only iso filesystem") + return 0, filesystem.ErrReadonlyFilesystem } // Seek set the offset to a particular point in the file diff --git a/filesystem/iso9660/iso9660.go b/filesystem/iso9660/iso9660.go index 64b28ed2..6cb56df1 100644 --- a/filesystem/iso9660/iso9660.go +++ b/filesystem/iso9660/iso9660.go @@ -282,6 +282,9 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { return fs, nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 func (fsm *FileSystem) Type() filesystem.Type { return filesystem.TypeISO9660 @@ -295,7 +298,7 @@ func (fsm *FileSystem) Type() filesystem.Type { // if readonly and not in workspace, will return an error func (fsm *FileSystem) Mkdir(p string) error { if fsm.workspace == "" { - return fmt.Errorf("cannot write to read-only filesystem") + return filesystem.ErrReadonlyFilesystem } err := os.MkdirAll(path.Join(fsm.workspace, p), 0o755) if err != nil { @@ -305,6 +308,50 @@ func (fsm *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Mknod(pathname string, mode uint32, dev int) error { + // Rock Ridge has device files support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + +// creates a new link (also known as a hard link) to an existing file. +func (fsm *FileSystem) Link(_, _ string) error { + return filesystem.ErrNotSupported +} + +// creates a symbolic link named linkpath which contains the string target. +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Symlink(oldpath, newpath string) error { + // Rock Ridge has symlink support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Chmod(name string, mode os.FileMode) error { + // Rock Ridge has UNIX-style file modes support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +// +//nolint:revive // parameters will be used eventually +func (fsm *FileSystem) Chown(name string, uid, gid int) error { + // Rock Ridge has user ids and group ids support + // https://en.wikipedia.org/wiki/ISO_9660#Rock_Ridge + return filesystem.ErrNotImplemented +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -368,7 +415,7 @@ func (fsm *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { writeMode := flag&os.O_WRONLY != 0 || flag&os.O_RDWR != 0 || flag&os.O_APPEND != 0 || flag&os.O_CREATE != 0 || flag&os.O_TRUNC != 0 || flag&os.O_EXCL != 0 if fsm.workspace == "" { if writeMode { - return nil, fmt.Errorf("cannot write to read-only filesystem") + return nil, filesystem.ErrReadonlyFilesystem } // get the directory entries @@ -414,6 +461,21 @@ func (fsm *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { return f, nil } +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +func (fsm *FileSystem) Rename(oldpath, newpath string) error { + if fsm.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Rename(path.Join(fsm.workspace, oldpath), path.Join(fsm.workspace, newpath)) +} + +func (fsm *FileSystem) Remove(p string) error { + if fsm.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Remove(path.Join(fsm.workspace, p)) +} + // readDirectory - read directory entry on iso only (not workspace) func (fsm *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { var ( diff --git a/filesystem/squashfs/file.go b/filesystem/squashfs/file.go index 1b77d969..d3c08f8f 100644 --- a/filesystem/squashfs/file.go +++ b/filesystem/squashfs/file.go @@ -4,6 +4,8 @@ import ( "fmt" "io" "os" + + "github.com/diskfs/go-diskfs/filesystem" ) // File represents a single file in a squashfs filesystem @@ -148,7 +150,7 @@ func (fl *File) Read(b []byte) (int, error) { // //nolint:unused,revive // but it is important to implement the interface func (fl *File) Write(p []byte) (int, error) { - return 0, fmt.Errorf("cannot write to a read-only squashfs filesystem") + return 0, filesystem.ErrReadonlyFilesystem } // Seek set the offset to a particular point in the file diff --git a/filesystem/squashfs/squashfs.go b/filesystem/squashfs/squashfs.go index bdd8d5a7..8b769deb 100644 --- a/filesystem/squashfs/squashfs.go +++ b/filesystem/squashfs/squashfs.go @@ -49,7 +49,7 @@ func (fs *FileSystem) Label() string { } func (fs *FileSystem) SetLabel(string) error { - return fmt.Errorf("SquashFS filesystem is read-only") + return filesystem.ErrReadonlyFilesystem } // Workspace get the workspace path @@ -229,6 +229,9 @@ func Read(file util.File, size, start, blocksize int64) (*FileSystem, error) { return fs, nil } +// interface guard +var _ filesystem.FileSystem = (*FileSystem)(nil) + // Type returns the type code for the filesystem. Always returns filesystem.TypeFat32 func (fs *FileSystem) Type() filesystem.Type { return filesystem.TypeSquashfs @@ -266,7 +269,7 @@ func (fs *FileSystem) GetCacheSize() int { // if readonly and not in workspace, will return an error func (fs *FileSystem) Mkdir(p string) error { if fs.workspace == "" { - return fmt.Errorf("cannot write to read-only filesystem") + return filesystem.ErrReadonlyFilesystem } err := os.MkdirAll(path.Join(fs.workspace, p), 0o755) if err != nil { @@ -276,6 +279,50 @@ func (fs *FileSystem) Mkdir(p string) error { return err } +// creates a filesystem node (file, device special file, or named pipe) named pathname, +// with attributes specified by mode and dev +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Mknod(pathname string, mode uint32, dev int) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_device_special_files + // https://dr-emann.github.io/squashfs/squashfs.html#_ipc_inodes_fifo_or_socket + return filesystem.ErrNotImplemented +} + +// creates a new link (also known as a hard link) to an existing file. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Link(oldpath, newpath string) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_symbolic_links + return filesystem.ErrNotImplemented +} + +// creates a symbolic link named linkpath which contains the string target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Symlink(oldpath, newpath string) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_symbolic_links + return filesystem.ErrNotImplemented +} + +// Chmod changes the mode of the named file to mode. If the file is a symbolic link, +// it changes the mode of the link's target. +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chmod(name string, mode os.FileMode) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_common_inode_header + return filesystem.ErrNotImplemented +} + +// Chown changes the numeric uid and gid of the named file. If the file is a symbolic link, +// it changes the uid and gid of the link's target. A uid or gid of -1 means to not change that value +// +//nolint:revive // parameters will be used eventually +func (fs *FileSystem) Chown(name string, uid, gid int) error { + // https://dr-emann.github.io/squashfs/squashfs.html#_id_table + return filesystem.ErrNotImplemented +} + // ReadDir return the contents of a given directory in a given filesystem. // // Returns a slice of os.FileInfo with all of the entries in the directory. @@ -336,7 +383,7 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { writeMode := flag&os.O_WRONLY != 0 || flag&os.O_RDWR != 0 || flag&os.O_APPEND != 0 || flag&os.O_CREATE != 0 || flag&os.O_TRUNC != 0 || flag&os.O_EXCL != 0 if fs.workspace == "" { if writeMode { - return nil, fmt.Errorf("cannot write to read-only filesystem") + return nil, filesystem.ErrReadonlyFilesystem } // get the directory entries @@ -379,6 +426,21 @@ func (fs *FileSystem) OpenFile(p string, flag int) (filesystem.File, error) { return f, nil } +// Rename renames (moves) oldpath to newpath. If newpath already exists and is not a directory, Rename replaces it. +func (fs *FileSystem) Rename(oldpath, newpath string) error { + if fs.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Rename(path.Join(fs.workspace, oldpath), path.Join(fs.workspace, newpath)) +} + +func (fs *FileSystem) Remove(p string) error { + if fs.workspace == "" { + return filesystem.ErrReadonlyFilesystem + } + return os.Remove(path.Join(fs.workspace, p)) +} + // readDirectory - read directory entry on squashfs only (not workspace) func (fs *FileSystem) readDirectory(p string) ([]*directoryEntry, error) { // use the root inode to find the location of the root direectory in the table