/*
Copyright © 2022-2025 Thomas von Dein
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see .
*/
package main
import (
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/JojiiOfficial/shred"
flag "github.com/spf13/pflag"
)
const VERSION string = "0.0.4"
const Usage string = `This is gowipe - destruct files in a non-recoverable way.
Usage: gowipe [-rcvz] ...
Options:
-r --recursive Delete recursively
-c --count Overwrite files times
-m --mode Use for overwriting (or use -E, -S, -M, -Z)
-n --nodelete Do not delete files after overwriting
-N --norename Do not rename the files
-v --verbose Verbose output
-V --version Show program version
-h --help Show usage
Available modes:
zero Overwrite with zeroes (-Z)
math Overwrite with math random bytes (-M)
secure Overwrite with secure random bytes (default) (-S)
encrypt Overwrite with ChaCha2Poly1305 encryption (most secure) (-E)`
type Conf struct {
mode string
count int
recurse bool
nodelete bool
norename bool
verbose bool
files int
dirs int
size int64
}
func main() {
showversion := false
showhelp := false
optzero := false
optsecure := false
optmath := false
optencrypt := false
c := Conf{
verbose: false,
mode: `secure`,
count: 30,
recurse: false,
nodelete: false,
norename: false,
}
flag.BoolVarP(&showversion, "version", "V", showversion, "show version")
flag.BoolVarP(&showhelp, "help", "h", showversion, "show help")
flag.BoolVarP(&c.verbose, "verbose", "v", c.verbose, "verbose")
flag.StringVarP(&c.mode, "mode", "m", c.mode, "overwrite mode")
flag.BoolVarP(&optzero, "zero", "Z", optzero, "zero mode")
flag.BoolVarP(&optsecure, "secure", "S", optsecure, "secure mode")
flag.BoolVarP(&optmath, "math", "M", optmath, "math mode")
flag.BoolVarP(&optencrypt, "encrypt", "E", optmath, "encrypt mode")
flag.BoolVarP(&c.recurse, "recursive", "r", c.recurse, "recursive")
flag.BoolVarP(&c.nodelete, "nodelete", "n", c.nodelete, "don't delete")
flag.BoolVarP(&c.norename, "norename", "N", c.norename, "don't rename")
flag.IntVarP(&c.count, "count", "c", c.count, "overwrite count")
flag.Parse()
if showversion {
fmt.Printf("This is gowipe version %s\n", VERSION)
os.Exit(0)
}
if showhelp {
fmt.Println(Usage)
os.Exit(0)
}
if len(flag.Args()) == 0 {
fmt.Println(Usage)
os.Exit(0)
}
var option shred.WriteOptions
if optzero {
option = shred.WriteZeros
}
if optmath {
option = shred.WriteRand
}
if optsecure {
option = shred.WriteRandSecure
}
if optencrypt {
c.mode = "encrypt"
}
switch c.mode {
case `secure`:
option = shred.WriteRandSecure
case `math`:
option = shred.WriteRand
case `zero`:
option = shred.WriteZeros
case `encrypt`:
optencrypt = true
default:
option = shred.WriteRandSecure
}
shredder := shred.Shredder{}
shredconf := shred.NewShredderConf(&shredder, option, c.count, !c.nodelete)
start := time.Now()
for _, file := range flag.Args() {
Wipe(file, &c, shredconf)
}
if c.verbose {
fmt.Println()
fmt.Printf(" Dirs wiped: %d\n", c.dirs)
fmt.Printf(" Files wiped: %d\n", c.files)
fmt.Printf("Bytes deleted: %d\n", c.size)
fmt.Printf(" Time elapsed: %s\n", time.Since(start))
fmt.Printf(" Overwritten: %d times\n", c.count)
fmt.Printf(" Wipe mode: %s\n", c.mode)
fmt.Printf(" Recurse dirs: %t\n", c.recurse)
}
}
func Wipe(file string, c *Conf, wiper *shred.ShredderConf) {
if info, err := os.Stat(file); err == nil {
if info.IsDir() {
if !c.recurse {
fmt.Printf("-r not set, ignoring directory %s\n", file)
return
}
files, err := os.ReadDir(file)
if err != nil {
log.Fatal(err)
}
for _, entry := range files {
Wipe(filepath.Join(file, entry.Name()), c, wiper)
}
// delete dir
if !c.nodelete {
err = os.Remove(Rename(file, c))
if err != nil {
log.Fatal(err)
}
c.dirs++
}
} else {
if c.mode == "encrypt" {
if err := Encrypt(c, file); err != nil {
log.Fatal(err)
}
// delete encrypted file
if !c.nodelete {
err = os.Remove(Rename(file, c))
if err != nil {
log.Fatal(err)
}
}
} else {
if err := wiper.ShredFile(Rename(file, c)); err != nil {
log.Fatal(err)
}
}
c.size += info.Size()
}
if c.verbose {
fmt.Printf("Wiped %s (%d bytes)\n", file, info.Size())
}
c.files++
} else {
if os.IsNotExist(err) {
fmt.Printf("No such file or directory: %s\n", file)
} else {
fmt.Println(err)
}
os.Exit(1)
}
}
func Rename(file string, c *Conf) string {
var newname string
dir := filepath.Dir(file)
base := filepath.Base(file)
length := len(base)
for i := 0; i < c.count; i++ {
for {
switch c.mode {
case `secure`, `encrypt`:
new, err := GenerateSecureRandomString(length)
if err != nil {
log.Fatal(err)
}
newname = new
case `math`:
newname = GenerateMathRandomString(length)
case `zero`:
newname = strings.Repeat("0", length)
}
if newname != base {
break
}
}
err := os.Rename(filepath.Join(dir, base), filepath.Join(dir, newname))
if err != nil {
log.Fatal(err)
}
base = newname
}
return filepath.Join(dir, newname)
}