Skip to content

Commit

Permalink
Add info and read sub commands to example program
Browse files Browse the repository at this point in the history
To make room for a new convert sub command, using parallel convert. This
is also a better example, having only the relevant argument and separate
file for every example command.

Example usage:

    % ./go-qcow2reader-example
    Usage: ./go-qcow2reader-example COMMAND [OPTIONS...]

    Available commands:
      info		show image information
      read		read image data and print to stdout

    % ./go-qcow2reader-example info /tmp/images/test.zlib.qcow2
    {
        "type": "qcow2",
        "size": 3758096384,
    ...

    % time ./go-qcow2reader-example read /tmp/images/test.zlib.qcow2 >/dev/null
    ./go-qcow2reader-example read /tmp/images/test.zlib.qcow2 > /dev/null  10.05s user 0.35s system 101% cpu 10.279 total

Signed-off-by: Nir Soffer <[email protected]>
  • Loading branch information
nirs committed Oct 25, 2024
1 parent 64164ed commit d597d74
Show file tree
Hide file tree
Showing 4 changed files with 174 additions and 75 deletions.
67 changes: 67 additions & 0 deletions cmd/go-qcow2reader-example/info.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package main

import (
"encoding/json"
"errors"
"flag"
"fmt"
"os"

"github.com/lima-vm/go-qcow2reader"
"github.com/lima-vm/go-qcow2reader/image"
)

func cmdInfo(args []string) error {
var (
// Required
filename string

// Options
debug bool
)

fs := flag.NewFlagSet("info", flag.ExitOnError)
fs.Usage = func() {
fmt.Fprintf(fs.Output(), "Usage: %s info [OPTIONS...] FILE\n", os.Args[0])
fs.PrintDefaults()
}
fs.BoolVar(&debug, "debug", false, "enable printing debug messages")
if err := fs.Parse(args); err != nil {
return err
}

switch len(fs.Args()) {
case 0:
return errors.New("no file was specified")
case 1:
filename = fs.Arg(0)
default:
return errors.New("too many files specified")
}

f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()

img, err := qcow2reader.Open(f)
if err != nil {
return err
}
defer img.Close()

imgInfo := image.NewImageInfo(img)
j, err := json.MarshalIndent(imgInfo, "", " ")
if err != nil {
return err
}
if _, err = fmt.Println(string(j)); err != nil {
return err
}
if err = img.Readable(); err != nil {
logWarn(err.Error())
}

return nil
}
101 changes: 30 additions & 71 deletions cmd/go-qcow2reader-example/main.go
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
package main

import (
"encoding/json"
"errors"
"flag"
"fmt"
"io"
"os"

"github.com/klauspost/compress/zstd"
"github.com/lima-vm/go-qcow2reader"
"github.com/lima-vm/go-qcow2reader/image"
"github.com/lima-vm/go-qcow2reader/image/qcow2"
"github.com/lima-vm/go-qcow2reader/log"
)

func warn(s string) {
func logWarn(s string) {
fmt.Fprintln(os.Stderr, "WARNING: "+s)
}

func debugPrint(s string) {
func logDebug(s string) {
fmt.Fprintln(os.Stderr, "DEBUG: "+s)
}

Expand All @@ -40,82 +35,46 @@ func newZstdDecompressor(r io.Reader) (io.ReadCloser, error) {
return &zstdDecompressor{dec}, nil
}

func usage() {
usage := `Usage: %s COMMAND [OPTIONS...]
Available commands:
info show image information
read read image data and print to stdout
convert convert image to raw format
`
fmt.Fprintf(os.Stderr, usage, os.Args[0])
os.Exit(1)
}

func main() {
log.SetWarnFunc(warn)
log.SetWarnFunc(logWarn)

// zlib (deflate) decompressor is registered by default, but zstd is not.
qcow2.SetDecompressor(qcow2.CompressionTypeZstd, newZstdDecompressor)

if err := xmain(); err != nil {
fmt.Fprintln(os.Stderr, "ERROR: "+err.Error())
os.Exit(1)
}
}
var err error

func xmain() error {
flag.Usage = func() {
fmt.Fprintf(flag.CommandLine.Output(), "Usage: %s [OPTIONS...] FILE\n", os.Args[0])
flag.PrintDefaults()
var cmd string
if len(os.Args) > 1 {
cmd = os.Args[1]
}
var (
debug bool
info bool
bufferSize int
offset int64
length int64
)
flag.BoolVar(&debug, "debug", false, "enable printing debug messages")
flag.BoolVar(&info, "info", false, "print the image info and exit")
flag.IntVar(&bufferSize, "buffer", 65536, "buffer size")
flag.Int64Var(&offset, "offset", 0, "offset to read")
flag.Int64Var(&length, "length", -1, "length to read")
flag.Parse()
if debug {
log.SetDebugFunc(debugPrint)
var args []string
if len(os.Args) > 2 {
args = os.Args[2:]
}

args := flag.Args()
switch len(args) {
case 0:
return errors.New("no file was specified")
case 1:
// NOP
switch cmd {
case "info":
err = cmdInfo(args)
case "read":
err = cmdRead(args)
default:
return errors.New("too many files were specified")
}
fName := args[0]

f, err := os.Open(fName)
if err != nil {
return err
usage()
}
defer f.Close()

img, err := qcow2reader.Open(f)
if err != nil {
return err
}

if info {
imgInfo := image.NewImageInfo(img)
j, err := json.MarshalIndent(imgInfo, "", " ")
if err != nil {
return err
}
if _, err = fmt.Println(string(j)); err != nil {
return err
}
if err = img.Readable(); err != nil {
warn(err.Error())
}
return nil
}

if length < 0 {
length = img.Size()
fmt.Fprintln(os.Stderr, "ERROR: "+err.Error())
os.Exit(1)
}
buf := make([]byte, bufferSize)
sr := io.NewSectionReader(img, offset, length)
_, err = io.CopyBuffer(os.Stdout, sr, buf)
return err
}
73 changes: 73 additions & 0 deletions cmd/go-qcow2reader-example/read.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"errors"
"flag"
"fmt"
"io"
"os"

"github.com/lima-vm/go-qcow2reader"
"github.com/lima-vm/go-qcow2reader/log"
)

func cmdRead(args []string) error {
var (
// Required
filename string

// Options
debug bool
bufferSize int
offset int64
length int64
)

fs := flag.NewFlagSet("read", flag.ExitOnError)
fs.Usage = func() {
fmt.Fprintf(fs.Output(), "Usage: %s read [OPTIONS...] FILE\n", os.Args[0])
flag.PrintDefaults()
}
fs.BoolVar(&debug, "debug", false, "enable printing debug messages")
fs.IntVar(&bufferSize, "buffer-size", 65536, "buffer size")
fs.Int64Var(&offset, "offset", 0, "offset to read")
fs.Int64Var(&length, "length", -1, "length to read")
if err := fs.Parse(args); err != nil {
return err
}

if debug {
log.SetDebugFunc(logDebug)
}

switch len(fs.Args()) {
case 0:
return errors.New("no file was specified")
case 1:
filename = fs.Arg(0)
default:
return errors.New("too many files were specified")
}

f, err := os.Open(filename)
if err != nil {
return err
}
defer f.Close()

img, err := qcow2reader.Open(f)
if err != nil {
return err
}
defer img.Close()

if length < 0 {
length = img.Size()
}

buf := make([]byte, bufferSize)
sr := io.NewSectionReader(img, offset, length)
_, err = io.CopyBuffer(os.Stdout, sr, buf)

return err
}
8 changes: 4 additions & 4 deletions hack/compare-with-qemu-img.sh
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ name_raw_b="${name_qcow2}.raw_b"

echo "Input file: ${name_qcow2}"
set -x
go-qcow2reader-example -info "${name_qcow2}"
go-qcow2reader-example info "${name_qcow2}"
set +x

echo "===== Phase 1: full read ====="
Expand All @@ -29,9 +29,9 @@ if [ ! -e "${name_raw_a}".sha256 ]; then
fi

rm -f "${name_raw_b}" "${name_raw_b}.sha256"
echo "Converting ${name_qcow2} to ${name_raw_b} with go-qcow2reader"
echo "Converting ${name_qcow2} to ${name_raw_b} with go-qcow2reader read"
set -x
go-qcow2reader-example "${name_qcow2}" >"${name_raw_b}"
go-qcow2reader-example read "${name_qcow2}" >"${name_raw_b}"
sha256sum "${name_raw_b}" | tee "${name_raw_b}.sha256"
set +x

Expand All @@ -54,7 +54,7 @@ for offset in 1 22 333 4444 55555 666666 7777777 88888888; do
set +o pipefail
expected="$(tail -c "+$((${offset} + 1))" "${name_raw_a}" | head -c "${length}" | sha256sum - | cut -d " " -f 1)"
set -o pipefail
got="$(go-qcow2reader-example -offset="${offset}" -length="${length}" "${name_qcow2}" | sha256sum - | cut -d " " -f 1)"
got="$(go-qcow2reader-example read -offset="${offset}" -length="${length}" "${name_qcow2}" | sha256sum - | cut -d " " -f 1)"
set +x
echo "Comparing: ${expected} vs ${got}"
if [ "${expected}" = "${got}" ]; then
Expand Down

0 comments on commit d597d74

Please sign in to comment.