Skip to content

Commit

Permalink
ironlib is able to fill a disk with all zeros (#131)
Browse files Browse the repository at this point in the history
* Add skeleton of DiskWiper

Signed-off-by: Ignacio Turégano <[email protected]>

* added zero wipe logic

Signed-off-by: Ignacio Turégano <[email protected]>

* example of disk wipe with zeroes

Signed-off-by: Ignacio Turégano <[email protected]>

* zero wipe unit tests

Signed-off-by: Ignacio Turégano <[email protected]>

* fix example description

Signed-off-by: Ignacio Turégano <[email protected]>

* translate comments to english

Signed-off-by: Ignacio Turégano <[email protected]>

* Added speed, eta and device to the output

Signed-off-by: Ignacio Turégano <[email protected]>

* Fix defects and include improvements from PR #131

* Refactor zero wipe to fill with zeros from PR #131

* Modified the code to track the remaining bytes and handle the nearly final scenario when the buffer size exceeds the remaining disk space to delete. Also, returning 'magic' numbers

* Added function to print progress and reverted calculation to avoid a integer divide by zero

* Exceptions for shadowing err and magic numbers in utils

* Testing against a range of file sizes (also a non-existent file)

* Removed magic numbers exception and checked file.Sync()

* golangci-lint to tests

* using log to be goroutine safe

* Removed the verbose err and reduced the printProgress to just the print data

* Check if the context has been canceled during disk wipe

* new linter rules, modified code to satisfy them

* Conditioning log to trace variable

* gofumpt fill_zero_test

---------

Signed-off-by: Ignacio Turégano <[email protected]>
  • Loading branch information
turegano-equinix authored Apr 23, 2024
1 parent 8f46009 commit af14e38
Show file tree
Hide file tree
Showing 5 changed files with 205 additions and 0 deletions.
5 changes: 5 additions & 0 deletions actions/interface.go
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,8 @@ type VirtualDiskManager interface {
VirtualDiskDestroyer
VirtualDisks(ctx context.Context) ([]*utils.MvcliDevice, error)
}

// DiskWiper defines an interface to override disk data
type DiskWiper interface {
WipeDisk(ctx context.Context, logicalName string) error
}
25 changes: 25 additions & 0 deletions actions/storage_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package actions
import (
"context"
"fmt"
"log"
"strings"

"github.com/bmc-toolbox/common"
Expand Down Expand Up @@ -78,3 +79,27 @@ func (s *StorageControllerAction) GetControllerUtility(vendorName, modelName str

return nil, errors.Wrap(ErrVirtualDiskManagerUtilNotIdentified, "vendor: "+vendorName+" model: "+modelName)
}

// GetWipeUtility returns the wipe utility based on the disk wipping features
func (s *StorageControllerAction) GetWipeUtility(logicalName string) (DiskWiper, error) {
var trace bool

if s.Logger.GetLevel().String() == "trace" {
trace = true
}
// TODO: use disk wipping features to return the best wipe utility, currently only one available
if trace {
log.Printf("%s | Detecting wipe utility", logicalName)
}

return utils.NewFillZeroCmd(trace), nil
}

func (s *StorageControllerAction) WipeDisk(ctx context.Context, logicalName string) error {
util, err := s.GetWipeUtility(logicalName)
if err != nil {
return err
}

return util.WipeDisk(ctx, logicalName)
}
26 changes: 26 additions & 0 deletions examples/diskwipe/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package main

import (
"context"
"fmt"
"time"

"github.com/metal-toolbox/ironlib/actions"
"github.com/sirupsen/logrus"
)

// This example invokes ironlib and wipes the disk /dev/sdZZZ with a timeout of 1 day

func main() {
logger := logrus.New()
logger.Formatter = new(logrus.JSONFormatter)
logger.SetLevel(logrus.TraceLevel)
sca := actions.NewStorageControllerAction(logger)
ctx, cancel := context.WithTimeout(context.Background(), 86400*time.Second)
defer cancel()
err := sca.WipeDisk(ctx, "/dev/sdZZZ")
if err != nil {
logger.Fatal(err)
}
fmt.Println("Wiped")
}
98 changes: 98 additions & 0 deletions utils/fill_zero.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
package utils

import (
"context"
"io"
"log"
"os"
"time"
)

type FillZero struct {
Quiet bool
}

// Return a new zerowipe executor
func NewFillZeroCmd(trace bool) *FillZero {
z := FillZero{}
if !trace {
z.SetQuiet()
}
return &z
}

func (z *FillZero) WipeDisk(ctx context.Context, logicalName string) error {
log.Println("Starting zero-fill of", logicalName)
// Write open
file, err := os.OpenFile(logicalName, os.O_WRONLY, 0)
if err != nil {
return err
}
defer file.Close()
// Get disk or partition size
partitionSize, err := file.Seek(0, io.SeekEnd)
if err != nil {
return err
}
log.Printf("%s | Size: %dB\n", logicalName, partitionSize)
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return err
}
var bytesSinceLastPrint int64
var totalBytesWritten int64
buffer := make([]byte, 4096)
start := time.Now()
for bytesRemaining := partitionSize; bytesRemaining > 0; {
// Check if the context has been canceled
select {
case <-ctx.Done():
log.Println("Context canceled. Stopping WipeDisk")
return ctx.Err()
default:
l := min(int64(len(buffer)), bytesRemaining)
bytesWritten, writeError := file.Write(buffer[:l])
if writeError != nil {
return writeError
}
totalBytesWritten += int64(bytesWritten)
bytesSinceLastPrint += int64(bytesWritten)
bytesRemaining -= int64(bytesWritten)
// Print progress report every 10 seconds and when done
if bytesRemaining == 0 || time.Since(start) >= 10*time.Second {
printProgress(totalBytesWritten, partitionSize, &start, &bytesSinceLastPrint, logicalName)
start = time.Now()
bytesSinceLastPrint = 0
}
}
}
err = file.Sync()
if err != nil {
return err
}
return nil
}

func printProgress(totalBytesWritten, partitionSize int64, start *time.Time, bytesSinceLastPrint *int64, path string) {
// Calculate progress and ETA
progress := float64(totalBytesWritten) / float64(partitionSize) * 100
elapsed := time.Since(*start).Seconds()
speed := float64(*bytesSinceLastPrint) / elapsed // Speed in bytes per second
remainingSeconds := (float64(partitionSize) - float64(totalBytesWritten)) / speed // Remaining time in seconds
remainingHours := float64(remainingSeconds / 3600)
mbPerSecond := speed / (1024 * 1024)
log.Printf("%s | Progress: %.2f%% | Speed: %.2f MB/s | Estimated time left: %.2f hour(s)\n", path, progress, mbPerSecond, remainingHours)
}

// We are in go 1.19 min not available yet
func min(a, b int64) int64 {
if a < b {
return a
}
return b
}

// SetQuiet lowers the verbosity
func (z *FillZero) SetQuiet() {
z.Quiet = true
}
51 changes: 51 additions & 0 deletions utils/fill_zero_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package utils

import (
"context"
"os"
"strconv"
"testing"
)

func Test_NewFillZeroCmd(t *testing.T) {
// Test if NewFillZeroCmd returns a non-nil pointer
zw := NewFillZeroCmd(false)
if zw == nil {
t.Error("Expected non-nil pointer, got nil")
}
}

func Test_WipeDisk(t *testing.T) {
for _, size := range []int{4095, 4096, 4097, 8192} {
t.Run(strconv.Itoa(size), func(t *testing.T) {
// Create a temporary file for testing
tmpfile, err := os.CreateTemp("", "example")
if err != nil {
t.Fatal(err)
}
defer os.Remove(tmpfile.Name()) // clean up
// Write some content to the temporary file
expectedSize := int64(4096)
if _, err = tmpfile.Write(make([]byte, expectedSize)); err != nil {
t.Fatal(err)
}
// Simulate a context
ctx := context.Background()
// Create a FillZero instance
zw := &FillZero{}
// Test Fill function
err = zw.WipeDisk(ctx, tmpfile.Name())
if err != nil {
t.Errorf("Fill returned an error: %v", err)
}
// Check if the file size remains the same after overwrite
fileInfo, err := os.Stat(tmpfile.Name())
if err != nil {
t.Fatal(err)
}
if size := fileInfo.Size(); size != expectedSize {
t.Errorf("Expected file size to remain %d after overwrite, got %d", expectedSize, size)
}
})
}
}

0 comments on commit af14e38

Please sign in to comment.