github.com/qxnw/lib4go@v0.0.0-20180426074627-c80c7e84b925/archiver/tar.go (about) 1 package archiver 2 3 import ( 4 "archive/tar" 5 "bytes" 6 "fmt" 7 "io" 8 "os" 9 "path/filepath" 10 "strconv" 11 "strings" 12 ) 13 14 // Tar is for Tar format12312111 15 var Tar tarFormat 16 17 func init() { 18 RegisterFormat("Tar", Tar) 19 } 20 21 type tarFormat struct{} 22 23 func (tarFormat) Match(filename string) bool { 24 return strings.HasSuffix(strings.ToLower(filename), ".tar") || isTar(filename) 25 } 26 27 const tarBlockSize int = 512 28 29 // isTar checks the file has the Tar format header by reading its beginning 30 // block. 31 func isTar(tarPath string) bool { 32 f, err := os.Open(tarPath) 33 if err != nil { 34 return false 35 } 36 defer f.Close() 37 38 buf := make([]byte, tarBlockSize) 39 if _, err = io.ReadFull(f, buf); err != nil { 40 return false 41 } 42 43 return hasTarHeader(buf) 44 } 45 46 // hasTarHeader checks passed bytes has a valid tar header or not. buf must 47 // contain at least 512 bytes and if not, it always returns false. 48 func hasTarHeader(buf []byte) bool { 49 if len(buf) < tarBlockSize { 50 return false 51 } 52 53 b := buf[148:156] 54 b = bytes.Trim(b, " \x00") // clean up all spaces and null bytes 55 if len(b) == 0 { 56 return false // unknown format 57 } 58 hdrSum, err := strconv.ParseUint(string(b), 8, 64) 59 if err != nil { 60 return false 61 } 62 63 // According to the go official archive/tar, Sun tar uses signed byte 64 // values so this calcs both signed and unsigned 65 var usum uint64 66 var sum int64 67 for i, c := range buf { 68 if 148 <= i && i < 156 { 69 c = ' ' // checksum field itself is counted as branks 70 } 71 usum += uint64(uint8(c)) 72 sum += int64(int8(c)) 73 } 74 75 if hdrSum != usum && int64(hdrSum) != sum { 76 return false // invalid checksum 77 } 78 79 return true 80 } 81 82 // Write outputs a .tar file to a Writer containing the 83 // contents of files listed in filePaths. File paths can 84 // be those of regular files or directories. Regular 85 // files are stored at the 'root' of the archive, and 86 // directories are recursively added. 87 func (tarFormat) Write(output io.Writer, filePaths []string) error { 88 return writeTar(filePaths, output, "") 89 } 90 91 // Make creates a .tar file at tarPath containing the 92 // contents of files listed in filePaths. File paths can 93 // be those of regular files or directories. Regular 94 // files are stored at the 'root' of the archive, and 95 // directories are recursively added. 96 func (tarFormat) Make(tarPath string, filePaths []string) error { 97 out, err := os.Create(tarPath) 98 if err != nil { 99 return fmt.Errorf("error creating %s: %v", tarPath, err) 100 } 101 defer out.Close() 102 103 return writeTar(filePaths, out, tarPath) 104 } 105 106 func writeTar(filePaths []string, output io.Writer, dest string) error { 107 tarWriter := tar.NewWriter(output) 108 defer tarWriter.Close() 109 110 return tarball(filePaths, tarWriter, dest) 111 } 112 113 // tarball writes all files listed in filePaths into tarWriter, which is 114 // writing into a file located at dest. 115 func tarball(filePaths []string, tarWriter *tar.Writer, dest string) error { 116 for _, fpath := range filePaths { 117 err := tarFile(tarWriter, fpath, dest) 118 if err != nil { 119 return err 120 } 121 } 122 return nil 123 } 124 125 // tarFile writes the file at source into tarWriter. It does so 126 // recursively for directories. 127 func tarFile(tarWriter *tar.Writer, source, dest string) error { 128 sourceInfo, err := os.Stat(source) 129 if err != nil { 130 return fmt.Errorf("%s: stat: %v", source, err) 131 } 132 133 var baseDir string 134 if sourceInfo.IsDir() { 135 baseDir = filepath.Base(source) 136 } 137 138 return filepath.Walk(source, func(path string, info os.FileInfo, err error) error { 139 if err != nil { 140 return fmt.Errorf("error walking to %s: %v", path, err) 141 } 142 143 header, err := tar.FileInfoHeader(info, path) 144 if err != nil { 145 return fmt.Errorf("%s: making header: %v", path, err) 146 } 147 148 if baseDir != "" { 149 header.Name = filepath.Join(baseDir, strings.TrimPrefix(path, source)) 150 } 151 152 if header.Name == dest { 153 // our new tar file is inside the directory being archived; skip it 154 return nil 155 } 156 157 if info.IsDir() { 158 header.Name += "/" 159 } 160 161 err = tarWriter.WriteHeader(header) 162 if err != nil { 163 return fmt.Errorf("%s: writing header: %v", path, err) 164 } 165 166 if info.IsDir() { 167 return nil 168 } 169 170 if header.Typeflag == tar.TypeReg { 171 file, err := os.Open(path) 172 if err != nil { 173 return fmt.Errorf("%s: open: %v", path, err) 174 } 175 defer file.Close() 176 177 _, err = io.CopyN(tarWriter, file, info.Size()) 178 if err != nil && err != io.EOF { 179 return fmt.Errorf("%s: copying contents: %v", path, err) 180 } 181 } 182 return nil 183 }) 184 } 185 186 // Read untars a .tar file read from a Reader and puts 187 // the contents into destination. 188 func (tarFormat) Read(input io.Reader, destination string) error { 189 return untar(tar.NewReader(input), destination) 190 } 191 192 // Open untars source and puts the contents into destination. 193 func (tarFormat) Open(source, destination string) error { 194 f, err := os.Open(source) 195 if err != nil { 196 return fmt.Errorf("%s: failed to open archive: %v", source, err) 197 } 198 defer f.Close() 199 200 return Tar.Read(f, destination) 201 } 202 203 // untar un-tarballs the contents of tr into destination. 204 func untar(tr *tar.Reader, destination string) error { 205 for { 206 header, err := tr.Next() 207 if err == io.EOF { 208 break 209 } else if err != nil { 210 return err 211 } 212 213 if err := untarFile(tr, header, destination); err != nil { 214 return err 215 } 216 } 217 return nil 218 } 219 220 // untarFile untars a single file from tr with header header into destination. 221 func untarFile(tr *tar.Reader, header *tar.Header, destination string) error { 222 switch header.Typeflag { 223 case tar.TypeDir: 224 return mkdir(filepath.Join(destination, header.Name)) 225 case tar.TypeReg, tar.TypeRegA, tar.TypeChar, tar.TypeBlock, tar.TypeFifo: 226 return writeNewFile(filepath.Join(destination, header.Name), tr, header.FileInfo().Mode()) 227 case tar.TypeSymlink: 228 return writeNewSymbolicLink(filepath.Join(destination, header.Name), header.Linkname) 229 case tar.TypeLink: 230 return writeNewHardLink(filepath.Join(destination, header.Name), filepath.Join(destination, header.Linkname)) 231 default: 232 return fmt.Errorf("%s: unknown type flag: %c", header.Name, header.Typeflag) 233 } 234 }