1
0
mirror of https://github.com/byReqz/go-etcher.git synced 2025-07-03 03:20:49 +00:00

11 Commits

Author SHA1 Message Date
0625418d9d restructure packages and start picking apart functions 2023-10-08 11:57:13 +02:00
d0f0957932 make linter happy 2022-12-29 23:01:03 +01:00
17bb76627f add lint workflow 2022-12-29 19:24:23 +01:00
427c4fd990 fmt 2022-03-26 11:12:58 +01:00
947660b773 add output verification 2022-02-08 12:28:49 +01:00
15b7e2f781 add headline to device overview 2022-02-07 13:03:38 +01:00
30afa9ae21 make force flag actually do something 2022-02-07 12:44:07 +01:00
e9ac06c8cf remove clutter 2022-02-07 12:36:08 +01:00
95f4d1d682 fix blockdevices being unwritable 2022-02-07 12:21:51 +01:00
4dfdda9e78 fix issue with size warning 2022-01-31 22:34:30 +01:00
36abb0e312 show possible target devices 2022-01-26 23:40:45 +01:00
6 changed files with 373 additions and 234 deletions

7
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,7 @@
name: ci
on: [push]
jobs:
test:
uses: byReqz/workflows/.github/workflows/golint_with_codeql.yml@main

View File

@ -12,6 +12,7 @@ arguments:
-d, --device string target device -d, --device string target device
-f, --force override safety features -f, --force override safety features
-i, --input string input file -i, --input string input file
-n, --no-hash disable hash verification
``` ```
If no image or device is specified, etcher will enter interactive mode and prompt for the missing parameters. If no image or device is specified, etcher will enter interactive mode and prompt for the missing parameters.

283
cmd/cetcher/main.go Normal file
View File

