Add cruntar - untar for ChromeOS.
This commit is contained in:
parent
1df0350fc7
commit
f44bbc9eca
|
@ -19,6 +19,8 @@ Contents:
|
|||
the time to expiry and checking for revocations.
|
||||
clustersh/ Run commands or transfer files across multiple
|
||||
servers via SSH.
|
||||
cruntar/ Untar an archive with hard links, copying instead of
|
||||
linking.
|
||||
csrpubdump/ Dump the public key from an X.509 certificate request.
|
||||
fragment/ Print a fragment of a file.
|
||||
jlp/ JSON linter/prettifier.
|
||||
|
@ -47,7 +49,7 @@ Contents:
|
|||
testio/ Various I/O utilities useful during testing.
|
||||
testutil/ Various utility functions useful during testing.
|
||||
|
||||
|
||||
|
||||
Each program should have a small README in the directory with more
|
||||
information.
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
ChromeOS untar
|
||||
|
||||
This is a tool that is intended to support untarring on SquashFS file
|
||||
systems. In particular, every time it encounters a hard link, it
|
||||
will just create a copy of the file.
|
||||
|
||||
Usage: cruntar [-jmvpz] archive [dest]
|
||||
|
||||
Flags:
|
||||
-a Shortcut for -m -p: preserve owners and file mode.
|
||||
-j The archive is compressed with bzip2.
|
||||
-m Preserve file modes.
|
||||
-p Preserve ownership.
|
||||
-v Print the name of each file as it is being processed.
|
||||
-z The archive is compressed with gzip.
|
||||
|
||||
I wrote this after running into problems with untarring the
|
||||
gcc-arm-eabi-none toolchain. The shared storage in Termux under
|
||||
ChromeOS doesn't support hard links, so I opted to just make a copy
|
||||
rather than dealing with links and whatnot.
|
|
@ -0,0 +1,265 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/bzip2"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"flag"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/kisom/goutils/die"
|
||||
)
|
||||
|
||||
var (
|
||||
preserveOwners bool
|
||||
preserveMode bool
|
||||
verbose bool
|
||||
)
|
||||
|
||||
func setupFile(hdr *tar.Header, file *os.File) error {
|
||||
if preserveMode {
|
||||
if verbose {
|
||||
fmt.Printf("\tchmod %0#o\n", hdr.Mode)
|
||||
}
|
||||
err := file.Chmod(os.FileMode(hdr.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if preserveOwners {
|
||||
fmt.Printf("\tchown %d:%d\n", hdr.Uid, hdr.Gid)
|
||||
err := file.Chown(hdr.Uid, hdr.Gid)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func linkTarget(target, top string) string {
|
||||
if filepath.IsAbs(target) {
|
||||
return target
|
||||
}
|
||||
|
||||
return filepath.Clean(filepath.Join(target, top))
|
||||
}
|
||||
|
||||
func processFile(tfr *tar.Reader, hdr *tar.Header, top string) error {
|
||||
if verbose {
|
||||
fmt.Println(hdr.Name)
|
||||
}
|
||||
filePath := filepath.Clean(filepath.Join(top, hdr.Name))
|
||||
switch hdr.Typeflag {
|
||||
case tar.TypeReg, tar.TypeRegA:
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(file, tfr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setupFile(hdr, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeLink:
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
source, err := os.Open(hdr.Linkname)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = io.Copy(file, source)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = setupFile(hdr, file)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeSymlink:
|
||||
err := os.Symlink(linkTarget(hdr.Linkname, top), filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case tar.TypeDir:
|
||||
err := os.MkdirAll(filePath, os.FileMode(hdr.Mode))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var compression = map[string]bool{
|
||||
"gzip": false,
|
||||
"bzip2": false,
|
||||
}
|
||||
|
||||
type bzipCloser struct {
|
||||
r io.Reader
|
||||
}
|
||||
|
||||
func (brc *bzipCloser) Read(p []byte) (int, error) {
|
||||
return brc.r.Read(p)
|
||||
}
|
||||
|
||||
func (brc *bzipCloser) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newBzipCloser(r io.ReadCloser) (io.ReadCloser, error) {
|
||||
br := bzip2.NewReader(r)
|
||||
return &bzipCloser{r: br}, nil
|
||||
}
|
||||
|
||||
var compressFuncs = map[string]func(io.ReadCloser) (io.ReadCloser, error){
|
||||
"gzip": func(r io.ReadCloser) (io.ReadCloser, error) { return gzip.NewReader(r) },
|
||||
"bzip2": newBzipCloser,
|
||||
}
|
||||
|
||||
func verifyCompression() bool {
|
||||
var compressed bool
|
||||
for _, v := range compression {
|
||||
if compressed && v {
|
||||
return false
|
||||
}
|
||||
compressed = compressed || v
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getReader(r io.ReadCloser) (io.ReadCloser, error) {
|
||||
for c, v := range compression {
|
||||
if v {
|
||||
return compressFuncs[c](r)
|
||||
}
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func openArchive(path string) (io.ReadCloser, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
r, err := getReader(file)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
var compressFlags struct {
|
||||
z bool
|
||||
j bool
|
||||
}
|
||||
|
||||
func parseCompressFlags() error {
|
||||
if compressFlags.z {
|
||||
compression["gzip"] = true
|
||||
}
|
||||
|
||||
if compressFlags.j {
|
||||
compression["bzip2"] = true
|
||||
}
|
||||
|
||||
if !verifyCompression() {
|
||||
return errors.New("multiple compression formats specified")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func usage(w io.Writer) {
|
||||
fmt.Fprintf(w, `ChromeOS untar
|
||||
|
||||
This is a tool that is intended to support untarring on SquashFS file
|
||||
systems. In particular, every time it encounters a hard link, it
|
||||
will just create a copy of the file.
|
||||
|
||||
Usage: cruntar [-jmvpz] archive [dest]
|
||||
|
||||
Flags:
|
||||
-a Shortcut for -m -p: preserve owners and file mode.
|
||||
-j The archive is compressed with bzip2.
|
||||
-m Preserve file modes.
|
||||
-p Preserve ownership.
|
||||
-v Print the name of each file as it is being processed.
|
||||
-z The archive is compressed with gzip.
|
||||
`)
|
||||
}
|
||||
|
||||
func init() {
|
||||
flag.Usage = func() { usage(os.Stderr) }
|
||||
}
|
||||
|
||||
func main() {
|
||||
var archive, help bool
|
||||
flag.BoolVar(&archive, "a", false, "Shortcut for -m -p: preserve owners and file mode.")
|
||||
flag.BoolVar(&help, "h", false, "print a help message")
|
||||
flag.BoolVar(&compressFlags.j, "j", false, "bzip2 compression")
|
||||
flag.BoolVar(&preserveMode, "m", false, "preserve file modes")
|
||||
flag.BoolVar(&preserveOwners, "p", false, "preserve ownership")
|
||||
flag.BoolVar(&verbose, "v", false, "verbose mode")
|
||||
flag.BoolVar(&compressFlags.z, "z", false, "gzip compression")
|
||||
flag.Parse()
|
||||
|
||||
if help {
|
||||
usage(os.Stdout)
|
||||
os.Exit(0)
|
||||
}
|
||||
|
||||
if archive {
|
||||
preserveMode = true
|
||||
preserveOwners = true
|
||||
}
|
||||
|
||||
err := parseCompressFlags()
|
||||
die.If(err)
|
||||
|
||||
if flag.NArg() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
top := "./"
|
||||
if flag.NArg() > 1 {
|
||||
top = flag.Arg(1)
|
||||
}
|
||||
|
||||
r, err := openArchive(flag.Arg(0))
|
||||
die.If(err)
|
||||
|
||||
tfr := tar.NewReader(r)
|
||||
for {
|
||||
hdr, err := tfr.Next()
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
die.If(err)
|
||||
|
||||
err = processFile(tfr, hdr, top)
|
||||
die.If(err)
|
||||
|
||||
}
|
||||
|
||||
r.Close()
|
||||
}
|
Loading…
Reference in New Issue