Last active
May 9, 2021 00:04
-
-
Save kylelemons/21539a152e9af1dd79c3775ca94efb60 to your computer and use it in GitHub Desktop.
Sketch of writable filesystem interfaces for io/fs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fs | |
import ( | |
"fmt" | |
"io" | |
"os" | |
. "io/fs" | |
) | |
var ErrUnsupported = fmt.Errorf("unsupported operation") | |
type WriteFileFS interface { | |
WriteFile(name string, data []byte, mode FileMode) error | |
} | |
// WriteFileMode writes the data to a file. | |
// | |
// This requires that the underlying filesystem implement modes via | |
// WriteFileMode, OpenFileMode, and/or returning a file that supports Chmod. | |
func WriteFileMode(fs FS, name string, data []byte, mode FileMode) error { | |
switch fs := fs.(type) { | |
case WriteFileFS: | |
return fs.WriteFile(name, data, mode) | |
} | |
// If the FS only supports Open, try to set the FileMode after opening | |
f, err := OpenFile(fs, name, mode) | |
if err != nil { | |
return fmt.Errorf("Open: %w", err) | |
} | |
return writeContents(f, data) | |
} | |
type OpenFileFS interface { | |
OpenFile(name string, mode FileMode) (File, error) | |
} | |
type ChmodFile interface { | |
Chmod(mode FileMode) error | |
} | |
var _ ChmodFile = (*os.File)(nil) | |
func OpenFile(fs FS, name string, mode FileMode) (File, error) { | |
// If the FS supports opening with permissions, open the file | |
if fs, ok := fs.(OpenFileFS); ok { | |
return fs.OpenFile(name, mode) | |
} | |
// If the FS only supports Open, try to set the FileMode after opening | |
f, err := fs.Open(name) | |
if err != nil { | |
return nil, fmt.Errorf("Open: %w", err) | |
} | |
// Chmod isn't required if the mode bits are already correct. | |
stat, err := f.Stat() | |
if err == nil && stat.Mode()&ModePerm == mode { | |
return f, nil | |
} | |
cm, ok := f.(ChmodFile) | |
if !ok { | |
return f, fmt.Errorf("%w: %T does not support chmod", ErrUnsupported, f) | |
} | |
if err := cm.Chmod(mode); err != nil { | |
return f, fmt.Errorf("Chmod: %w", err) | |
} | |
return f, nil | |
} | |
// writeContents writes data to the file and then closes it. | |
func writeContents(f File, data []byte) error { | |
defer f.Close() | |
if n, err := Write(f, data); err != nil { | |
return fmt.Errorf("Write: %w", err) | |
} else if want := len(data); n != want { | |
return fmt.Errorf("%w: wrote %d bytes of %d", io.ErrShortWrite, n, want) | |
} | |
if err := f.Close(); err != nil { | |
return fmt.Errorf("Close: %w", err) | |
} | |
return nil | |
} | |
func Write(f File, data []byte) (n int, err error) { | |
w, ok := f.(io.Writer) | |
if !ok { | |
return 0, fmt.Errorf("%w: %T does not implement %T", ErrUnsupported, f, w) | |
} | |
return w.Write(data) | |
} | |
type CreateFS interface { | |
Create(name string) (File, error) | |
} | |
type TruncateFile interface { | |
Truncate(int64) error | |
} | |
var _ TruncateFile = (*os.File)(nil) | |
// Create returns a zero-length file with the given name. | |
// | |
// If the filesystem does not support Create directly, the Opened file will | |
// be Truncated before returning if its size is nonzero. | |
func Create(fs FS, name string) (_ File, _err error) { | |
// If the FS supports this as a direct operation, use it | |
if fs, ok := fs.(CreateFS); ok { | |
return fs.Create(name) | |
} | |
// Otherwise Create needs to try to truncate the file after opening. | |
f, err := fs.Open(name) | |
if err != nil { | |
return nil, fmt.Errorf("Open: %w", err) | |
} | |
defer func() { | |
if _err != nil { | |
f.Close() | |
} | |
}() | |
// Truncate isn't required if the file is already zero. | |
stat, err := f.Stat() | |
if err == nil && stat.Size() == 0 { | |
return f, nil | |
} | |
t, ok := f.(TruncateFile) | |
if !ok { | |
return f, fmt.Errorf("%w: %T does not support truncation, file may contain data", ErrUnsupported, f) | |
} | |
if err := t.Truncate(0); err != nil { | |
return f, fmt.Errorf("Truncate: %w", err) | |
} | |
return f, nil | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment