This commit is contained in:
yi-ge 2019-08-07 20:40:32 +08:00
commit 1ec6d72a8f
29 changed files with 479 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
# Mac
.DS_Store

29
.travis.yml Normal file
View File

@ -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

21
LICENSE Normal file
View File

@ -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.

3
REAMDE.md Normal file
View File

@ -0,0 +1,3 @@
# Unxz
[![GoDoc](https://godoc.org/github.com/yi-ge/unxz?status.svg)](https://godoc.org/github.com/yi-ge/unxz)

5
go.mod Normal file
View File

@ -0,0 +1,5 @@
module github.com/yi-ge/unxz
go 1.12
require github.com/ulikunitz/xz v0.5.6

2
go.sum Normal file
View File

@ -0,0 +1,2 @@
github.com/ulikunitz/xz v0.5.6 h1:jGHAfXawEGZQ3blwU5wnWKQJvAraT7Ftq9EXjnXYgt8=
github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8=

0
test/a.txt Normal file
View File

1
test/b.txt Normal file
View File

@ -0,0 +1 @@
1

1
test/c.txt Normal file
View File

@ -0,0 +1 @@
2

1
test/d.txt Symbolic link
View File

@ -0,0 +1 @@
dir/d.txt

1
test/dir/c.txt Normal file
View File

@ -0,0 +1 @@
2

1
test/dir/d.txt Normal file
View File

@ -0,0 +1 @@
3

1
test/dir/dir/e.txt Normal file
View File

@ -0,0 +1 @@
4

1
test/dir/dir/f.txt Symbolic link
View File

@ -0,0 +1 @@
../f.txt

1
test/dir/e.txt Normal file
View File

@ -0,0 +1 @@
4

1
test/dir/f.txt Normal file
View File

@ -0,0 +1 @@
5

0
test/out/a.txt Normal file
View File

1
test/out/b.txt Normal file
View File

@ -0,0 +1 @@
1

2
test/out/c.txt Normal file
View File

@ -0,0 +1,2 @@
2
123

1
test/out/d.txt Symbolic link
View File

@ -0,0 +1 @@
dir/d.txt

2
test/out/dir/c.txt Normal file
View File

@ -0,0 +1,2 @@
2
123

1
test/out/dir/d.txt Normal file
View File

@ -0,0 +1 @@
3

1
test/out/dir/dir/e.txt Normal file
View File

@ -0,0 +1 @@
4abcde

1
test/out/dir/dir/f.txt Symbolic link
View File

@ -0,0 +1 @@
../f.txt

1
test/out/dir/e.txt Normal file
View File

@ -0,0 +1 @@
4abcde

1
test/out/dir/f.txt Normal file
View File

@ -0,0 +1 @@
5

BIN
test/t.tar.xz Normal file

Binary file not shown.

170
unxz.go Normal file
View File

@ -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
}
}

227
unxz_test.go Normal file
View File

@ -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")
}
}