@ -0,0 +1,283 @@
// package cetcher is the cli app for the go-etcher project
package cetcher
import (
"crypto/sha256"
"fmt"
"io"
"log"
"os"
"strings"
"time"
ac "github.com/JoaoDanielRufino/go-input-autocomplete"
"github.com/briandowns/spinner"
"github.com/byReqz/go-etcher/etcher"
"github.com/fatih/color"
"github.com/schollz/progressbar/v3"
flag "github.com/spf13/pflag"
)
var device string
var input string
var force bool
var disable_hash bool
func init() {
flag.StringVarP(&device, "device", "d", "", "target device")
flag.StringVarP(&input, "input", "i", "", "input file")
flag.BoolVarP(&force, "force", "f", false, "override safety features")
flag.BoolVarP(&disable_hash, "no-hash", "n", false, "disable hash verification")
flag.Parse()
}
func GetPath() string {
path, err := ac.Read("[ " + color.YellowString("i") + " ] Please input your image file: ")
if err != nil {
log.Fatal(err)
}
path = strings.TrimSpace(path)
if strings.HasPrefix(path, "~/") {
homedir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
return homedir + path[1:]
}
if path == "" {
fmt.Println("[", color.RedString("!"), "] No image given, retrying.")
path = GetPath()
}
return path
}
func GetDest() string {
PrintAvail()
dest, err := ac.Read("[ " + color.YellowString("i") + " ] Please input destination: ")
if err != nil {
log.Fatal(err)
}
dest = strings.TrimSpace(dest)
if strings.HasPrefix(dest, "~/") {
homedir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
return homedir + dest[1:]
}
if dest == "" {
fmt.Println("[", color.RedString("!"), "] No destination given, retrying.")
dest = GetDest()
}
return dest
}
func WriteImage(image *os.File, target *os.File, size int64) (int64, error) {
bar := progressbar.NewOptions(int(size),
progressbar.OptionSetWriter(os.Stderr),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(25),
progressbar.OptionSetDescription("Writing image file..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "=",
SaucerHead: ">",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}))
writer := io.MultiWriter(target, bar)
written, err := io.Copy(writer, image)
if err != nil {
return 0, err
}
return written, err
}
func PrintAvail() {
devices, err := etcher.GetBlockdevices()
if err != nil {
log.Fatal(err)
}
fmt.Println("Available devices:")
for _, dev := range devices {
fmt.Print(" * ", dev.Path)
if dev.Size > 0 {
fmt.Print(" [", (dev.Size / 1024 / 1024 / 1024), "GB]\n")
} else {
fmt.Println("")
}
}
}
func Main() {
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond)
if input == "" {
if len(flag.Args()) == 0 {
input = GetPath()
} else if len(flag.Args()) > 0 {
input = flag.Args()[0]
}
}
if device == "" {
if len(flag.Args()) == 0 {
device = GetDest()
} else if len(flag.Args()) > 0 {
if input == flag.Args()[0] && len(flag.Args()) > 1 {
device = flag.Args()[1]
} else if input != flag.Args()[0] && len(flag.Args()) > 0 {
device = flag.Args()[0]
}
}
}
s.Prefix = "[ "
s.Suffix = " ] Getting file details"
s.Start()
statinput, err := os.Stat(input)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
}
statdevice, err := os.Stat(device)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
}
image, err := os.Open(input)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
}
var inputsize int64
var inputisblock bool
if statinput.Size() != 0 {
inputsize = statinput.Size()
inputisblock = false
} else {
inputsize, _ = image.Seek(0, io.SeekEnd)
inputisblock = true
_, _ = image.Seek(0, 0)
}
target, err := os.OpenFile(device, os.O_RDWR, 0660)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
}
var targetsize int64
var targetisblock bool
if statdevice.Size() != 0 {
targetsize = statdevice.Size()
targetisblock = false
} else {
targetsize, err = target.Seek(0, io.SeekEnd)
targetisblock = true
_, _ = target.Seek(0, 0)
}
prehash := sha256.New()
if !(force || disable_hash) {
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
}
_, err = io.Copy(prehash, image)
_, _ = image.Seek(0, 0)
}
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
} else {
s.Stop()
fmt.Println("\r[", color.GreenString("✓"), "] Getting file details ")
}
inputmb := fmt.Sprint("[", inputsize/1024/1024, "MB]")
devicemb := fmt.Sprint("[", targetsize/1024/1024, "MB]")
var inputblock string
var targetblock string
if inputisblock {
inputblock = "[Blockdevice]"
} else {
inputblock = "[File]"
}
if targetisblock {
targetblock = "[Blockdevice]"
} else {
targetblock = "[File]"
}
fmt.Println("[", color.BlueString("i"), "] Input device/file: "+input, inputmb, inputblock)
fmt.Println("[", color.BlueString("i"), "] Output device/file: "+device, devicemb, targetblock)
if !force {
if inputsize > targetsize {
fmt.Println("[", color.RedString("w"), "]", color.RedString(" Warning:"), "Input file seems to be bigger than the destination!")
}
fmt.Print(color.HiWhiteString("Do you want to continue? [y/N]: "))
var yesno string
_, _ = fmt.Scanln(&yesno)
yesno = strings.TrimSpace(yesno)
if !(yesno == "y" || yesno == "Y") {
log.Fatal("aborted")
}
}
written, err := WriteImage(image, target, inputsize)
_, _ = target.Seek(0, 0)
if err != nil {
fmt.Println("\r[", color.RedString("✘"), "] Writing image,", written, "bytes written ")
log.Fatal(err)
} else {
fmt.Println("\r[", color.GreenString("✓"), "] Writing image,", written, "bytes written ")
}
s.Prefix = "[ "
s.Suffix = " ] Syncing"
s.Start()
err = image.Sync()
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Syncing ")
log.Fatal(err)
}
err = target.Sync()
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Syncing ")
log.Fatal(err)
} else {
s.Stop()
fmt.Println("\r[", color.GreenString("✓"), "] Syncing ")
}
if !(force || disable_hash) {
s.Prefix = "[ "
s.Suffix = " ] Verifying"
s.Start()
posthash := sha256.New()
_, err = io.CopyN(posthash, target, inputsize)
presum := fmt.Sprintf("%x", prehash.Sum(nil))
postsum := fmt.Sprintf("%x", posthash.Sum(nil))
if err != nil || presum != postsum {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Verifying ")
log.Fatal(err)
} else {
s.Stop()
fmt.Println("\r[", color.GreenString("✓"), "] Verifying ")
}
}
err = image.Close()
if err != nil {
log.Fatal(err)
}
err = target.Close()
if err != nil {
log.Fatal(err)
}
}

