pax_global_header00006660000000000000000000000064150171176440014520gustar00rootroot0000000000000052 comment=5609089578b61d00e73f0a9d08c0a697c06158eb golang-github-u-root-uio-0.0~git20240224.d2acac8/000077500000000000000000000000001501711764400210515ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/.github/000077500000000000000000000000001501711764400224115ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/.github/workflows/000077500000000000000000000000001501711764400244465ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/.github/workflows/go.yml000066400000000000000000000037031501711764400256010ustar00rootroot00000000000000name: Go on: push: branches: [ main ] pull_request: branches: [ main ] # Cancel running workflows on new push to a PR. concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} cancel-in-progress: true jobs: tidy: name: Tidy runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v4 with: go-version: '1.22.x' - name: gofmt run: test -z "$(gofmt -s -l $(find -name '*.go'))" - name: go mod tidy run: | go mod tidy git status if [[ -n "$(git status --porcelain .)" ]]; then echo 'go.mod/go.sum is out-of-date: run `go mod tidy` and then check in the changes' echo 'If `go mod tidy` results in no changes, make sure you are using the latest relase of Go' git status --porcelain . exit 1 fi build: name: Build runs-on: ubuntu-latest strategy: matrix: goversion: ['1.21.x', '1.22.x'] steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.goversion }} - run: go build -v ./... - run: ./cross-compile.sh test: name: Test runs-on: ubuntu-latest strategy: matrix: goversion: ['1.21.x', '1.22.x'] steps: - uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v4 with: go-version: ${{ matrix.goversion }} - name: Test run: go test -v -covermode atomic -coverpkg ./... -coverprofile cover.out ./... - name: Race run: go test -race -v ./... - uses: codecov/codecov-action@v4-beta env: CODECOV_TOKEN: '9f38c23a-5d79-4a19-a90f-72edcac95ce1' with: flags: ${{ matrix.goversion }} fail_ci_if_error: true verbose: true golang-github-u-root-uio-0.0~git20240224.d2acac8/.github/workflows/golangci-lint.yml000066400000000000000000000011161501711764400277170ustar00rootroot00000000000000name: golangci-lint on: push: tags: - v* branches: - main pull_request: branches: - main permissions: contents: read # Optional: allow read access to pull request. Use with `only-new-issues` option. pull-requests: read jobs: golangci: name: lint runs-on: ubuntu-latest steps: - uses: actions/setup-go@v3 with: go-version: '1.22.x' - uses: actions/checkout@v3 - name: golangci-lint uses: golangci/golangci-lint-action@v3 with: version: latest only-new-issues: true golang-github-u-root-uio-0.0~git20240224.d2acac8/.gitignore000066400000000000000000000004151501711764400230410ustar00rootroot00000000000000# Binaries for programs and plugins *.exe *.exe~ *.dll *.so *.dylib # Test binary, built with `go test -c` *.test # Output of the go coverage tool, specifically when used with LiteIDE *.out # Dependency directories (remove the comment below to include it) # vendor/ golang-github-u-root-uio-0.0~git20240224.d2acac8/.golangci.yml000066400000000000000000000035621501711764400234430ustar00rootroot00000000000000linters: enable: - containedctx - gocritic - godot - nilerr - revive - thelper - unconvert issues: include: - EXC0012 - EXC0013 - EXC0014 - EXC0015 linters-settings: revive: # Maximum number of open files at the same time. # See https://github.com/mgechev/revive#command-line-flags # Defaults to unlimited. max-open-files: 2048 # When set to false, ignores files with "GENERATED" header, similar to golint. # See https://github.com/mgechev/revive#available-rules for details. # Default: false ignore-generated-header: true # Sets the default severity. # See https://github.com/mgechev/revive#configuration # Default: warning severity: error # Default: false # Sets the default failure confidence. # This means that linting errors with less than 0.8 confidence will be ignored. # Default: 0.8 confidence: 0.8 rules: - name: blank-imports - name: context-as-argument arguments: - allowTypesBefore: "*testing.T,*github.com/user/repo/testing.Harness" - name: context-keys-type - name: error-return - name: error-strings - name: error-naming - name: exported arguments: - "checkPrivateReceivers" - "sayRepetitiveInsteadOfStutters" - name: if-return - name: increment-decrement - name: var-naming - name: var-declaration - name: package-comments - name: range - name: receiver-naming - name: time-naming - name: unexported-return - name: indent-error-flow - name: errorf - name: early-return - name: file-header arguments: - "Copyright 20[1-2][0-9](-20[1-2][0-9])? the u-root Authors. All rights reserved Use of this source code is governed by a BSD-style license that can be found in the LICENSE file." golang-github-u-root-uio-0.0~git20240224.d2acac8/.revive.toml000066400000000000000000000013301501711764400233210ustar00rootroot00000000000000ignoreGeneratedHeader = false severity = "warning" confidence = 0.8 errorCode = 0 warningCode = 0 [rule.blank-imports] [rule.context-as-argument] [rule.context-keys-type] [rule.dot-imports] [rule.error-return] [rule.error-strings] [rule.error-naming] [rule.exported] [rule.if-return] [rule.increment-decrement] [rule.var-naming] [rule.var-declaration] [rule.package-comments] [rule.range] [rule.receiver-naming] [rule.time-naming] [rule.unexported-return] [rule.indent-error-flow] [rule.errorf] [rule.early-return] [rule.file-header] arguments=["Copyright 20[1-2][0-9](-20[1-2][0-9])? the u-root Authors. All rights reserved Use of this source code is governed by a BSD-style license that can be found in the LICENSE file."] golang-github-u-root-uio-0.0~git20240224.d2acac8/LICENSE000066400000000000000000000027571501711764400220710ustar00rootroot00000000000000BSD 3-Clause License Copyright (c) 2012-2021, u-root Authors All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. golang-github-u-root-uio-0.0~git20240224.d2acac8/README.md000066400000000000000000000010261501711764400223270ustar00rootroot00000000000000# uio [![CircleCI](https://circleci.com/gh/u-root/uio.svg?style=svg)](https://circleci.com/gh/u-root/uio) [![Go Report Card](https://goreportcard.com/badge/github.com/u-root/uio)](https://goreportcard.com/report/github.com/u-root/uio) [![GoDoc](https://godoc.org/github.com/u-root/uio?status.svg)](https://godoc.org/github.com/u-root/uio) [![Slack](https://slack.osfw.dev/badge.svg)](https://slack.osfw.dev) [![License](https://img.shields.io/badge/License-BSD%203--Clause-blue.svg)](https://github.com/u-root/uio/blob/main/LICENSE) golang-github-u-root-uio-0.0~git20240224.d2acac8/cli/000077500000000000000000000000001501711764400216205ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/cli/cli.go000066400000000000000000000040701501711764400227170ustar00rootroot00000000000000// Copyright 2024 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package cli provides a bare bones CLI with commands. // // cli supports standard Go flags. package cli import ( "flag" "fmt" "io" "os" "slices" "strings" "text/tabwriter" ) // Command is a CLI command. type Command struct { // Name or Aliases are used to match argv[1] and select this command. Name string Aliases []string // Short is a one-line description for the command. Short string // Run is called if argv[1] matches the command. Flags are parsed before Run is called. Run func(args []string) flags *flag.FlagSet } // Flags returns a modifiable flag set for this command. // // If argv[1] matches this command, these flags will be parsed. func (c *Command) Flags() *flag.FlagSet { if c.flags == nil { c.flags = flag.NewFlagSet(c.Name, flag.ContinueOnError) } return c.flags } // An App is composed of many commands. type App []Command // Help returns the app's help string. func (a App) Help() string { var s strings.Builder w := tabwriter.NewWriter(&s, 1, 2, 4, ' ', 0) fmt.Fprintf(w, "Commands:\n\n") for _, cmd := range a { fmt.Fprintf(w, "\t%s\t%s\n", cmd.Name, cmd.Short) } w.Flush() return s.String() } func (a App) commandFor(args []string) *Command { if len(args) == 0 { return nil } for _, cmd := range a { if args[0] == cmd.Name || slices.Contains(cmd.Aliases, args[0]) { return &cmd } } return nil } func (a App) run(errW io.Writer, args []string) int { if len(args) == 0 { fmt.Fprintf(errW, "No program name provided\n") return 1 } cmd := a.commandFor(args[1:]) if cmd == nil { fmt.Fprintf(errW, "%s", a.Help()) return 1 } cmd.Flags().SetOutput(errW) if err := cmd.Flags().Parse(args[2:]); err != nil { cmd.Flags().Output() return 1 } cmd.Run(cmd.Flags().Args()) return 0 } // Run runs the app. Expects program name as the first arg, and an optional command name next. func (a App) Run(args []string) { os.Exit(a.run(os.Stderr, args)) } golang-github-u-root-uio-0.0~git20240224.d2acac8/cli/cli_test.go000066400000000000000000000067111501711764400237620ustar00rootroot00000000000000// Copyright 2024 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package cli import ( "reflect" "strings" "testing" ) func TestCLI(t *testing.T) { type flags struct { output string input string } var f flags var cmd string var cmdArgs []string makeCmd := Command{ Name: "make", Short: "create uimage", Run: func(args []string) { cmdArgs = args cmd = "make" }, } makeCmd.Flags().StringVar(&f.output, "o", "", "Output") makeCmd.Flags().StringVar(&f.input, "i", "", "Input") listCmd := Command{ Name: "list", Short: "list uimage", Aliases: []string{"ls", "l"}, Run: func(args []string) { cmdArgs = args cmd = "list" }, } app := App{makeCmd, listCmd} for _, tt := range []struct { name string args []string wantCmd string wantCmdArgs []string wantFlags flags wantExit int wantPrint string }{ { name: "cmd with flag", args: []string{"uimage", "make", "-o", "high", "foobar", "bla"}, wantCmdArgs: []string{"foobar", "bla"}, wantCmd: "make", wantFlags: flags{ output: "high", }, wantExit: 0, wantPrint: "", }, { name: "not exist", args: []string{"uimage", "notmake", "-o", "low"}, wantExit: 1, wantPrint: "Commands:\n\n make create uimage\n list list uimage\n", }, { name: "cmd exist but flag doesn't", args: []string{"uimage", "list", "-o", "low"}, wantExit: 1, wantPrint: "flag provided but not defined: -o\nUsage of list:\n", }, { name: "cmd with no flags", args: []string{"uimage", "list", "anything"}, wantExit: 0, wantCmd: "list", wantCmdArgs: []string{"anything"}, }, { name: "alias", args: []string{"uimage", "ls", "anything"}, wantExit: 0, wantCmd: "list", wantCmdArgs: []string{"anything"}, }, { name: "no program name", wantExit: 1, wantPrint: "No program name provided\n", }, { name: "no command name", args: []string{"uimage"}, wantExit: 1, wantPrint: "Commands:\n\n make create uimage\n list list uimage\n", }, { name: "cmd help", args: []string{"uimage", "make", "-h"}, wantExit: 1, wantPrint: "Usage of make:\n -i string\n \tInput\n -o string\n \tOutput\n", }, { name: "app help", args: []string{"uimage", "-h"}, wantExit: 1, wantPrint: "Commands:\n\n make create uimage\n list list uimage\n", }, { name: "app help 2", args: []string{"uimage", "help"}, wantExit: 1, wantPrint: "Commands:\n\n make create uimage\n list list uimage\n", }, } { t.Run(tt.name, func(t *testing.T) { f = flags{} cmd = "" cmdArgs = nil var s strings.Builder exitCode := app.run(&s, tt.args) t.Logf("App:\n%s", s.String()) if exitCode != tt.wantExit { t.Errorf("run = %d, want %d", exitCode, tt.wantExit) } if cmd != tt.wantCmd { t.Errorf("run = cmd %s, want cmd %s", cmd, tt.wantCmd) } if !reflect.DeepEqual(cmdArgs, tt.wantCmdArgs) { t.Errorf("run = args %+v, want %+v", cmdArgs, tt.wantCmdArgs) } if !reflect.DeepEqual(f, tt.wantFlags) { t.Errorf("run = flags %+v, want %+v", f, tt.wantFlags) } if got := s.String(); got != tt.wantPrint { t.Errorf("run = %#v, want %#v", got, tt.wantPrint) } }) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/cp/000077500000000000000000000000001501711764400214535ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/cp/cmp/000077500000000000000000000000001501711764400222325ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/cp/cmp/cmp.go000066400000000000000000000052371501711764400233470ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package cmp compares trees of files. // // cmp is an internal package for pkg/cp's and cmds/core/cp's tests. package cmp import ( "fmt" "os" "path/filepath" "reflect" "github.com/u-root/uio/cp" "github.com/u-root/uio/uio" ) // isEqualFile compare two files by checksum. func isEqualFile(fpath1, fpath2 string) error { file1, err := os.Open(fpath1) if err != nil { return err } defer file1.Close() file2, err := os.Open(fpath2) if err != nil { return err } defer file2.Close() if !uio.ReaderAtEqual(file1, file2) { return fmt.Errorf("%q and %q do not have equal content", fpath1, fpath2) } return nil } func readDirNames(path string) ([]string, error) { entries, err := os.ReadDir(path) if err != nil { return nil, err } var basenames []string for _, entry := range entries { basenames = append(basenames, entry.Name()) } return basenames, nil } func stat(o cp.Options, path string) (os.FileInfo, error) { if o.NoFollowSymlinks { return os.Lstat(path) } return os.Stat(path) } // IsEqualTree compare the content in the file trees in src and dst paths. func IsEqualTree(o cp.Options, src, dst string) error { srcInfo, err := stat(o, src) if err != nil { return err } dstInfo, err := stat(o, dst) if err != nil { return err } if sm, dm := srcInfo.Mode()&os.ModeType, dstInfo.Mode()&os.ModeType; sm != dm { return fmt.Errorf("mismatched mode: %q has mode %s while %q has mode %s", src, sm, dst, dm) } switch { case srcInfo.Mode().IsDir(): srcEntries, err := readDirNames(src) if err != nil { return err } dstEntries, err := readDirNames(dst) if err != nil { return err } // os.ReadDir guarantees these are sorted. if !reflect.DeepEqual(srcEntries, dstEntries) { return fmt.Errorf("directory contents did not match:\n%q had %v\n%q had %v", src, srcEntries, dst, dstEntries) } for _, basename := range srcEntries { if err := IsEqualTree(o, filepath.Join(src, basename), filepath.Join(dst, basename)); err != nil { return err } } return nil case srcInfo.Mode().IsRegular(): return isEqualFile(src, dst) case srcInfo.Mode()&os.ModeSymlink == os.ModeSymlink: srcTarget, err := os.Readlink(src) if err != nil { return err } dstTarget, err := os.Readlink(dst) if err != nil { return err } if srcTarget != dstTarget { return fmt.Errorf("target mismatch: symlink %q had target %q, while %q had target %q", src, srcTarget, dst, dstTarget) } return nil default: return fmt.Errorf("unsupported mode: %s", srcInfo.Mode()) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/cp/cp.go000066400000000000000000000070011501711764400224020ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package cp implements routines to copy files. // // CopyTree in particular copies entire trees of files. // // Only directories, symlinks, and regular files are currently supported. package cp import ( "errors" "fmt" "io" "os" "path/filepath" ) // ErrSkip can be returned by PreCallback to skip a file. var ErrSkip = errors.New("skip") // Options are configuration options for how copying files should behave. type Options struct { // If NoFollowSymlinks is set, Copy copies the symlink itself rather // than following the symlink and copying the file it points to. NoFollowSymlinks bool // PreCallback is called on each file to be copied before it is copied // if specified. // // If PreCallback returns ErrSkip, the file is skipped and Copy returns // nil. // // If PreCallback returns another non-nil error, the file is not copied // and Copy returns the error. PreCallback func(src, dst string, srcfi os.FileInfo) error // PostCallback is called on each file after it is copied if specified. PostCallback func(src, dst string) } // Default are the default options. Default follows symlinks. var Default = Options{} // NoFollowSymlinks is the default options with following symlinks turned off. var NoFollowSymlinks = Options{ NoFollowSymlinks: true, } func (o Options) stat(path string) (os.FileInfo, error) { if o.NoFollowSymlinks { return os.Lstat(path) } return os.Stat(path) } // Copy copies a file at src to dst. func (o Options) Copy(src, dst string) error { srcInfo, err := o.stat(src) if err != nil { return err } if o.PreCallback != nil { if err := o.PreCallback(src, dst, srcInfo); err == ErrSkip { return nil } else if err != nil { return err } } if err := copyFile(src, dst, srcInfo); err != nil { return err } if o.PostCallback != nil { o.PostCallback(src, dst) } return nil } // CopyTree recursively copies all files in the src tree to dst. func (o Options) CopyTree(src, dst string) error { return filepath.Walk(src, func(path string, fi os.FileInfo, err error) error { if err != nil { return err } rel, err := filepath.Rel(src, path) if err != nil { return err } return o.Copy(path, filepath.Join(dst, rel)) }) } // Copy src file to dst file using Default's config. func Copy(src, dst string) error { return Default.Copy(src, dst) } // CopyTree recursively copies all files in the src tree to dst using Default's // config. func CopyTree(src, dst string) error { return Default.CopyTree(src, dst) } func copyFile(src, dst string, srcInfo os.FileInfo) error { m := srcInfo.Mode() switch { case m.IsDir(): return os.MkdirAll(dst, srcInfo.Mode().Perm()) case m.IsRegular(): return copyRegularFile(src, dst, srcInfo) case m&os.ModeSymlink == os.ModeSymlink: // Yeah, this may not make any sense logically. But this is how // cp does it. target, err := os.Readlink(src) if err != nil { return err } return os.Symlink(target, dst) default: return &os.PathError{ Op: "copy", Path: src, Err: fmt.Errorf("unsupported file mode %s", m), } } } func copyRegularFile(src, dst string, srcfi os.FileInfo) error { srcf, err := os.Open(src) if err != nil { return err } defer srcf.Close() dstf, err := os.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, srcfi.Mode().Perm()) if err != nil { return err } defer dstf.Close() _, err = io.Copy(dstf, srcf) return err } golang-github-u-root-uio-0.0~git20240224.d2acac8/cp/cp_test.go000066400000000000000000000066361501711764400234560ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package cp import ( "errors" "fmt" "io/fs" "os" "path/filepath" "strings" "testing" "golang.org/x/sys/unix" ) var ( testdata = []byte("This is a test string") ) func TestCopySimple(t *testing.T) { var err error tmpdirDst := t.TempDir() tmpdirSrc := t.TempDir() srcfiles := make([]*os.File, 2) dstfiles := make([]*os.File, 2) for iterator := range srcfiles { srcfiles[iterator], err = os.CreateTemp(tmpdirSrc, "file-to-copy"+fmt.Sprintf("%d", iterator)) if err != nil { t.Errorf("failed to create temp file: %q", err) } if _, err = srcfiles[iterator].Write(testdata); err != nil { t.Errorf("failed to write testdata to file") } dstfiles[iterator], err = os.CreateTemp(tmpdirDst, "file-to-copy"+fmt.Sprintf("%d", iterator)) if err != nil { t.Errorf("failed to create temp file: %q", err) } } sl := filepath.Join(tmpdirDst, "test-symlink") if err := os.Symlink(srcfiles[1].Name(), sl); err != nil { t.Errorf("creating symlink failed") } for _, tt := range []struct { name string srcfile string dstfile string opt Options wantErr error }{ { name: "Success", srcfile: srcfiles[0].Name(), dstfile: dstfiles[0].Name(), opt: Default, }, { name: "SrcDstDirctoriesSuccess", srcfile: tmpdirSrc, dstfile: tmpdirDst, }, { name: "SrcNotExist", srcfile: "file-does-not-exist", dstfile: dstfiles[0].Name(), wantErr: fs.ErrNotExist, }, { name: "DstIsDirectory", srcfile: srcfiles[0].Name(), dstfile: tmpdirDst, wantErr: unix.EISDIR, }, { name: "CopySymlink", srcfile: sl, dstfile: dstfiles[1].Name(), opt: Options{ NoFollowSymlinks: false, }, }, { name: "CopySymlinkFollow", srcfile: sl, dstfile: filepath.Join(tmpdirDst, "followed-symlink"), opt: Options{ NoFollowSymlinks: true, }, }, } { t.Run(tt.name, func(t *testing.T) { err := Copy(tt.srcfile, tt.dstfile) if !errors.Is(err, tt.wantErr) { t.Errorf("Test %q failed. Want: %q, Got: %q", tt.name, tt.wantErr, err) } }) // After every test with NoFollowSymlink we have to delete the created symlink. if strings.Contains(tt.dstfile, "symlink") { os.Remove(tt.dstfile) } t.Run(tt.name, func(t *testing.T) { if err := tt.opt.Copy(tt.srcfile, tt.dstfile); !errors.Is(err, tt.wantErr) { t.Errorf("%q failed. Want: %q, Got: %q", tt.name, tt.wantErr, err) } }) // After every test with NoFollowSymlink we have to delete the created symlink. if strings.Contains(tt.dstfile, "symlink") { os.Remove(tt.dstfile) } t.Run(tt.name, func(t *testing.T) { if err := tt.opt.CopyTree(tt.srcfile, tt.dstfile); !errors.Is(err, tt.wantErr) { t.Errorf("Test %q failed. Want: %q, Got: %q", tt.name, tt.wantErr, err) } }) // After every test with NoFollowSymlink we have to delete the created symlink. if strings.Contains(tt.dstfile, "symlink") { os.Remove(tt.dstfile) } t.Run(tt.name, func(t *testing.T) { if err := CopyTree(tt.srcfile, tt.dstfile); !errors.Is(err, tt.wantErr) { t.Errorf("Test %q failed. Want: %q, Got: %q", tt.name, tt.wantErr, err) } }) // After every test with NoFollowSymlink we have to delete the created symlink. if strings.Contains(tt.dstfile, "symlink") { os.Remove(tt.dstfile) } } } golang-github-u-root-uio-0.0~git20240224.d2acac8/cross-compile.sh000077500000000000000000000011341501711764400241660ustar00rootroot00000000000000#!/bin/bash set -eu GO="go" if [ -v GOROOT ]; then GO="$GOROOT/bin/go" fi function buildem() { for GOOS in $1 do for GOARCH in $2 do echo "Building $GOOS/$GOARCH..." GOOS=$GOOS GOARCH=$GOARCH $GO build ./... done done } GOARCHES="386 amd64 arm arm64 ppc64 ppc64le s390x mips mipsle mips64 mips64le" buildem "linux" "$GOARCHES" GOARCHES_BSD="386 amd64 arm arm64" GOOSES_BSD="freebsd netbsd openbsd" buildem "$GOOSES_BSD" "$GOARCHES_BSD" GOOSES_AMD64="solaris windows" buildem "$GOOSES_AMD64" "amd64" GOARCHES_DARWIN="arm64 amd64" buildem "darwin" "$GOARCHES_DARWIN" golang-github-u-root-uio-0.0~git20240224.d2acac8/go.mod000066400000000000000000000002131501711764400221530ustar00rootroot00000000000000module github.com/u-root/uio go 1.21 require ( github.com/pierrec/lz4/v4 v4.1.14 golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 ) golang-github-u-root-uio-0.0~git20240224.d2acac8/go.sum000066400000000000000000000005721501711764400222100ustar00rootroot00000000000000github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE= github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664 h1:wEZYwx+kK+KlZ0hpvP2Ls1Xr4+RWnlzGFwPP0aiDjIU= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang-github-u-root-uio-0.0~git20240224.d2acac8/llog/000077500000000000000000000000001501711764400220065ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/llog/default_test.go000066400000000000000000000006721501711764400250250ustar00rootroot00000000000000// Copyright 2024 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package llog_test import ( "log/slog" "github.com/u-root/uio/llog" ) func ExampleDefault_withtime() { l := llog.Default() l.Infof("An INFO level string") l.Debugf("A DEBUG level that does not appear") l.Level = slog.LevelDebug l.Debugf("A DEBUG level that appears") } golang-github-u-root-uio-0.0~git20240224.d2acac8/llog/example_test.go000066400000000000000000000010451501711764400250270ustar00rootroot00000000000000// Copyright 2024 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package llog_test import ( "flag" "log/slog" "github.com/u-root/uio/llog" ) func someFunc(v llog.Printf) { v("logs at the given level %s", "foo") } func Example() { l := llog.Default() // If -v is set, l.Level becomes slog.LevelDebug. l.RegisterDebugFlag(flag.CommandLine, "v") flag.Parse() someFunc(l.Debugf) someFunc(l.AtLevelFunc(slog.LevelWarn)) someFunc(l.Infof) } golang-github-u-root-uio-0.0~git20240224.d2acac8/llog/levellog.go000066400000000000000000000114531501711764400241520ustar00rootroot00000000000000// Copyright 2024 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package llog is a dirt-simple leveled text logger. package llog import ( "flag" "fmt" "io" "log" "log/slog" "math" "strconv" "strings" "testing" ) // Default is the stdlib default log sink. func Default() *Logger { return &Logger{Sink: SinkFor(log.Printf)} } // Test is a logger that prints every level to t.Logf. func Test(tb testing.TB) *Logger { tb.Helper() return &Logger{Sink: SinkFor(tb.Logf), Level: math.MinInt32} } // Debug prints to log.Printf at the debug level. func Debug() *Logger { return &Logger{Sink: SinkFor(log.Printf), Level: slog.LevelDebug} } // Printf is a logger printf function. type Printf func(format string, v ...any) // Printfer is an interface implementing Printf. type Printfer interface { Printf(format string, v ...any) } func lineSprintf(format string, v ...any) string { s := fmt.Sprintf(format, v...) if strings.HasSuffix(s, "\n") { return s } return s + "\n" } // WritePrintf is a Printf that prints lines to w. func WritePrintf(w io.Writer) Printf { return func(format string, v ...any) { _, _ = io.WriteString(w, lineSprintf(format, v...)) } } // MultiPrintf is a Printf that prints to all given p. func MultiPrintf(p ...Printf) Printf { return func(format string, v ...any) { for _, q := range p { if q != nil { q(format, v...) } } } } // Sink is the output for Logger. type Sink func(level slog.Level, format string, v ...any) // SinkFor prepends the log with a log level and outputs to p. func SinkFor(p Printf) Sink { return func(level slog.Level, format string, args ...any) { // Prepend log level. format = "%s " + format args = append([]any{level}, args...) p(format, args...) } } // Logger is a dirt-simple leveled logger. // // If the log level is >= Level, it logs to the given Sink. // // Logger or Sink may be nil in order to log nothing. type Logger struct { Sink Sink Level slog.Level } // New creates a logger from p which prepends the log level to the output and // uses l as the default log level. // // Logs with level >= l will be printed using p. func New(l slog.Level, p Printf) *Logger { return &Logger{ Sink: SinkFor(p), Level: l, } } // RegisterLevelFlag registers a flag that sets the given numeric level as the level. func (l *Logger) RegisterLevelFlag(f *flag.FlagSet, flagName string) { f.IntVar((*int)(&l.Level), flagName, int(l.Level), "Level to log at. Lower level emits more logs. -4 = DEBUG, 0 = INFO, 4 = WARN, 8 = ERROR") } // RegisterVerboseFlag registers a boolean flag that, if set, assigns verboseLevel as the level. func (l *Logger) RegisterVerboseFlag(f *flag.FlagSet, flagName string, verboseLevel slog.Level) { f.BoolFunc(flagName, fmt.Sprintf("If set, logs at %d level", verboseLevel), func(val string) error { b, err := strconv.ParseBool(val) if err != nil { return err } if b { l.Level = verboseLevel } return nil }) } // RegisterDebugFlag registers a boolean flag that, if set, assigns LevelDebug as the level. func (l *Logger) RegisterDebugFlag(f *flag.FlagSet, flagName string) { l.RegisterVerboseFlag(f, flagName, slog.LevelDebug) } // AtLevelFunc returns a Printf that can be passed around to log at the given level. // // AtLevelFunc never returns nil. func (l *Logger) AtLevelFunc(level slog.Level) Printf { if l == nil || l.Sink == nil { return func(fmt string, args ...any) {} } return func(fmt string, args ...any) { l.Logf(level, fmt, args...) } } type printfer struct { printf Printf } // Printf implements Printfer. func (p printfer) Printf(format string, v ...any) { p.printf(format, v...) } // AtLevel returns a Printfer that can be passed around to log at the given level. // // AtLevel never returns nil. func (l *Logger) AtLevel(level slog.Level) Printfer { return printfer{printf: l.AtLevelFunc(level)} } // Debugf is a printf function that logs at the Debug level. func (l *Logger) Debugf(fmt string, args ...any) { if l == nil { return } l.Logf(slog.LevelDebug, fmt, args...) } // Infof is a printf function that logs at the Info level. func (l *Logger) Infof(fmt string, args ...any) { if l == nil { return } l.Logf(slog.LevelInfo, fmt, args...) } // Warnf is a printf function that logs at the Warn level. func (l *Logger) Warnf(fmt string, args ...any) { if l == nil { return } l.Logf(slog.LevelWarn, fmt, args...) } // Errorf is a printf function that logs at the Error level. func (l *Logger) Errorf(fmt string, args ...any) { if l == nil { return } l.Logf(slog.LevelError, fmt, args...) } // Logf logs at the given level. func (l *Logger) Logf(level slog.Level, fmt string, args ...any) { if l == nil || l.Sink == nil { return } if level >= l.Level { l.Sink(level, fmt, args...) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/llog/llog_test.go000066400000000000000000000077151501711764400243430ustar00rootroot00000000000000// Copyright 2024 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package llog import ( "flag" "fmt" "log" "log/slog" "strconv" "strings" "testing" ) func TestLevelFlag(t *testing.T) { for _, tt := range []struct { args []string want slog.Level }{ { args: []string{"-level=4"}, want: slog.LevelWarn, }, { args: []string{}, want: slog.LevelInfo, }, } { f := flag.NewFlagSet("", flag.ContinueOnError) v := &Logger{} v.RegisterLevelFlag(f, "level") _ = f.Parse(tt.args) if v.Level != tt.want { t.Errorf("Parse(%#v) = %v, want %v", tt.args, v, tt.want) } } for _, tt := range []struct { args []string want slog.Level err error }{ { args: []string{"-v"}, want: slog.LevelWarn, }, { args: []string{}, want: slog.LevelInfo, }, { args: []string{"-v=true"}, want: slog.LevelWarn, }, { args: []string{"-v=true", "-v=false"}, want: slog.LevelWarn, }, { args: []string{"-v=foobar"}, want: slog.LevelInfo, err: strconv.ErrSyntax, }, } { f := flag.NewFlagSet("", flag.ContinueOnError) v := &Logger{} v.RegisterVerboseFlag(f, "v", slog.LevelWarn) // Parse doesn't use %w. if err := f.Parse(tt.args); err != tt.err && err != nil && !strings.Contains(err.Error(), tt.err.Error()) { t.Errorf("Parse(%#v) = %v, want %v", tt.args, err, tt.err) } if v.Level != tt.want { t.Errorf("Parse(%#v) = %v, want %v", tt.args, v, tt.want) } } for _, tt := range []struct { args []string want slog.Level err error }{ { args: []string{"-v"}, want: slog.LevelDebug, }, { args: []string{}, want: slog.LevelInfo, }, { args: []string{"-v=true"}, want: slog.LevelDebug, }, { args: []string{"-v=true", "-v=false"}, want: slog.LevelDebug, }, { args: []string{"-v=foobar"}, want: slog.LevelInfo, err: strconv.ErrSyntax, }, } { f := flag.NewFlagSet("", flag.ContinueOnError) v := &Logger{} v.RegisterDebugFlag(f, "v") // Parse doesn't use %w. if err := f.Parse(tt.args); err != tt.err && err != nil && !strings.Contains(err.Error(), tt.err.Error()) { t.Errorf("Parse(%#v) = %v, want %v", tt.args, err, tt.err) } if v.Level != tt.want { t.Errorf("Parse(%#v) = %v, want %v", tt.args, v, tt.want) } } } func TestNilLogger(t *testing.T) { for _, l := range []*Logger{nil, {}} { // Test that none of this panics. l.AtLevelFunc(slog.LevelDebug)("nothing") l.AtLevel(slog.LevelDebug).Printf("nothing") l.Debugf("nothing") l.Infof("nothing") l.Warnf("nothing") l.Errorf("nothing") l.Logf(slog.LevelDebug, "nothing") } } func TestLog(t *testing.T) { var s strings.Builder l := New(slog.LevelDebug, func(format string, args ...any) { fmt.Fprintf(&s, format+"\n", args...) }) l.AtLevelFunc(slog.LevelDebug)("nothing") l.AtLevel(slog.LevelInfo).Printf("nothing") l.Debugf("nothing") l.Infof("nothing") l.Warnf("nothing") l.Errorf("nothing") l.Logf(slog.LevelDebug, "nothing") want := `DEBUG nothing INFO nothing DEBUG nothing INFO nothing WARN nothing ERROR nothing DEBUG nothing ` if got := s.String(); got != want { t.Errorf("got %v, want %v", got, want) } } func TestDefaults(t *testing.T) { var s strings.Builder log.SetOutput(&s) log.SetFlags(0) l := Debug() l.Debugf("foobar") want := "DEBUG foobar\n" if got := s.String(); got != want { t.Errorf("got %v, want %v", got, want) } l = Default() l.Debugf("bazzed") l.Infof("stuff") want = "DEBUG foobar\nINFO stuff\n" if got := s.String(); got != want { t.Errorf("got %v, want %v", got, want) } l = Test(t) l.Debugf("more foobar") } func TestBufOutput(t *testing.T) { var s strings.Builder l := New(slog.LevelDebug, MultiPrintf(nil, t.Logf, WritePrintf(&s))) l.Debugf("test output!") l.Infof("test output\n\n") got := s.String() want := "DEBUG test output!\nINFO test output\n\n" if got != want { t.Errorf("got = %v, want %v", got, want) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/000077500000000000000000000000001501711764400217755ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/random.go000066400000000000000000000033641501711764400236120ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package rand implements cancelable reads from a cryptographically safe // random number source. package rand import ( "context" ) // Reader is a cryptographically safe random number source. var Reader = DefaultReaderWithContext(context.Background()) // Read blockingly reads from a random number source. func Read(b []byte) (int, error) { return Reader.Read(b) } // ReadContext is a context-aware reader for random numbers. func ReadContext(ctx context.Context, b []byte) (int, error) { return Reader.ReadContext(ctx, b) } // ContextReader is a cancelable io.Reader. type ContextReader interface { // Read behaves like a blocking io.Reader.Read. // // Read wraps ReadContext with a background context. Read(b []byte) (n int, err error) // ReadContext is an io.Reader that blocks until data is available or // until ctx is done. ReadContext(ctx context.Context, b []byte) (n int, err error) } // contextReader is a cancelable io.Reader. type contextReader interface { ReadContext(context.Context, []byte) (int, error) } // ctxReader takes a contextReader and turns it into a ContextReader. type ctxReader struct { contextReader ctx context.Context //nolint:containedctx } func (cr ctxReader) Read(b []byte) (int, error) { return cr.contextReader.ReadContext(cr.ctx, b) } // DefaultReaderWithContext returns a context-aware io.Reader. // // Because this stores the context, only use this in situations where an // io.Reader is unavoidable. func DefaultReaderWithContext(ctx context.Context) ContextReader { return ctxReader{ ctx: ctx, contextReader: defaultContextReader, } } golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/random_linux.go000066400000000000000000000035331501711764400250270ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package rand import ( "context" "log" "os" "sync" "syscall" "time" "golang.org/x/sys/unix" ) var defaultContextReader = &getrandomReader{} var backupReader = &urandomReader{} type getrandomReader struct { once sync.Once backup bool } // ReadContext implements a cancelable read from /dev/urandom. func (r *getrandomReader) ReadContext(ctx context.Context, b []byte) (int, error) { r.once.Do(func() { if os.Getenv("UROOT_NOHWRNG") != "" { r.backup = true return } if _, err := unix.Getrandom(b, unix.GRND_NONBLOCK); err == syscall.ENOSYS { r.backup = true } }) if r.backup { return backupReader.ReadContext(ctx, b) } for { // getrandom(2) with GRND_NONBLOCK uses the urandom number // source, but only returns numbers if the crng has been // initialized. // // This is preferrable to /dev/urandom, as /dev/urandom will // make up fake random numbers until the crng has been // initialized. n, err := unix.Getrandom(b, unix.GRND_NONBLOCK) if err == nil { return n, nil } select { case <-ctx.Done(): return 0, ctx.Err() default: if err != syscall.EAGAIN && err != syscall.EINTR { return n, err } } } } // ReadContextWithSlowLogs logs a helpful message if it takes a significant // amount of time (>2s) to produce random data. func (r *getrandomReader) ReadContextWithSlowLogs(ctx context.Context, b []byte) (int, error) { d := 2 * time.Second t := time.AfterFunc(d, func() { log.Printf("getrandom is taking a long time (>%v). "+ "If running on hardware, consider enabling Linux's CONFIG_RANDOM_TRUST_CPU=y. "+ "If running in a VM/emulator, try setting up virtio-rng.", d) }) defer t.Stop() return r.ReadContext(ctx, b) } golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/random_std.go000066400000000000000000000012211501711764400244520ustar00rootroot00000000000000// Copyright 2020 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build plan9 || windows // +build plan9 windows package rand import ( "context" "crypto/rand" ) var defaultContextReader = &cryptoRandReader{} type cryptoRandReader struct{} // ReadContext implements a cancelable read. func (r *cryptoRandReader) ReadContext(ctx context.Context, b []byte) (n int, err error) { ch := make(chan struct{}) go func() { n, err = rand.Reader.Read(b) close(ch) }() select { case <-ctx.Done(): return 0, ctx.Err() case <-ch: return n, err } } golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/random_test.go000066400000000000000000000013471501711764400246500ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package rand import ( "context" "testing" "time" ) func TestRandomRead(t *testing.T) { b := make([]byte, 5) n, err := Read(b) if err != nil { t.Fatalf("got %v, expected nil err", err) } if n != 5 { t.Fatalf("got %d bytes, expected 5 bytes", n) } } func TestRandomReadContext(t *testing.T) { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() b := make([]byte, 5) n, err := ReadContext(ctx, b) if err != nil { t.Fatalf("got %v, expected nil err", err) } if n != 5 { t.Fatalf("got %d bytes, expected 5 bytes", n) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/random_unix.go000066400000000000000000000005721501711764400246530ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build aix || darwin || dragonfly || freebsd || nacl || netbsd || openbsd || solaris // +build aix darwin dragonfly freebsd nacl netbsd openbsd solaris package rand var defaultContextReader = &urandomReader{} golang-github-u-root-uio-0.0~git20240224.d2acac8/rand/random_urandom.go000066400000000000000000000023311501711764400253300ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:build aix || darwin || dragonfly || freebsd || nacl || netbsd || openbsd || solaris || linux // +build aix darwin dragonfly freebsd nacl netbsd openbsd solaris linux package rand import ( "context" "fmt" "sync" "syscall" "golang.org/x/sys/unix" ) // urandomReader is a contextReader. type urandomReader struct { once sync.Once // fd is expected to be non-blocking. fd int } func (r *urandomReader) init() error { var realErr error r.once.Do(func() { fd, err := unix.Open("/dev/urandom", unix.O_RDONLY, 0) if err != nil { realErr = fmt.Errorf("open(/dev/urandom): %v", err) return } r.fd = fd }) return realErr } // ReadContext implements a cancelable read from /dev/urandom. func (r *urandomReader) ReadContext(ctx context.Context, b []byte) (int, error) { if err := r.init(); err != nil { return 0, err } for { n, err := unix.Read(r.fd, b) if err == nil { return n, nil } select { case <-ctx.Done(): return 0, ctx.Err() default: if err != syscall.EAGAIN && err != syscall.EINTR { return n, err } } } } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/000077500000000000000000000000001501711764400216455ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/alignreader.go000066400000000000000000000016701501711764400244550ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "io" ) // AlignReader keeps track of how many bytes were read so the reader can be // aligned at a future time. type AlignReader struct { R io.Reader N int } // Read reads from the underlying io.Reader. func (r *AlignReader) Read(b []byte) (int, error) { n, err := r.R.Read(b) r.N += n return n, err } // ReadByte reads one byte from the underlying io.Reader. func (r *AlignReader) ReadByte() (byte, error) { b := make([]byte, 1) _, err := io.ReadFull(r, b) return b[0], err } // Align aligns the reader to the given number of bytes and returns the // bytes read to pad it. func (r *AlignReader) Align(n int) ([]byte, error) { if r.N%n == 0 { return []byte{}, nil } pad := make([]byte, n-r.N%n) m, err := io.ReadFull(r, pad) return pad[:m], err } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/alignwriter.go000066400000000000000000000013571501711764400245310ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "io" ) // AlignWriter keeps track of how many bytes were written so the writer can be // aligned at a future time. type AlignWriter struct { W io.Writer N int } // Write writes to the underlying io.Writew. func (w *AlignWriter) Write(b []byte) (int, error) { n, err := w.W.Write(b) w.N += n return n, err } // Align aligns the writer to the given number of bytes using the given pad // value. func (w *AlignWriter) Align(n int, pad byte) error { if w.N%n == 0 { return nil } _, err := w.Write(bytes.Repeat([]byte{pad}, n-w.N%n)) return err } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/archivereader.go000066400000000000000000000051621501711764400250040ustar00rootroot00000000000000// Copyright 2021 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "errors" "io" "github.com/pierrec/lz4/v4" ) const ( // preReadSizeBytes is the num of bytes pre-read from a io.Reader that will // be used to match against archive header. defaultArchivePreReadSizeBytes = 1024 ) // ErrPreReadError indicates there was not enough underlying data to decompress. var ErrPreReadError = errors.New("pre-read nothing") // ArchiveReader reads from a io.Reader, decompresses source bytes // when applicable. // // It allows probing for multiple archive format, while still able // to read from beginning, by pre-reading a small number of bytes. // // Always use newArchiveReader to initialize. type ArchiveReader struct { // src is where we read source bytes. src io.Reader // buf stores pre-read bytes from original io.Reader. Archive format // detection will be done against it. buf []byte // preReadSizeBytes is how many bytes we pre-read for magic number // matching for each archive type. This should be greater than or // equal to the largest header frame size of each supported archive // format. preReadSizeBytes int } // NewArchiveReader is a decompression reader. func NewArchiveReader(r io.Reader) (ArchiveReader, error) { ar := ArchiveReader{ src: r, // Randomly chosen, should be enough for most types: // // e.g. gzip with 10 byte header, lz4 with a header size // between 7 and 19 bytes. preReadSizeBytes: defaultArchivePreReadSizeBytes, } pbuf := make([]byte, ar.preReadSizeBytes) nr, err := io.ReadFull(r, pbuf) // In case the image is smaller pre-read block size, 1kb for now. // Ever possible ? probably not in case a compression is needed! ar.buf = pbuf[:nr] if err == io.EOF { // If we could not pre-read anything, we can't determine if // it is a compressed file. ar.src = io.MultiReader(bytes.NewReader(pbuf[:nr]), r) return ar, ErrPreReadError } // Try each supported compression type, return upon first match. // Try lz4. // magic number error will be thrown if source is not a lz4 archive. // e.g. "lz4: bad magic number". if ok, err := lz4.ValidFrameHeader(ar.buf); err == nil && ok { ar.src = lz4.NewReader(io.MultiReader(bytes.NewReader(ar.buf), r)) return ar, nil } // Try other archive types here, gzip, xz, etc when needed. // Last resort, read as is. ar.src = io.MultiReader(bytes.NewReader(ar.buf), r) return ar, nil } // Read reads from the archive uncompressed. func (ar ArchiveReader) Read(p []byte) (n int, err error) { return ar.src.Read(p) } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/archivereader_test.go000066400000000000000000000153161501711764400260450ustar00rootroot00000000000000// Copyright 2021 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "io" "math/rand" "strings" "testing" "time" "github.com/pierrec/lz4/v4" ) const choices = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" func TestArchiveReaderRegular(t *testing.T) { dataStr := strings.Repeat("This is an important data!@#$%^^&&*&**(()())", 1000) ar, err := NewArchiveReader(bytes.NewReader([]byte(dataStr))) if err != nil { t.Fatalf("newArchiveReader(bytes.NewReader(%v)) returned error: %v", []byte(dataStr), err) } buf := new(strings.Builder) if _, err := io.Copy(buf, ar); err != nil { t.Errorf("io.Copy(%v, %v) returned error: %v, want nil.", buf, ar, err) } if buf.String() != dataStr { t.Errorf("got %s, want %s", buf.String(), dataStr) } } func TestArchiveReaderPreReadShort(t *testing.T) { dataStr := "short data" ar, err := NewArchiveReader(bytes.NewReader([]byte(dataStr))) if err != nil { t.Errorf("newArchiveReader(bytes.NewReader([]byte(%s))) returned err: %v, want nil", dataStr, err) } got, err := io.ReadAll(ar) if err != nil { t.Errorf("got error reading archive reader: %v, want nil", err) } if string(got) != dataStr { t.Errorf("got %s, want %s", string(got), dataStr) } // Pre-read nothing. dataStr = "" ar, err = NewArchiveReader(bytes.NewReader([]byte(dataStr))) if err != ErrPreReadError { t.Errorf("newArchiveReader(bytes.NewReader([]byte(%s))) returned err: %v, want %v", dataStr, err, ErrPreReadError) } got, err = io.ReadAll(ar) if err != nil { t.Errorf("got error reading archive reader: %v, want nil", err) } if string(got) != dataStr { t.Errorf("got %s, want %s", string(got), dataStr) } } // randomString generates random string of fixed length in a fast and simple way. func randomString(l int) string { //nolint rand.Seed(time.Now().UnixNano()) r := make([]byte, l) for i := 0; i < l; i++ { r[i] = choices[rand.Intn(len(choices))] } return string(r) } func checkArchiveReaderLZ4(t *testing.T, tt archiveReaderLZ4Case) { t.Helper() srcR := bytes.NewReader([]byte(tt.dataStr)) srcBuf := new(bytes.Buffer) lz4w := tt.setup(srcBuf) n, err := io.Copy(lz4w, srcR) if err != nil { t.Fatalf("io.Copy(%v, %v) returned error: %v, want nil", lz4w, srcR, err) } if n != int64(len([]byte(tt.dataStr))) { t.Fatalf("got %d bytes compressed, want %d", n, len([]byte(tt.dataStr))) } if err = lz4w.Close(); err != nil { t.Fatalf("Failed to close lz4 writer: %v", err) } // Test ArchiveReader reading it. ar, err := NewArchiveReader(bytes.NewReader(srcBuf.Bytes())) if err != nil { t.Fatalf("newArchiveReader(bytes.NewReader(%v)) returned error: %v", srcBuf.Bytes(), err) } buf := new(strings.Builder) if _, err := io.Copy(buf, ar); err != nil { t.Errorf("io.Copy(%v, %v) returned error: %v, want nil.", buf, ar, err) } if buf.String() != tt.dataStr { t.Errorf("got %s, want %s", buf.String(), tt.dataStr) } } type archiveReaderLZ4Case struct { name string setup func(w io.Writer) *lz4.Writer dataStr string } func TestArchiveReaderLZ4(t *testing.T) { for _, tt := range []archiveReaderLZ4Case{ { name: "non-legacy regular", setup: lz4.NewWriter, dataStr: randomString(1024), }, { name: "non-legacy larger data", setup: lz4.NewWriter, dataStr: randomString(5 * 1024), }, { name: "non-legacy short data", // Likley not realistic for most cases in the real world. setup: lz4.NewWriter, dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes. }, { name: "legacy regular", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(1024), }, { name: "legacy larger data", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, { name: "legacy small data", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes.. }, { name: "legacy small data", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(100), // Smaller than pre-read size, 1024 bytes.. }, { name: "regular larger data with fast compression", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.CompressionLevelOption(lz4.Fast)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, { name: "legacy larger data with fast compression", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true), lz4.CompressionLevelOption(lz4.Fast)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, } { t.Run(tt.name, func(t *testing.T) { checkArchiveReaderLZ4(t, tt) }) } } func TestArchiveReaderLZ4SlowCompressed(t *testing.T) { for _, tt := range []archiveReaderLZ4Case{ { name: "regular larger data with medium compression", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.CompressionLevelOption(lz4.Level5)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, { name: "regular larger data with slow compression", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.CompressionLevelOption(lz4.Level9)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, { name: "legacy larger data with medium compression", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true), lz4.CompressionLevelOption(lz4.Level5)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, { name: "legacy larger data with slow compression", setup: func(w io.Writer) *lz4.Writer { lz4w := lz4.NewWriter(w) if err := lz4w.Apply(lz4.LegacyOption(true), lz4.CompressionLevelOption(lz4.Level9)); err != nil { t.Fatal(err) } return lz4w }, dataStr: randomString(5 * 1024), }, } { t.Run(tt.name, func(t *testing.T) { checkArchiveReaderLZ4(t, tt) }) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/buffer.go000066400000000000000000000217231501711764400234520ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "encoding/binary" "errors" "fmt" ) // Marshaler is the interface implemented by an object that can marshal itself // into binary form. // // Marshal appends data to the buffer b. type Marshaler interface { Marshal(l *Lexer) } // Unmarshaler is the interface implemented by an object that can unmarshal a // binary representation of itself. // // Unmarshal Consumes data from the buffer b. type Unmarshaler interface { Unmarshal(l *Lexer) error } // ToBytes marshals m in the given byte order. func ToBytes(m Marshaler, order binary.ByteOrder) []byte { l := NewLexer(NewBuffer(nil), order) m.Marshal(l) return l.Data() } // FromBytes unmarshals b into obj in the given byte order. func FromBytes(obj Unmarshaler, b []byte, order binary.ByteOrder) error { l := NewLexer(NewBuffer(b), order) return obj.Unmarshal(l) } // ToBigEndian marshals m to big endian byte order. func ToBigEndian(m Marshaler) []byte { l := NewBigEndianBuffer(nil) m.Marshal(l) return l.Data() } // FromBigEndian unmarshals b into obj in big endian byte order. func FromBigEndian(obj Unmarshaler, b []byte) error { l := NewBigEndianBuffer(b) return obj.Unmarshal(l) } // ToLittleEndian marshals m to little endian byte order. func ToLittleEndian(m Marshaler) []byte { l := NewLittleEndianBuffer(nil) m.Marshal(l) return l.Data() } // FromLittleEndian unmarshals b into obj in little endian byte order. func FromLittleEndian(obj Unmarshaler, b []byte) error { l := NewLittleEndianBuffer(b) return obj.Unmarshal(l) } // Buffer implements functions to manipulate byte slices in a zero-copy way. type Buffer struct { // data is the underlying data. data []byte // byteCount keeps track of how many bytes have been consumed for // debugging. byteCount int } // NewBuffer Consumes b for marshaling or unmarshaling in the given byte order. func NewBuffer(b []byte) *Buffer { return &Buffer{data: b} } // Preallocate increases the capacity of the buffer by n bytes. func (b *Buffer) Preallocate(n int) { b.data = append(b.data, make([]byte, 0, n)...) } // WriteN appends n bytes to the Buffer and returns a slice pointing to the // newly appended bytes. func (b *Buffer) WriteN(n int) []byte { b.data = append(b.data, make([]byte, n)...) return b.data[len(b.data)-n:] } // ErrBufferTooShort is returned when a caller wants to read more bytes than // are available in the buffer. var ErrBufferTooShort = errors.New("buffer too short") // ReadN consumes n bytes from the Buffer. It returns nil, false if there // aren't enough bytes left. func (b *Buffer) ReadN(n int) ([]byte, error) { if !b.Has(n) { return nil, fmt.Errorf("%w at position %d: have %d bytes, want %d bytes", ErrBufferTooShort, b.byteCount, b.Len(), n) } rval := b.data[:n] b.data = b.data[n:] b.byteCount += n return rval, nil } // Data is unConsumed data remaining in the Buffer. func (b *Buffer) Data() []byte { return b.data } // Has returns true if n bytes are available. func (b *Buffer) Has(n int) bool { return len(b.data) >= n } // Len returns the length of the remaining bytes. func (b *Buffer) Len() int { return len(b.data) } // Cap returns the available capacity. func (b *Buffer) Cap() int { return cap(b.data) } // Lexer is a convenient encoder/decoder for buffers. // // Use: // // func (s *something) Unmarshal(l *Lexer) { // s.Foo = l.Read8() // s.Bar = l.Read8() // s.Baz = l.Read16() // return l.Error() // } type Lexer struct { *Buffer // order is the byte order to write in / read in. order binary.ByteOrder // err err error } // NewLexer returns a new coder for buffers. func NewLexer(b *Buffer, order binary.ByteOrder) *Lexer { return &Lexer{ Buffer: b, order: order, } } // NewLittleEndianBuffer returns a new little endian coder for a new buffer. func NewLittleEndianBuffer(b []byte) *Lexer { return &Lexer{ Buffer: NewBuffer(b), order: binary.LittleEndian, } } // NewBigEndianBuffer returns a new big endian coder for a new buffer. func NewBigEndianBuffer(b []byte) *Lexer { return &Lexer{ Buffer: NewBuffer(b), order: binary.BigEndian, } } // NewNativeEndianBuffer returns a new native endian coder for a new buffer. func NewNativeEndianBuffer(b []byte) *Lexer { return &Lexer{ Buffer: NewBuffer(b), order: binary.NativeEndian, } } // SetError sets the error if no error has previously been set. // // The error can later be retried with Error or FinError methods. func (l *Lexer) SetError(err error) { if l.err == nil { l.err = err } } // Consume returns a slice of the next n bytes from the buffer. // // Consume gives direct access to the underlying data. func (l *Lexer) Consume(n int) []byte { v, err := l.Buffer.ReadN(n) if err != nil { l.SetError(err) return nil } return v } func (l *Lexer) append(n int) []byte { return l.Buffer.WriteN(n) } // Error returns an error if an error occurred reading from the buffer. func (l *Lexer) Error() error { return l.err } // ErrUnreadBytes is returned when there is more data left to read in the buffer. var ErrUnreadBytes = errors.New("buffer contains unread bytes") // FinError returns an error if an error occurred or if there is more data left // to read in the buffer. func (l *Lexer) FinError() error { if l.err != nil { return l.err } if l.Buffer.Len() > 0 { return ErrUnreadBytes } return nil } // Read8 reads a byte from the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Read8() uint8 { v := l.Consume(1) if v == nil { return 0 } return v[0] } // Read16 reads a 16-bit value from the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Read16() uint16 { v := l.Consume(2) if v == nil { return 0 } return l.order.Uint16(v) } // Read32 reads a 32-bit value from the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Read32() uint32 { v := l.Consume(4) if v == nil { return 0 } return l.order.Uint32(v) } // Read64 reads a 64-bit value from the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Read64() uint64 { v := l.Consume(8) if v == nil { return 0 } return l.order.Uint64(v) } // CopyN returns a copy of the next n bytes. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) CopyN(n int) []byte { v := l.Consume(n) if v == nil { return nil } p := make([]byte, n) m := copy(p, v) return p[:m] } // ReadAll Consumes and returns a copy of all remaining bytes in the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) ReadAll() []byte { return l.CopyN(l.Len()) } // ReadBytes reads exactly len(p) values from the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) ReadBytes(p []byte) { copy(p, l.Consume(len(p))) } // Read implements io.Reader.Read. func (l *Lexer) Read(p []byte) (int, error) { v := l.Consume(len(p)) if v == nil { return 0, l.Error() } return copy(p, v), nil } // ReadData reads the binary representation of data from the buffer. // // See binary.Read. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) ReadData(data interface{}) { l.SetError(binary.Read(l, l.order, data)) } // WriteData writes a binary representation of data to the buffer. // // See binary.Write. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) WriteData(data interface{}) { l.SetError(binary.Write(l, l.order, data)) } // Write8 writes a byte to the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Write8(v uint8) { l.append(1)[0] = v } // Write16 writes a 16-bit value to the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Write16(v uint16) { l.order.PutUint16(l.append(2), v) } // Write32 writes a 32-bit value to the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Write32(v uint32) { l.order.PutUint32(l.append(4), v) } // Write64 writes a 64-bit value to the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Write64(v uint64) { l.order.PutUint64(l.append(8), v) } // Append returns a newly appended n-size Buffer to write to. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Append(n int) []byte { return l.append(n) } // WriteBytes writes p to the Buffer. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) WriteBytes(p []byte) { copy(l.append(len(p)), p) } // Write implements io.Writer.Write. // // If an error occurred, Error() will return a non-nil error. func (l *Lexer) Write(p []byte) (int, error) { return copy(l.append(len(p)), p), nil } // Align appends bytes to align the length of the buffer to be divisible by n. func (l *Lexer) Align(n int) { pad := ((l.Len() + n - 1) &^ (n - 1)) - l.Len() l.Append(pad) } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/cached.go000066400000000000000000000041701501711764400234050ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "io" ) // CachingReader is a lazily caching wrapper of an io.Reader. // // The wrapped io.Reader is only read from on demand, not upfront. type CachingReader struct { buf bytes.Buffer r io.Reader pos int eof bool } // NewCachingReader buffers reads from r. // // r is only read from when Read() is called. func NewCachingReader(r io.Reader) *CachingReader { return &CachingReader{ r: r, } } func (cr *CachingReader) read(p []byte) (int, error) { n, err := cr.r.Read(p) cr.buf.Write(p[:n]) if err == io.EOF || (n == 0 && err == nil) { cr.eof = true return n, io.EOF } return n, err } // NewReader returns a new io.Reader that reads cr from offset 0. func (cr *CachingReader) NewReader() io.Reader { return Reader(cr) } // Read reads from cr; implementing io.Reader. // // TODO(chrisko): Decide whether to keep this or only keep NewReader(). func (cr *CachingReader) Read(p []byte) (int, error) { n, err := cr.ReadAt(p, int64(cr.pos)) cr.pos += n return n, err } // ReadAt reads from cr; implementing io.ReaderAt. func (cr *CachingReader) ReadAt(p []byte, off int64) (int, error) { if len(p) == 0 { return 0, nil } end := int(off) + len(p) // Is the caller asking for some uncached bytes? unread := end - cr.buf.Len() if unread > 0 { // Avoiding allocations: use `p` to read more bytes. for unread > 0 { toRead := unread % len(p) if toRead == 0 { toRead = len(p) } m, err := cr.read(p[:toRead]) unread -= m if err == io.EOF { break } if err != nil { return 0, err } } } // If this is true, the entire file was read just to find out, but the // offset is beyond the end of the file. if off > int64(cr.buf.Len()) { return 0, io.EOF } var err error // Did the caller ask for more than was available? // // Note that any io.ReaderAt implementation *must* return an error for // short reads. if cr.eof && unread > 0 { err = io.EOF } return copy(p, cr.buf.Bytes()[off:]), err } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/cached_test.go000066400000000000000000000107361501711764400244510ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "fmt" "io" "testing" ) func TestCachingReaderRead(t *testing.T) { type read struct { // Buffer sizes to call Read with. size int // Buffer value corresponding Read(size) we want. want []byte // Error corresponding to Read(size) we want. err error } for i, tt := range []struct { // Content of the underlying io.Reader. content []byte // Read calls to make in order. reads []read }{ { content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, reads: []read{ { size: 0, }, { size: 1, want: []byte{0x11}, }, { size: 2, want: []byte{0x22, 0x33}, }, { size: 0, }, { size: 3, want: []byte{0x44, 0x55, 0x66}, }, { size: 4, want: []byte{0x77, 0x88, 0x99}, err: io.EOF, }, }, }, { content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, reads: []read{ { size: 11, want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, err: io.EOF, }, }, }, { content: nil, reads: []read{ { size: 2, err: io.EOF, }, { size: 0, }, }, }, { content: []byte{0x33, 0x22, 0x11}, reads: []read{ { size: 3, want: []byte{0x33, 0x22, 0x11}, err: nil, }, { size: 0, }, { size: 1, err: io.EOF, }, }, }, } { t.Run(fmt.Sprintf("Test [%02d]", i), func(t *testing.T) { buf := NewCachingReader(bytes.NewBuffer(tt.content)) for j, r := range tt.reads { p := make([]byte, r.size) m, err := buf.Read(p) if err != r.err { t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, err, r.err) } if m != len(r.want) { t.Errorf("Read#%d(%d) = %d, want %d", j, r.size, m, len(r.want)) } if !bytes.Equal(r.want, p[:m]) { t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, p[:m], r.want) } } }) } } func TestCachingReaderReadAt(t *testing.T) { type readAt struct { // Buffer sizes to call Read with. size int // Offset to read from. off int64 // Buffer value corresponding Read(size) we want. want []byte // Error corresponding to Read(size) we want. err error } for i, tt := range []struct { // Content of the underlying io.Reader. content []byte // Read calls to make in order. reads []readAt }{ { content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, reads: []readAt{ { off: 0, size: 0, }, { off: 0, size: 1, want: []byte{0x11}, }, { off: 1, size: 2, want: []byte{0x22, 0x33}, }, { off: 0, size: 0, }, { off: 3, size: 3, want: []byte{0x44, 0x55, 0x66}, }, { off: 6, size: 4, want: []byte{0x77, 0x88, 0x99}, err: io.EOF, }, { off: 0, size: 9, want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, }, { off: 0, size: 10, want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, err: io.EOF, }, { off: 0, size: 8, want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88}, }, }, }, { content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, reads: []readAt{ { off: 10, size: 10, err: io.EOF, }, { off: 5, size: 4, want: []byte{0x66, 0x77, 0x88, 0x99}, }, }, }, { content: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, reads: []readAt{ { size: 9, want: []byte{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99}, }, { off: 5, size: 4, want: []byte{0x66, 0x77, 0x88, 0x99}, }, { off: 9, size: 1, err: io.EOF, }, }, }, } { t.Run(fmt.Sprintf("Test [%02d]", i), func(t *testing.T) { buf := NewCachingReader(bytes.NewBuffer(tt.content)) for j, r := range tt.reads { p := make([]byte, r.size) m, err := buf.ReadAt(p, r.off) if err != r.err { t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, err, r.err) } if m != len(r.want) { t.Errorf("Read#%d(%d) = %d, want %d", j, r.size, m, len(r.want)) } if !bytes.Equal(r.want, p[:m]) { t.Errorf("Read#%d(%d) = %v, want %v", j, r.size, p[:m], r.want) } } }) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/lazy.go000066400000000000000000000074261501711764400231640ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "fmt" "io" "os" ) // ReadOneByte reads one byte from given io.ReaderAt. func ReadOneByte(r io.ReaderAt) error { buf := make([]byte, 1) n, err := r.ReadAt(buf, 0) if err != nil { return err } if n != 1 { return fmt.Errorf("expected to read 1 byte, but got %d", n) } return nil } // LazyOpener is a lazy io.Reader. // // LazyOpener will use a given open function to derive an io.Reader when Read // is first called on the LazyOpener. type LazyOpener struct { r io.Reader s string err error open func() (io.Reader, error) } // NewLazyOpener returns a lazy io.Reader based on `open`. func NewLazyOpener(filename string, open func() (io.Reader, error)) *LazyOpener { if len(filename) == 0 { return nil } return &LazyOpener{s: filename, open: open} } // Read implements io.Reader.Read lazily. // // If called for the first time, the underlying reader will be obtained and // then used for the first and subsequent calls to Read. func (lr *LazyOpener) Read(p []byte) (int, error) { if lr.r == nil && lr.err == nil { lr.r, lr.err = lr.open() } if lr.err != nil { return 0, lr.err } return lr.r.Read(p) } // String implements fmt.Stringer. func (lr *LazyOpener) String() string { if len(lr.s) > 0 { return lr.s } if lr.r != nil { return fmt.Sprintf("%v", lr.r) } return "unopened mystery file" } // Close implements io.Closer.Close. func (lr *LazyOpener) Close() error { if c, ok := lr.r.(io.Closer); ok { return c.Close() } return nil } // LazyOpenerAt is a lazy io.ReaderAt. // // LazyOpenerAt will use a given open function to derive an io.ReaderAt when // ReadAt is first called. type LazyOpenerAt struct { r io.ReaderAt s string err error limit int64 open func() (io.ReaderAt, error) } // NewLazyFile returns a lazy ReaderAt opened from path. func NewLazyFile(path string) *LazyOpenerAt { if len(path) == 0 { return nil } return NewLazyOpenerAt(path, func() (io.ReaderAt, error) { return os.Open(path) }) } // NewLazyLimitFile returns a lazy ReaderAt opened from path with a limit reader on it. func NewLazyLimitFile(path string, limit int64) *LazyOpenerAt { if len(path) == 0 { return nil } return NewLazyLimitOpenerAt(path, limit, func() (io.ReaderAt, error) { return os.Open(path) }) } // NewLazyOpenerAt returns a lazy io.ReaderAt based on `open`. func NewLazyOpenerAt(filename string, open func() (io.ReaderAt, error)) *LazyOpenerAt { return &LazyOpenerAt{s: filename, open: open, limit: -1} } // NewLazyLimitOpenerAt returns a lazy io.ReaderAt based on `open`. func NewLazyLimitOpenerAt(filename string, limit int64, open func() (io.ReaderAt, error)) *LazyOpenerAt { return &LazyOpenerAt{s: filename, open: open, limit: limit} } // String implements fmt.Stringer. func (loa *LazyOpenerAt) String() string { if len(loa.s) > 0 { return loa.s } if loa.r != nil { return fmt.Sprintf("%v", loa.r) } return "unopened mystery file" } // File returns the backend file of the io.ReaderAt if it // is backed by a os.File. func (loa *LazyOpenerAt) File() *os.File { if f, ok := loa.r.(*os.File); ok { return f } return nil } // ReadAt implements io.ReaderAt.ReadAt. func (loa *LazyOpenerAt) ReadAt(p []byte, off int64) (int, error) { if loa.r == nil && loa.err == nil { loa.r, loa.err = loa.open() } if loa.err != nil { return 0, loa.err } if loa.limit > 0 { if off >= loa.limit { return 0, io.EOF } if int64(len(p)) > loa.limit-off { p = p[0 : loa.limit-off] } } return loa.r.ReadAt(p, off) } // Close implements io.Closer.Close. func (loa *LazyOpenerAt) Close() error { if c, ok := loa.r.(io.Closer); ok { return c.Close() } return nil } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/lazy_test.go000066400000000000000000000063701501711764400242200ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "errors" "fmt" "io" "strings" "testing" ) type mockReader struct { // called is whether Read has been called. called bool // err is the error to return on Read. err error } func (m *mockReader) Read([]byte) (int, error) { m.called = true return 0, m.err } func (m *mockReader) ReadAt([]byte, int64) (int, error) { m.called = true return 0, m.err } func TestLazyOpenerRead(t *testing.T) { for i, tt := range []struct { openErr error reader *mockReader wantCalled bool }{ { openErr: nil, reader: &mockReader{}, wantCalled: true, }, { openErr: io.EOF, reader: nil, wantCalled: false, }, { openErr: nil, reader: &mockReader{ err: io.ErrUnexpectedEOF, }, wantCalled: true, }, } { t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) { var opened bool lr := NewLazyOpener("testname", func() (io.Reader, error) { opened = true return tt.reader, tt.openErr }) _, err := lr.Read([]byte{}) if !opened { t.Fatalf("Read(): Reader was not opened") } if tt.openErr != nil && err != tt.openErr { t.Errorf("Read() = %v, want %v", err, tt.openErr) } if tt.reader != nil { if got, want := tt.reader.called, tt.wantCalled; got != want { t.Errorf("mockReader.Read() called is %v, want %v", got, want) } if tt.reader.err != nil && err != tt.reader.err { t.Errorf("Read() = %v, want %v", err, tt.reader.err) } } }) } } func TestLazyOpenerReadAt(t *testing.T) { for i, tt := range []struct { limit int64 bufSize int openErr error reader io.ReaderAt off int64 want error wantB []byte }{ { limit: -1, bufSize: 10, openErr: nil, reader: &mockReader{}, }, { limit: -1, bufSize: 10, openErr: io.EOF, reader: nil, want: io.EOF, }, { limit: -1, bufSize: 10, openErr: nil, reader: &mockReader{ err: io.ErrUnexpectedEOF, }, want: io.ErrUnexpectedEOF, }, { limit: -1, bufSize: 6, reader: strings.NewReader("foobar"), wantB: []byte("foobar"), }, { limit: -1, off: 3, bufSize: 3, reader: strings.NewReader("foobar"), wantB: []byte("bar"), }, { limit: 5, off: 3, bufSize: 3, reader: strings.NewReader("foobar"), wantB: []byte("ba"), }, { limit: 2, bufSize: 2, reader: strings.NewReader("foobar"), wantB: []byte("fo"), }, { limit: 2, off: 2, reader: strings.NewReader("foobar"), want: io.EOF, }, } { t.Run(fmt.Sprintf("Test #%02d", i), func(t *testing.T) { var opened bool lr := NewLazyLimitOpenerAt("", tt.limit, func() (io.ReaderAt, error) { opened = true return tt.reader, tt.openErr }) b := make([]byte, tt.bufSize) n, err := lr.ReadAt(b, tt.off) if !opened { t.Fatalf("Read(): Reader was not opened") } if !errors.Is(tt.want, err) { t.Errorf("Read() = %v, want %v", err, tt.want) } if err == nil { if !bytes.Equal(b[:n], tt.wantB) { t.Errorf("Read() = %s, want %s", b[:n], tt.wantB) } } }) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/null.go000066400000000000000000000034541501711764400231540ustar00rootroot00000000000000// Copyright 2012-2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Discard implementation copied from the Go project: // https://golang.org/src/io/ioutil/ioutil.go. // Copyright 2009 The Go Authors. All rights reserved. package uio import ( "io" "sync" ) // devNull implements an io.Writer and io.ReaderFrom that discards any writes. type devNull struct{} // devNull implements ReaderFrom as an optimization so io.Copy to // ioutil.Discard can avoid doing unnecessary work. var _ io.ReaderFrom = devNull{} // Write is an io.Writer.Write that discards data. func (devNull) Write(p []byte) (int, error) { return len(p), nil } // Name is like os.File.Name() and returns "null". func (devNull) Name() string { return "null" } // WriteString implements io.StringWriter and discards given data. func (devNull) WriteString(s string) (int, error) { return len(s), nil } var blackHolePool = sync.Pool{ New: func() interface{} { b := make([]byte, 8192) return &b }, } // ReadFrom implements io.ReaderFrom and discards data being read. func (devNull) ReadFrom(r io.Reader) (n int64, err error) { bufp := blackHolePool.Get().(*[]byte) var readSize int for { readSize, err = r.Read(*bufp) n += int64(readSize) if err != nil { blackHolePool.Put(bufp) if err == io.EOF { return n, nil } return } } } // Close does nothing. func (devNull) Close() error { return nil } // WriteNameCloser is the interface that groups Write, Close, and Name methods. type WriteNameCloser interface { io.Writer io.Closer Name() string } // Discard is a WriteNameCloser on which all Write and Close calls succeed // without doing anything, and the Name call returns "null". var Discard WriteNameCloser = devNull{} golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/progress.go000066400000000000000000000017571501711764400240520ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "io" "strings" ) // ProgressReadCloser implements io.ReadCloser and prints Symbol to W after every // Interval bytes passes through RC. type ProgressReadCloser struct { RC io.ReadCloser Symbol string Interval int W io.Writer counter int written bool } // Read implements io.Reader for ProgressReadCloser. func (rc *ProgressReadCloser) Read(p []byte) (n int, err error) { defer func() { numSymbols := (rc.counter%rc.Interval + n) / rc.Interval _, _ = rc.W.Write([]byte(strings.Repeat(rc.Symbol, numSymbols))) rc.counter += n rc.written = (rc.written || numSymbols > 0) if err == io.EOF && rc.written { _, _ = rc.W.Write([]byte("\n")) } }() return rc.RC.Read(p) } // Close implements io.Closer for ProgressReader. func (rc *ProgressReadCloser) Close() error { return rc.RC.Close() } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/progress_test.go000066400000000000000000000030351501711764400251000ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "io" "testing" ) func TestProgressReadCloser(t *testing.T) { input := io.NopCloser(bytes.NewBufferString("01234567890123456789")) stdout := &bytes.Buffer{} prc := ProgressReadCloser{ RC: input, Symbol: "#", Interval: 4, W: stdout, } // Read one byte at a time. output := make([]byte, 1) _, _ = prc.Read(output) _, _ = prc.Read(output) _, _ = prc.Read(output) if len(stdout.Bytes()) != 0 { t.Errorf("found %q, but expected no bytes to be written", stdout) } _, _ = prc.Read(output) if stdout.String() != "#" { t.Errorf("found %q, expected %q to be written", stdout.String(), "#") } // Read 9 bytes all at once. output = make([]byte, 9) _, _ = prc.Read(output) if stdout.String() != "###" { t.Errorf("found %q, expected %q to be written", stdout.String(), "###") } if string(output) != "456789012" { t.Errorf("found %q, expected %q to be written", string(output), "456789012") } // Read until EOF output, err := io.ReadAll(&prc) if err != nil { t.Errorf("got %v, expected nil error", err) } if stdout.String() != "#####\n" { t.Errorf("found %q, expected %q to be written", stdout.String(), "#####\n") } if string(output) != "3456789" { t.Errorf("found %q, expected %q to be written", string(output), "3456789") } err = prc.Close() if err != nil { t.Errorf("got %v, expected nil error", err) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/reader.go000066400000000000000000000030141501711764400234340ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "bytes" "io" "math" "os" "reflect" ) type inMemReaderAt interface { Bytes() []byte } // ReadAll reads everything that r contains. // // Callers *must* not modify bytes in the returned byte slice. // // If r is an in-memory representation, ReadAll will attempt to return a // pointer to those bytes directly. func ReadAll(r io.ReaderAt) ([]byte, error) { if imra, ok := r.(inMemReaderAt); ok { return imra.Bytes(), nil } return io.ReadAll(Reader(r)) } // Reader generates a Reader from a ReaderAt. func Reader(r io.ReaderAt) io.Reader { return io.NewSectionReader(r, 0, math.MaxInt64) } // ReaderAtEqual compares the contents of r1 and r2. func ReaderAtEqual(r1, r2 io.ReaderAt) bool { var c, d []byte var r1err, r2err error if r1 != nil { c, r1err = ReadAll(r1) } if r2 != nil { d, r2err = ReadAll(r2) } return bytes.Equal(c, d) && reflect.DeepEqual(r1err, r2err) } // ReadIntoFile reads all from io.Reader into the file at given path. // // If the file at given path does not exist, a new file will be created. // If the file exists at the given path, but not empty, it will be truncated. func ReadIntoFile(r io.Reader, p string) error { f, err := os.OpenFile(p, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0o644) if err != nil { return err } defer f.Close() _, err = io.Copy(f, r) if err != nil { return err } return f.Close() } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/reader_test.go000066400000000000000000000030531501711764400244760ustar00rootroot00000000000000// Copyright 2021 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package uio import ( "os" "path/filepath" "strings" "testing" ) func readAndCheck(t *testing.T, want, tmpfileP string) { t.Helper() r := strings.NewReader(want) if err := ReadIntoFile(r, tmpfileP); err != nil { t.Errorf("ReadIntoFile(%v, %s) = %v, want no error", r, tmpfileP, err) } got, err := os.ReadFile(tmpfileP) if err != nil { t.Fatalf("os.ReadFile(%s) = %v, want no error", tmpfileP, err) } if want != string(got) { t.Errorf("got: %v, want %s", string(got), want) } } func TestReadIntoFile(t *testing.T) { want := "I am the wanted" dir := t.TempDir() // Write to a file already exist. p := filepath.Join(dir, "uio-out") // Expect net effect to be creating a new empty file: "uio-out". f, err := os.OpenFile(p, os.O_RDONLY|os.O_CREATE|os.O_TRUNC, 0o755) if err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } readAndCheck(t, want, f.Name()) // Write to a file that does not exist. p = filepath.Join(dir, "uio-out-not-existing") readAndCheck(t, want, p) // Write to an existing file that has pre-existing content. p = filepath.Join(dir, "uio-out-prexist-content") f, err = os.OpenFile(p, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o755) if err != nil { t.Fatal(err) } if _, err := f.Write([]byte("temporary file's content")); err != nil { t.Fatal(err) } if err := f.Close(); err != nil { t.Fatal(err) } readAndCheck(t, want, p) } golang-github-u-root-uio-0.0~git20240224.d2acac8/uio/uio.go000066400000000000000000000005611501711764400227720ustar00rootroot00000000000000// Copyright 2018 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package uio unifies commonly used io utilities for u-root. // // uio's most used feature is the Buffer/Lexer combination to parse binary data // of arbitrary endianness into data structures. package uio golang-github-u-root-uio-0.0~git20240224.d2acac8/ulog/000077500000000000000000000000001501711764400220175ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/ulog/log.go000066400000000000000000000016231501711764400231310ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package ulog exposes logging via a Go interface. // // ulog has three implementations of the Logger interface: a Go standard // library "log" package Logger and a test Logger that logs via a test's // testing.TB.Logf. To use the test logger import "ulog/ulogtest". package ulog import "log" // Logger is a log receptacle. // // It puts your information somewhere for safekeeping. type Logger interface { Printf(format string, v ...interface{}) } // Log is a Logger that prints to the log package's default logger. var Log Logger = log.Default() type emptyLogger struct{} // Printf implements Logger.Printf. func (emptyLogger) Printf(format string, v ...interface{}) {} // Null is a logger that prints nothing. var Null Logger = emptyLogger{} golang-github-u-root-uio-0.0~git20240224.d2acac8/ulog/log_test.go000066400000000000000000000007471501711764400241760ustar00rootroot00000000000000// Copyright 2023 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ulog import ( "log" "strings" "testing" ) func TestDefault(t *testing.T) { var b strings.Builder log.SetPrefix("[foobar] ") log.SetOutput(&b) log.SetFlags(0) Log.Printf("Some output") want := "[foobar] Some output\n" if got := b.String(); got != want { t.Errorf("log is %q, want %q", got, want) } } golang-github-u-root-uio-0.0~git20240224.d2acac8/ulog/ulogtest/000077500000000000000000000000001501711764400236655ustar00rootroot00000000000000golang-github-u-root-uio-0.0~git20240224.d2acac8/ulog/ulogtest/log.go000066400000000000000000000010631501711764400247750ustar00rootroot00000000000000// Copyright 2019 the u-root Authors. All rights reserved // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. // Package ulogtest implement the Logger interface via a test's testing.TB.Logf. package ulogtest import ( "testing" ) // Logger is a Logger implementation that logs to testing.TB.Logf. type Logger struct { TB testing.TB } // Printf formats according to the format specifier and prints to a unit test's log. func (tl Logger) Printf(format string, v ...interface{}) { tl.TB.Logf(format, v...) }