covermyass/lib/shred/shred.go

107 lines
2.1 KiB
Go

package shred
import (
"crypto/rand"
"fmt"
"io/fs"
"os"
"time"
)
// A FileInfo describes a file and is returned by Stat.
type FileInfo interface {
Name() string // base name of the file
Size() int64 // length in bytes for regular files; system-dependent for others
Mode() fs.FileMode // file mode bits
ModTime() time.Time // modification time
IsDir() bool // abbreviation for Mode().IsDir()
Sys() any // underlying data source (can return nil)
}
type File interface {
Seek(int64, int) (int64, error)
Sync() error
Write([]byte) (int, error)
WriteAt([]byte, int64) (int, error)
Close() error
}
type ProgressBar interface {
Add(int) error
Write([]byte) (int, error)
Finish() error
}
type ShredderOptions struct {
Zero bool
Iterations int
Bar ProgressBar
}
type Shredder struct {
options *ShredderOptions
}
func New(opts *ShredderOptions) *Shredder {
return &Shredder{opts}
}
func (s *Shredder) Write(pathName string) error {
// Stat the file for the file length
fstat, err := os.Stat(pathName)
if err != nil {
return fmt.Errorf("file stat failed: %w", err)
}
// Open the file
file, err := os.OpenFile(pathName, os.O_WRONLY, fstat.Mode())
if err != nil {
return fmt.Errorf("file opening failed: %w", err)
}
defer file.Close()
err = s.shred(fstat, file)
if err != nil {
return fmt.Errorf("shredding failed: %w", err)
}
if s.options.Zero {
if err := os.Truncate(pathName, 0); err != nil {
return fmt.Errorf("truncate failed: %w", err)
}
}
return nil
}
func (s *Shredder) shred(fstat FileInfo, file File) error {
fSize := fstat.Size()
// Avoid shredding if the file is already empty
if fSize == 0 {
return nil
}
// Write random bytes over the file N times
junkBuf := make([]byte, fstat.Size())
for i := 0; i < s.options.Iterations; i++ {
_, err := rand.Read(junkBuf)
if err != nil {
return err
}
_, err = file.WriteAt(junkBuf, 0)
if err != nil {
return err
}
err = file.Sync()
if err != nil {
return err
}
// Add written bytes to progress bar
_, _ = s.options.Bar.Write(junkBuf)
}
return nil
}