commit 1ec6d72a8f6697b4257d3d9cac1ba13d3a1f68c3 Author: yige Date: Wed Aug 7 20:40:32 2019 +0800 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d90025 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +# Mac +.DS_Store diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..312b83b --- /dev/null +++ b/.travis.yml @@ -0,0 +1,29 @@ +language: go +sudo: required + +matrix: + allow_failures: + - go: master + include: + # Supported versions of Go: https://golang.org/dl/ + - go: '1.11.x' + - go: '1.12.x' + - go: master + +go_import_path: github.com/yi-ge/unxz + +cache: + directories: + - $GOPATH/pkg + +before_install: + - echo "TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}" + +install: + - go mod install + +script: + - make test COVERAGE_DIR=/tmp/coverage + +after_success: + - goveralls -service=travis-ci -coverprofile /tmp/coverage/combined.txt diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..a5ae341 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Yige + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/REAMDE.md b/REAMDE.md new file mode 100644 index 0000000..8b4faaa --- /dev/null +++ b/REAMDE.md @@ -0,0 +1,3 @@ +# Unxz + +[![GoDoc](https://godoc.org/github.com/yi-ge/unxz?status.svg)](https://godoc.org/github.com/yi-ge/unxz) diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..904646a --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.com/yi-ge/unxz + +go 1.12 + +require github.com/ulikunitz/xz v0.5.6 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..5ce8963 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= diff --git a/test/a.txt b/test/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/b.txt b/test/b.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/test/b.txt @@ -0,0 +1 @@ +1 diff --git a/test/c.txt b/test/c.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/test/c.txt @@ -0,0 +1 @@ +2 diff --git a/test/d.txt b/test/d.txt new file mode 120000 index 0000000..3451cc9 --- /dev/null +++ b/test/d.txt @@ -0,0 +1 @@ +dir/d.txt \ No newline at end of file diff --git a/test/dir/c.txt b/test/dir/c.txt new file mode 100644 index 0000000..0cfbf08 --- /dev/null +++ b/test/dir/c.txt @@ -0,0 +1 @@ +2 diff --git a/test/dir/d.txt b/test/dir/d.txt new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/test/dir/d.txt @@ -0,0 +1 @@ +3 diff --git a/test/dir/dir/e.txt b/test/dir/dir/e.txt new file mode 100644 index 0000000..bf0d87a --- /dev/null +++ b/test/dir/dir/e.txt @@ -0,0 +1 @@ +4 \ No newline at end of file diff --git a/test/dir/dir/f.txt b/test/dir/dir/f.txt new file mode 120000 index 0000000..7064d14 --- /dev/null +++ b/test/dir/dir/f.txt @@ -0,0 +1 @@ +../f.txt \ No newline at end of file diff --git a/test/dir/e.txt b/test/dir/e.txt new file mode 100644 index 0000000..bf0d87a --- /dev/null +++ b/test/dir/e.txt @@ -0,0 +1 @@ +4 \ No newline at end of file diff --git a/test/dir/f.txt b/test/dir/f.txt new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/test/dir/f.txt @@ -0,0 +1 @@ +5 \ No newline at end of file diff --git a/test/out/a.txt b/test/out/a.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/out/b.txt b/test/out/b.txt new file mode 100644 index 0000000..d00491f --- /dev/null +++ b/test/out/b.txt @@ -0,0 +1 @@ +1 diff --git a/test/out/c.txt b/test/out/c.txt new file mode 100644 index 0000000..33a317a --- /dev/null +++ b/test/out/c.txt @@ -0,0 +1,2 @@ +2 +123 \ No newline at end of file diff --git a/test/out/d.txt b/test/out/d.txt new file mode 120000 index 0000000..3451cc9 --- /dev/null +++ b/test/out/d.txt @@ -0,0 +1 @@ +dir/d.txt \ No newline at end of file diff --git a/test/out/dir/c.txt b/test/out/dir/c.txt new file mode 100644 index 0000000..33a317a --- /dev/null +++ b/test/out/dir/c.txt @@ -0,0 +1,2 @@ +2 +123 \ No newline at end of file diff --git a/test/out/dir/d.txt b/test/out/dir/d.txt new file mode 100644 index 0000000..00750ed --- /dev/null +++ b/test/out/dir/d.txt @@ -0,0 +1 @@ +3 diff --git a/test/out/dir/dir/e.txt b/test/out/dir/dir/e.txt new file mode 100644 index 0000000..9f4e85e --- /dev/null +++ b/test/out/dir/dir/e.txt @@ -0,0 +1 @@ +4abcde \ No newline at end of file diff --git a/test/out/dir/dir/f.txt b/test/out/dir/dir/f.txt new file mode 120000 index 0000000..7064d14 --- /dev/null +++ b/test/out/dir/dir/f.txt @@ -0,0 +1 @@ +../f.txt \ No newline at end of file diff --git a/test/out/dir/e.txt b/test/out/dir/e.txt new file mode 100644 index 0000000..9f4e85e --- /dev/null +++ b/test/out/dir/e.txt @@ -0,0 +1 @@ +4abcde \ No newline at end of file diff --git a/test/out/dir/f.txt b/test/out/dir/f.txt new file mode 100644 index 0000000..7813681 --- /dev/null +++ b/test/out/dir/f.txt @@ -0,0 +1 @@ +5 \ No newline at end of file diff --git a/test/t.tar.xz b/test/t.tar.xz new file mode 100644 index 0000000..d37237c Binary files /dev/null and b/test/t.tar.xz differ diff --git a/unxz.go b/unxz.go new file mode 100644 index 0000000..d0c4be7 --- /dev/null +++ b/unxz.go @@ -0,0 +1,170 @@ +package unxz + +import ( + "archive/tar" + "bufio" + "errors" + "io" + "os" + "path/filepath" + "runtime" + "strings" + + "github.com/ulikunitz/xz" +) + +var ( + linkFiles = []linkFile{} + errHeaderType = errors.New("unxz: invalid tar header type") +) + +// Unxz - Unxz struct. +type Unxz struct { + Src string + Dest string +} + +type linkFile struct { + filePath string + targetPath string +} + +// New - Create a new Unxz. +func New(src string, dest string) Unxz { + return Unxz{src, dest} +} + +// Extract - Extract *.tar.xz package. +func (uz Unxz) Extract() error { + destPath := uz.Dest + + xzFile, err := os.Open(uz.Src) + if err != nil { + return err + } + defer xzFile.Close() + + r := bufio.NewReader(xzFile) + xr, err := xz.NewReader(r) + + if err != nil { + return err + } + + tr := tar.NewReader(xr) + + os.Mkdir(destPath, 0755) + + for { + hdr, err := tr.Next() + + if err == io.EOF { + break + } else if err != nil { + return err + } + + err = untarFile(tr, hdr, destPath, true) + if err != nil { + return err + } + } + + for _, item := range linkFiles { + err = writeLink(item.filePath, item.targetPath) + if err != nil { + return err + } + } + + return nil +} + +func writeFile(filePath string, in io.Reader, fm os.FileMode) error { + err := os.MkdirAll(filepath.Dir(filePath), 0755) + if err != nil { + return err + } + + out, err := os.Create(filePath) + if err != nil { + return err + } + defer out.Close() + + err = out.Chmod(fm) + if err != nil && runtime.GOOS != "windows" { + return err + } + + _, err = io.Copy(out, in) + if err != nil { + return err + } + + return nil +} + +func writeSymbolicLink(filePath string, targetPath string) error { + err := os.MkdirAll(filepath.Dir(filePath), 0755) + if err != nil { + return err + } + + err = os.Symlink(targetPath, filePath) + if err != nil { + return err + } + + return nil +} + +func writeLink(filePath string, targetPath string) error { + err := os.MkdirAll(filepath.Dir(filePath), 0755) + if err != nil { + return err + } + + err = os.Link(targetPath, filePath) + if err != nil { + return err + } + + return nil +} + +func untarFile(tr *tar.Reader, header *tar.Header, destPath string, stripInnerFolder bool) error { + fileName := header.Name + filePath := filepath.Join(destPath, fileName) + + if stripInnerFolder { + slashIndex := strings.Index(fileName, "/") + if slashIndex != -1 { + fileName = fileName[slashIndex+1:] + } + } + + switch header.Typeflag { + case tar.TypeDir: + err := os.MkdirAll(filePath, 0755) + if err != nil { + return err + } + + return nil + case tar.TypeReg, tar.TypeRegA: + return writeFile(filePath, tr, header.FileInfo().Mode()) + case tar.TypeLink: + linkFiles = append(linkFiles, linkFile{ + filePath, + filepath.Join(destPath, header.Linkname), + }) + return nil + case tar.TypeSymlink: + return writeSymbolicLink(filePath, header.Linkname) + case tar.TypeXGlobalHeader: + return nil + default: + return errHeaderType + } +} diff --git a/unxz_test.go b/unxz_test.go new file mode 100644 index 0000000..8d24707 --- /dev/null +++ b/unxz_test.go @@ -0,0 +1,227 @@ +package unxz + +import ( + "io/ioutil" + "os" + "path" + "path/filepath" + "testing" +) + +func currentDir() string { + dir, err := os.Getwd() + if err != nil { + return "" + } + return dir +} + +func TestUnxz(t *testing.T) { + filePath := filepath.FromSlash(path.Join(currentDir(), "./test/t.tar.xz")) + outDir := filepath.FromSlash(path.Join(currentDir(), "./test/out") + "/") + + os.RemoveAll(outDir) + + unxz := New(filePath, outDir) + err := unxz.Extract() + if err != nil { + t.Fatal(err) + } + + aFile, err := os.Open(path.Join(currentDir(), "./test/a.txt")) + if err != nil { + t.Fatal(err) + } + defer aFile.Close() + + aFileContent, err := ioutil.ReadAll(aFile) + + aOutFile, err := os.Open(path.Join(currentDir(), "./test/out/a.txt")) + if err != nil { + t.Fatal(err) + } + defer aOutFile.Close() + + aOutFileContent, err := ioutil.ReadAll(aOutFile) + + if string(aFileContent) != string(aOutFileContent) { + t.Fatal("Unxz file content error.") + } + + if string(aFileContent) != "" { + t.Fatal("Unxz file content error.") + } + + bFile, err := os.Open(path.Join(currentDir(), "./test/b.txt")) + if err != nil { + t.Fatal(err) + } + defer bFile.Close() + + bFileContent, err := ioutil.ReadAll(bFile) + + bOutFile, err := os.Open(path.Join(currentDir(), "./test/out/b.txt")) + if err != nil { + t.Fatal(err) + } + defer bOutFile.Close() + + bOutFileContent, err := ioutil.ReadAll(bOutFile) + + if string(bFileContent) != string(bOutFileContent) { + t.Log(string(bFileContent)) + t.Log(string(bOutFileContent)) + t.Fatal("Unxz file content error.") + } + + cOutFile, err := os.OpenFile(path.Join(currentDir(), "./test/out/c.txt"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + t.Fatal(err) + } + defer cOutFile.Close() + + cOutFileContent, err := ioutil.ReadAll(cOutFile) + + cOutLinkFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/c.txt")) + if err != nil { + t.Fatal(err) + } + defer cOutLinkFile.Close() + + cOutLinkFileContent, err := ioutil.ReadAll(cOutLinkFile) + + if string(cOutFileContent) != string(cOutLinkFileContent) { + t.Fatal("Unxz file content error or link file content error.") + } + + t.Log("Old c.txt content:", string(cOutFileContent)) + t.Log("Change c.txt content.") + _, err = cOutFile.WriteString("123") + if err != nil { + t.Fatal(err) + } + + cOutFileNew, err := os.Open(path.Join(currentDir(), "./test/out/c.txt")) + if err != nil { + t.Fatal(err) + } + defer cOutFileNew.Close() + + cOutFileContentNew, err := ioutil.ReadAll(cOutFileNew) + t.Log("New c.txt content:", string(cOutFileContentNew)) + + cOutLinkFileNew, err := os.Open(path.Join(currentDir(), "./test/out/dir/c.txt")) + if err != nil { + t.Fatal(err) + } + defer cOutLinkFileNew.Close() + + cOutLinkFileContentNew, err := ioutil.ReadAll(cOutLinkFileNew) + + if string(cOutFileContentNew) != string(cOutLinkFileContentNew) { + t.Fatal("Unxz file link error.") + } + + // test d + targetURL, err := filepath.EvalSymlinks(path.Join(currentDir(), "./test/out/d.txt")) + + if targetURL != path.Join(currentDir(), "./test/out/dir/d.txt") { + t.Fatal("Unxz file Symlink error.") + } + + dOutSymlinkFile, err := os.Open(path.Join(currentDir(), "./test/out/d.txt")) + if err != nil { + t.Fatal(err) + } + defer dOutSymlinkFile.Close() + + dOutSymlinkFileContent, err := ioutil.ReadAll(dOutSymlinkFile) + + dOutFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/d.txt")) + if err != nil { + t.Fatal(err) + } + defer dOutFile.Close() + + dOutFileContent, err := ioutil.ReadAll(dOutFile) + + if string(dOutSymlinkFileContent) != string(dOutFileContent) { + t.Fatal("The content of symlink file is not equal to that of target file") + } + + eOutFile, err := os.OpenFile(path.Join(currentDir(), "./test/out/dir/dir/e.txt"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) + if err != nil { + t.Fatal(err) + } + defer eOutFile.Close() + + eOutFileContent, err := ioutil.ReadAll(eOutFile) + + eOutLinkFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/e.txt")) + if err != nil { + t.Fatal(err) + } + defer eOutLinkFile.Close() + + eOutLinkFileContent, err := ioutil.ReadAll(eOutLinkFile) + + if string(eOutFileContent) != string(eOutLinkFileContent) { + t.Fatal("Unxz file content error or link file content error.") + } + + t.Log("Old dir/dir/e.txt content:", string(eOutFileContent)) + t.Log("Change dir/dir/e.txt content.") + _, err = eOutFile.WriteString("abcde") + if err != nil { + t.Fatal(err) + } + + eOutFileNew, err := os.Open(path.Join(currentDir(), "./test/out/dir/dir/e.txt")) + if err != nil { + t.Fatal(err) + } + defer eOutFileNew.Close() + + eOutFileContentNew, err := ioutil.ReadAll(eOutFileNew) + t.Log("New dir/dir/e.txt content:", string(eOutFileContentNew)) + + eOutLinkFileNew, err := os.Open(path.Join(currentDir(), "./test/out/dir/e.txt")) + if err != nil { + t.Fatal(err) + } + defer eOutLinkFileNew.Close() + + eOutLinkFileContentNew, err := ioutil.ReadAll(eOutLinkFileNew) + + if string(eOutFileContentNew) != string(eOutLinkFileContentNew) { + t.Fatal("Unxz file link error.") + } + + // test f + inDirTargetURL, err := filepath.EvalSymlinks(path.Join(currentDir(), "./test/out/dir/dir/f.txt")) + t.Log("F outSymlinkFile link:", inDirTargetURL) + + if inDirTargetURL != path.Join(currentDir(), "./test/out/dir/f.txt") { + t.Fatal("Unxz file Symlink error.") + } + + fOutSymlinkFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/dir/f.txt")) + if err != nil { + t.Fatal(err) + } + defer fOutSymlinkFile.Close() + + fOutSymlinkFileContent, err := ioutil.ReadAll(fOutSymlinkFile) + + fOutFile, err := os.Open(path.Join(currentDir(), "./test/out/dir/f.txt")) + if err != nil { + t.Fatal(err) + } + defer fOutFile.Close() + + fOutFileContent, err := ioutil.ReadAll(fOutFile) + + if string(fOutSymlinkFileContent) != string(fOutFileContent) { + t.Fatal("The content of symlink file is not equal to that of target file") + } +}