Implement blackbox in Golang (#250)

* Initial release
This commit is contained in:
Tom Limoncelli
2020-07-24 14:21:33 -04:00
committed by GitHub
parent e049c02655
commit 1c77c87555
86 changed files with 6074 additions and 22 deletions

130
pkg/bbutil/filestats.go Normal file
View 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
View 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
View 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
View 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
}

View 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)
}
}