11
etcher/etcher.go Normal file
View File

@ -0,0 +1,11 @@
package etcher
type Blockdevice struct {
Path string
Size int
Type string
//ID string
}
// CheckHash checks if the given file has been properly applied to the given blockdevice.
//func CheckHash(f os.File, b Blockdevice) error {}

68
etcher/etcher_linux.go Normal file
View File

@ -0,0 +1,68 @@
package etcher
import (
"fmt"
"io"
"os"
"strconv"
"strings"
)
// getBlockdeviceSize gets the size of a blockdevice from the kernel filesystem.
func getBlockdeviceSize(name string) (size int, err error) {
sizefile, err := os.Open("/sys/block/" + name + "/size")
if err != nil {
return
}
sizeread, err := io.ReadAll(sizefile)
if err != nil {
return
}
_ = sizefile.Close()
sizestring := strings.TrimSuffix(string(sizeread), "\n")
size, err = strconv.Atoi(sizestring)
if err != nil {
return
}
size = size * 512 // multiply blockcount by blocksize
return size, err
}
// GetBlockdevices gets the available blockdevices for reading from/writing to.
func GetBlockdevices() (devices []Blockdevice, err error) {
block, err := os.ReadDir("/sys/block")
if err != nil {
return
} else if len(block) == 0 {
return devices, fmt.Errorf("no blockdevices found")
}
for _, device := range block {
var dev Blockdevice
dev.Path = "/dev/" + device.Name()
if strings.Contains(device.Name(), "sd") {
dev.Type = "sata"
} else if strings.Contains(device.Name(), "hd") {
dev.Type = "ide"
} else if strings.Contains(device.Name(), "nvme") {
dev.Type = "nvme"
} else if strings.Contains(device.Name(), "dm") {
dev.Type = "dmcrypt"
} else if strings.Contains(device.Name(), "md") {
dev.Type = "raid"
}
dev.Size, err = getBlockdeviceSize(device.Name())
if err != nil {
return
}
devices = append(devices, dev)
}
return devices, nil
}

237
main.go
View File

