130
pkg/bbutil/filestats.go
Normal file
130
pkg/bbutil/filestats.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package bbutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DirExists returns true if directory exists.
|
||||
func DirExists(path string) (bool, error) {
|
||||
stat, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return stat.IsDir(), nil
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false, nil
|
||||
}
|
||||
return true, err
|
||||
}
|
||||
|
||||
// FileExistsOrProblem returns true if the file exists or if we can't determine its existence.
|
||||
func FileExistsOrProblem(path string) bool {
|
||||
_, err := os.Stat(path)
|
||||
if err == nil {
|
||||
return true
|
||||
}
|
||||
if os.IsNotExist(err) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Touch updates the timestamp of a file.
|
||||
func Touch(name string) error {
|
||||
var err error
|
||||
_, err = os.Stat(name)
|
||||
if os.IsNotExist(err) {
|
||||
file, err := os.Create(name)
|
||||
if err != nil {
|
||||
return fmt.Errorf("TouchFile failed: %w", err)
|
||||
}
|
||||
file.Close()
|
||||
}
|
||||
|
||||
currentTime := time.Now().Local()
|
||||
return os.Chtimes(name, currentTime, currentTime)
|
||||
}
|
||||
|
||||
// ReadFileLines is like ioutil.ReadFile() but returns an []string.
|
||||
func ReadFileLines(filename string) ([]string, error) {
|
||||
b, err := ioutil.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
s := string(b)
|
||||
s = strings.TrimSuffix(s, "\n")
|
||||
if s == "" {
|
||||
return []string{}, nil
|
||||
}
|
||||
l := strings.Split(s, "\n")
|
||||
return l, nil
|
||||
}
|
||||
|
||||
// AddLinesToSortedFile adds a line to a sorted file.
|
||||
func AddLinesToSortedFile(filename string, newlines ...string) error {
|
||||
lines, err := ReadFileLines(filename)
|
||||
//fmt.Printf("DEBUG: read=%q\n", lines)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AddLinesToSortedFile can't read %q: %w", filename, err)
|
||||
}
|
||||
if !sort.StringsAreSorted(lines) {
|
||||
return fmt.Errorf("AddLinesToSortedFile: file wasn't sorted: %v", filename)
|
||||
}
|
||||
lines = append(lines, newlines...)
|
||||
sort.Strings(lines)
|
||||
contents := strings.Join(lines, "\n") + "\n"
|
||||
//fmt.Printf("DEBUG: write=%q\n", contents)
|
||||
err = ioutil.WriteFile(filename, []byte(contents), 0o660)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AddLinesToSortedFile can't write %q: %w", filename, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddLinesToFile adds lines to the end of a file.
|
||||
func AddLinesToFile(filename string, newlines ...string) error {
|
||||
lines, err := ReadFileLines(filename)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AddLinesToFile can't read %q: %w", filename, err)
|
||||
}
|
||||
lines = append(lines, newlines...)
|
||||
contents := strings.Join(lines, "\n") + "\n"
|
||||
err = ioutil.WriteFile(filename, []byte(contents), 0o660)
|
||||
if err != nil {
|
||||
return fmt.Errorf("AddLinesToFile can't write %q: %w", filename, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FindDirInParent looks for target in CWD, or .., or ../.., etc.
|
||||
func FindDirInParent(target string) (string, error) {
|
||||
// Prevent an infinite loop by only doing "cd .." this many times
|
||||
maxDirLevels := 30
|
||||
relpath := "."
|
||||
for i := 0; i < maxDirLevels; i++ {
|
||||
// Does relpath contain our target?
|
||||
t := filepath.Join(relpath, target)
|
||||
//logDebug.Printf("Trying %q\n", t)
|
||||
_, err := os.Stat(t)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if !os.IsNotExist(err) {
|
||||
return "", fmt.Errorf("stat failed FindDirInParent (%q): %w", t, err)
|
||||
}
|
||||
// Ok, it really wasn't found.
|
||||
|
||||
// If we are at the root, stop.
|
||||
if abs, err := filepath.Abs(relpath); err == nil && abs == "/" {
|
||||
break
|
||||
}
|
||||
// Try one directory up
|
||||
relpath = filepath.Join("..", relpath)
|
||||
}
|
||||
return "", fmt.Errorf("Not found")
|
||||
}
|
||||
21
pkg/bbutil/rbio_test.go
Normal file
21
pkg/bbutil/rbio_test.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package bbutil
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestRunBashInputOutput(t *testing.T) {
|
||||
|
||||
in := "This is a test of the RBIO system.\n"
|
||||
bin := []byte(in)
|
||||
|
||||
out, err := RunBashInputOutput(bin, "cat")
|
||||
sout := string(out)
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if in != sout {
|
||||
t.Errorf("not equal %q %q", in, out)
|
||||
}
|
||||
}
|
||||
77
pkg/bbutil/runbash.go
Normal file
77
pkg/bbutil/runbash.go
Normal file
@@ -0,0 +1,77 @@
|
||||
package bbutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
// RunBash runs a Bash command.
|
||||
func RunBash(command string, args ...string) error {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Start()
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
err = cmd.Wait()
|
||||
if err != nil {
|
||||
return fmt.Errorf("RunBash cmd=%q err=%w", command, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunBashOutput runs a Bash command, captures output.
|
||||
func RunBashOutput(command string, args ...string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("RunBashOutput err=%w", err)
|
||||
}
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// RunBashOutputSilent runs a Bash command, captures output, discards stderr.
|
||||
func RunBashOutputSilent(command string, args ...string) (string, error) {
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = os.Stdin
|
||||
// Leave cmd.Stderr unmodified and stderr is discarded.
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("RunBashOutputSilent err=%w", err)
|
||||
}
|
||||
return string(out), err
|
||||
}
|
||||
|
||||
// RunBashInput runs a Bash command, sends input on stdin.
|
||||
func RunBashInput(input string, command string, args ...string) error {
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = bytes.NewBuffer([]byte(input))
|
||||
cmd.Stdout = os.Stdout
|
||||
cmd.Stderr = os.Stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("RunBashInput err=%w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunBashInputOutput runs a Bash command, sends input on stdin.
|
||||
func RunBashInputOutput(input []byte, command string, args ...string) ([]byte, error) {
|
||||
|
||||
cmd := exec.Command(command, args...)
|
||||
cmd.Stdin = bytes.NewBuffer(input)
|
||||
cmd.Stderr = os.Stderr
|
||||
out, err := cmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("RunBashInputOutput err=%w", err)
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
109
pkg/bbutil/shred.go
Normal file
109
pkg/bbutil/shred.go
Normal file
@@ -0,0 +1,109 @@
|
||||
package bbutil
|
||||
|
||||
// Pick an appropriate secure erase command for this operating system
|
||||
// or just delete the file with os.Remove().
|
||||
|
||||
// Code rewritten based https://codereview.stackexchange.com/questions/245072
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
)
|
||||
|
||||
var shredCmds = []struct {
|
||||
name, opts string
|
||||
}{
|
||||
{"sdelete", "-a"},
|
||||
{"shred", "-u"},
|
||||
{"srm", "-f"},
|
||||
{"rm", "-Pf"},
|
||||
}
|
||||
|
||||
func shredTemp(path, opts string) error {
|
||||
file, err := ioutil.TempFile("", "shredTemp.")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
filename := file.Name()
|
||||
defer os.Remove(filename)
|
||||
defer file.Close()
|
||||
|
||||
err = file.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = RunBash(path, opts, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
var shredPath, shredOpts = func() (string, string) {
|
||||
for _, cmd := range shredCmds {
|
||||
path, err := exec.LookPath(cmd.name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
err = shredTemp(path, cmd.opts)
|
||||
if err == nil {
|
||||
return path, cmd.opts
|
||||
}
|
||||
}
|
||||
return "", ""
|
||||
}()
|
||||
|
||||
// ShredInfo reveals the shred command and flags (for "blackbox info")
|
||||
func ShredInfo() string {
|
||||
return shredPath + " " + shredOpts
|
||||
}
|
||||
|
||||
// shredFile shreds one file.
|
||||
func shredFile(filename string) error {
|
||||
fi, err := os.Stat(filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !fi.Mode().IsRegular() {
|
||||
err := fmt.Errorf("filename is not mode regular")
|
||||
return err
|
||||
}
|
||||
|
||||
if shredPath == "" {
|
||||
// No secure erase command found. Default to a normal file delete.
|
||||
// TODO(tlim): Print a warning? Have a flag that causes this to be an error?
|
||||
return os.Remove(filename)
|
||||
}
|
||||
|
||||
err = RunBash(shredPath, shredOpts, filename)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ShredFiles securely erases a list of files.
|
||||
func ShredFiles(names []string) error {
|
||||
|
||||
// TODO(tlim) DO the shredding in parallel like in v1.
|
||||
|
||||
var eerr error
|
||||
for _, n := range names {
|
||||
_, err := os.Stat(n)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
fmt.Printf("======= already gone: %q\n", n)
|
||||
continue
|
||||
}
|
||||
}
|
||||
fmt.Printf("========== SHREDDING: %q\n", n)
|
||||
e := shredFile(n)
|
||||
if e != nil {
|
||||
eerr = e
|
||||
fmt.Printf("ERROR: %v\n", e)
|
||||
}
|
||||
}
|
||||
return eerr
|
||||
}
|
||||
66
pkg/bbutil/sortedfile_test.go
Normal file
66
pkg/bbutil/sortedfile_test.go
Normal file
@@ -0,0 +1,66 @@
|
||||
package bbutil
|
||||
|
||||
import (
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestAddLinesToSortedFile(t *testing.T) {
|
||||
|
||||
var tests = []struct {
|
||||
start string
|
||||
add []string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
"",
|
||||
[]string{"one"},
|
||||
"one\n",
|
||||
},
|
||||
{
|
||||
"begin\ntwo\n",
|
||||
[]string{"at top"},
|
||||
"at top\nbegin\ntwo\n",
|
||||
},
|
||||
{
|
||||
"begin\ntwo\n",
|
||||
[]string{"zbottom"},
|
||||
"begin\ntwo\nzbottom\n",
|
||||
},
|
||||
{
|
||||
"begin\ntwo\n",
|
||||
[]string{"middle"},
|
||||
"begin\nmiddle\ntwo\n",
|
||||
},
|
||||
}
|
||||
|
||||
for i, test := range tests {
|
||||
content := []byte(test.start)
|
||||
tmpfile, err := ioutil.TempFile("", "example")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tmpfilename := tmpfile.Name()
|
||||
defer os.Remove(tmpfilename)
|
||||
|
||||
if _, err := tmpfile.Write(content); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
AddLinesToSortedFile(tmpfilename, test.add...)
|
||||
expected := test.expected
|
||||
|
||||
got, err := ioutil.ReadFile(tmpfilename)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if expected != string(got) {
|
||||
t.Errorf("test %v: contents wrong:\nexpected: %q\n got: %q", i, expected, got)
|
||||
}
|
||||
os.Remove(tmpfilename)
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user