@ -1,238 +1,7 @@
package main package main
import (
"fmt"
"os"
"io"
"time"
"log"
"strings"
"github.com/schollz/progressbar/v3"
"github.com/fatih/color"
"github.com/briandowns/spinner"
flag "github.com/spf13/pflag"
ac "github.com/JoaoDanielRufino/go-input-autocomplete"
)
var device string import "github.com/byReqz/go-etcher/cmd/cetcher"
var input string
var force bool
func init() {
flag.StringVarP(&device, "device", "d", "", "target device")
flag.StringVarP(&input, "input", "i", "", "input file")
flag.BoolVarP(&force, "force", "f", false, "override safety features")
flag.Parse()
}
func GetPath() string {
path, err := ac.Read("[ " + color.YellowString("i") + " ] Please input your image file: ")
if err != nil {
log.Fatal(err)
}
path = strings.TrimSpace(path)
if strings.HasPrefix(path, "~/") {
homedir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
return homedir + path[1:]
}
if path == "" {
fmt.Println("[", color.RedString("!"), "] No image given, retrying.")
path = GetPath()
}
return path
}
func GetDest() string {
dest, err := ac.Read("[ " + color.YellowString("i") + " ] Please input destination: ")
if err != nil {
log.Fatal(err)
}
dest = strings.TrimSpace(dest)
if strings.HasPrefix(dest, "~/") {
homedir, err := os.UserHomeDir()
if err != nil {
log.Fatal(err)
}
return homedir + dest[1:]
}
if dest == "" {
fmt.Println("[", color.RedString("!"), "] No destination given, retrying.")
dest = GetDest()
}
return dest
}
func WriteImage(image *os.File, target *os.File, size int64) (int64, error) {
bar := progressbar.NewOptions(int(size),
progressbar.OptionSetWriter(os.Stderr),
progressbar.OptionShowBytes(true),
progressbar.OptionSetWidth(25),
progressbar.OptionSetDescription("Writing image file..."),
progressbar.OptionSetTheme(progressbar.Theme{
Saucer: "=",
SaucerHead: ">",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}))
writer := io.MultiWriter(target, bar)
written, err := io.Copy(writer, image)
if err != nil {
return 0, err
}
return written, err
}
func Sync(image *os.File, target *os.File) error {
err := image.Sync()
if err != nil {
return err
}
err = target.Sync()
if err != nil {
return err
}
err = image.Close()
if err != nil {
return err
}
err = target.Close()
if err != nil {
return err
}
return nil
}
func main() { func main() {
s := spinner.New(spinner.CharSets[14], 100*time.Millisecond) cetcher.Main()
}
if input == "" {
if len(flag.Args()) == 0 {
input = GetPath()
} else if len(flag.Args()) > 0 {
input = flag.Args()[0]
}
}
if device == "" {
if len(flag.Args()) == 0 {
device = GetDest()
} else if len(flag.Args()) > 0 {
if input == flag.Args()[0] && len(flag.Args()) > 1 {
device = flag.Args()[1]
} else if input != flag.Args()[0] && len(flag.Args()) > 0 {
device = flag.Args()[0]
}
}
}
s.Prefix = "[ "
s.Suffix = " ] Getting file details"
s.Start()
statinput, err := os.Stat(input)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
}
statdevice, err := os.Stat(device)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Getting file details ")
log.Fatal(err)
} else {
s.Stop()
fmt.Println("\r[", color.GreenString("✓"), "] Getting file details ")
}
s.Prefix = "[ "
s.Suffix = " ] Opening files"
s.Start()
image, err := os.Open(input)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Opening files ")
log.Fatal(err)
}
var inputsize int64
var inputisblock bool
if statinput.Size() != 0 {
inputsize = statinput.Size()
inputisblock = false
} else {
inputsize, err = image.Seek(0, io.SeekEnd)
inputisblock = true
}
target, err := os.OpenFile(device, os.O_RDWR, 0660)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Opening files ")
log.Fatal(err)
}
var targetsize int64
var targetisblock bool
if statdevice.Size() != 0 {
targetsize = statdevice.Size()
targetisblock = false
} else {
targetsize, err = target.Seek(0, io.SeekEnd)
targetisblock = true
}
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Opening files ")
log.Fatal(err)
} else {
s.Stop()
fmt.Println("\r[", color.GreenString("✓"), "] Opening files ")
}
inputmb := fmt.Sprint("[", inputsize / 1024 / 1024, "MB]")
devicemb := fmt.Sprint("[", targetsize / 1024 / 1024, "MB]")
var inputblock string
var targetblock string
if inputisblock == true {
inputblock = "[Blockdevice]"
} else {
inputblock = "[File]"
}
if targetisblock == true {
targetblock = "[Blockdevice]"
} else {
targetblock = "[File]"
}
fmt.Println("[", color.BlueString("i"), "] Input device/file: " + input, inputmb, inputblock)
fmt.Println("[", color.BlueString("i"), "] Output device/file: " + device, devicemb, targetblock)
if statinput.Size() > statdevice.Size() {
fmt.Println("[", color.RedString("w"), "]", color.RedString(" Warning:"), "Input file seems to be bigger than the destination!")
}
fmt.Print(color.HiWhiteString("Do you want to continue? [y/N]: "))
var yesno string
_, _ = fmt.Scanln(&yesno)
yesno = strings.TrimSpace(yesno)
if ! (yesno == "y" || yesno == "Y") {
log.Fatal("aborted")
}
written, err := WriteImage(image, target, inputsize)
if err != nil {
fmt.Println("\r[", color.RedString("✘"), "] Writing image,", written, "bytes written ")
log.Fatal(err)
} else {
fmt.Println("\r[", color.GreenString("✓"), "] Writing image,", written, "bytes written ")
}
s.Prefix = "[ "
s.Suffix = " ] Syncing"
s.Start()
err = Sync(image, target)
if err != nil {
s.Stop()
fmt.Println("\r[", color.RedString("✘"), "] Syncing ")
log.Fatal(err)
} else {
s.Stop()
fmt.Println("\r[", color.GreenString("✓"), "] Syncing ")
}
}