archive.go 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288
  1. // Copyright 2016 The go-ethereum Authors
  2. // This file is part of the go-ethereum library.
  3. //
  4. // The go-ethereum library is free software: you can redistribute it and/or modify
  5. // it under the terms of the GNU Lesser General Public License as published by
  6. // the Free Software Foundation, either version 3 of the License, or
  7. // (at your option) any later version.
  8. //
  9. // The go-ethereum library is distributed in the hope that it will be useful,
  10. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  12. // GNU Lesser General Public License for more details.
  13. //
  14. // You should have received a copy of the GNU Lesser General Public License
  15. // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
  16. package build
  17. import (
  18. "archive/tar"
  19. "archive/zip"
  20. "compress/gzip"
  21. "fmt"
  22. "io"
  23. "os"
  24. "path/filepath"
  25. "strings"
  26. )
  27. type Archive interface {
  28. // Directory adds a new directory entry to the archive and sets the
  29. // directory for subsequent calls to Header.
  30. Directory(name string) error
  31. // Header adds a new file to the archive. The file is added to the directory
  32. // set by Directory. The content of the file must be written to the returned
  33. // writer.
  34. Header(os.FileInfo) (io.Writer, error)
  35. // Close flushes the archive and closes the underlying file.
  36. Close() error
  37. }
  38. func NewArchive(file *os.File) (Archive, string) {
  39. switch {
  40. case strings.HasSuffix(file.Name(), ".zip"):
  41. return NewZipArchive(file), strings.TrimSuffix(file.Name(), ".zip")
  42. case strings.HasSuffix(file.Name(), ".tar.gz"):
  43. return NewTarballArchive(file), strings.TrimSuffix(file.Name(), ".tar.gz")
  44. default:
  45. return nil, ""
  46. }
  47. }
  48. // AddFile appends an existing file to an archive.
  49. func AddFile(a Archive, file string) error {
  50. fd, err := os.Open(file)
  51. if err != nil {
  52. return err
  53. }
  54. defer fd.Close()
  55. fi, err := fd.Stat()
  56. if err != nil {
  57. return err
  58. }
  59. w, err := a.Header(fi)
  60. if err != nil {
  61. return err
  62. }
  63. if _, err := io.Copy(w, fd); err != nil {
  64. return err
  65. }
  66. return nil
  67. }
  68. // WriteArchive creates an archive containing the given files.
  69. func WriteArchive(name string, files []string) (err error) {
  70. archfd, err := os.Create(name)
  71. if err != nil {
  72. return err
  73. }
  74. defer func() {
  75. archfd.Close()
  76. // Remove the half-written archive on failure.
  77. if err != nil {
  78. os.Remove(name)
  79. }
  80. }()
  81. archive, basename := NewArchive(archfd)
  82. if archive == nil {
  83. return fmt.Errorf("unknown archive extension")
  84. }
  85. fmt.Println(name)
  86. if err := archive.Directory(basename); err != nil {
  87. return err
  88. }
  89. for _, file := range files {
  90. fmt.Println(" +", filepath.Base(file))
  91. if err := AddFile(archive, file); err != nil {
  92. return err
  93. }
  94. }
  95. return archive.Close()
  96. }
  97. type ZipArchive struct {
  98. dir string
  99. zipw *zip.Writer
  100. file io.Closer
  101. }
  102. func NewZipArchive(w io.WriteCloser) Archive {
  103. return &ZipArchive{"", zip.NewWriter(w), w}
  104. }
  105. func (a *ZipArchive) Directory(name string) error {
  106. a.dir = name + "/"
  107. return nil
  108. }
  109. func (a *ZipArchive) Header(fi os.FileInfo) (io.Writer, error) {
  110. head, err := zip.FileInfoHeader(fi)
  111. if err != nil {
  112. return nil, fmt.Errorf("can't make zip header: %v", err)
  113. }
  114. head.Name = a.dir + head.Name
  115. head.Method = zip.Deflate
  116. w, err := a.zipw.CreateHeader(head)
  117. if err != nil {
  118. return nil, fmt.Errorf("can't add zip header: %v", err)
  119. }
  120. return w, nil
  121. }
  122. func (a *ZipArchive) Close() error {
  123. if err := a.zipw.Close(); err != nil {
  124. return err
  125. }
  126. return a.file.Close()
  127. }
  128. type TarballArchive struct {
  129. dir string
  130. tarw *tar.Writer
  131. gzw *gzip.Writer
  132. file io.Closer
  133. }
  134. func NewTarballArchive(w io.WriteCloser) Archive {
  135. gzw := gzip.NewWriter(w)
  136. tarw := tar.NewWriter(gzw)
  137. return &TarballArchive{"", tarw, gzw, w}
  138. }
  139. func (a *TarballArchive) Directory(name string) error {
  140. a.dir = name + "/"
  141. return a.tarw.WriteHeader(&tar.Header{
  142. Name: a.dir,
  143. Mode: 0755,
  144. Typeflag: tar.TypeDir,
  145. })
  146. }
  147. func (a *TarballArchive) Header(fi os.FileInfo) (io.Writer, error) {
  148. head, err := tar.FileInfoHeader(fi, "")
  149. if err != nil {
  150. return nil, fmt.Errorf("can't make tar header: %v", err)
  151. }
  152. head.Name = a.dir + head.Name
  153. if err := a.tarw.WriteHeader(head); err != nil {
  154. return nil, fmt.Errorf("can't add tar header: %v", err)
  155. }
  156. return a.tarw, nil
  157. }
  158. func (a *TarballArchive) Close() error {
  159. if err := a.tarw.Close(); err != nil {
  160. return err
  161. }
  162. if err := a.gzw.Close(); err != nil {
  163. return err
  164. }
  165. return a.file.Close()
  166. }
  167. // ExtractArchive unpacks a .zip or .tar.gz archive to the destination directory.
  168. func ExtractArchive(archive string, dest string) error {
  169. ar, err := os.Open(archive)
  170. if err != nil {
  171. return err
  172. }
  173. defer ar.Close()
  174. switch {
  175. case strings.HasSuffix(archive, ".tar.gz"):
  176. return extractTarball(ar, dest)
  177. case strings.HasSuffix(archive, ".zip"):
  178. return extractZip(ar, dest)
  179. default:
  180. return fmt.Errorf("unhandled archive type %s", archive)
  181. }
  182. }
  183. // extractTarball unpacks a .tar.gz file.
  184. func extractTarball(ar io.Reader, dest string) error {
  185. gzr, err := gzip.NewReader(ar)
  186. if err != nil {
  187. return err
  188. }
  189. defer gzr.Close()
  190. tr := tar.NewReader(gzr)
  191. for {
  192. // Move to the next file header.
  193. header, err := tr.Next()
  194. if err != nil {
  195. if err == io.EOF {
  196. return nil
  197. }
  198. return err
  199. }
  200. // We only care about regular files, directory modes
  201. // and special file types are not supported.
  202. if header.Typeflag == tar.TypeReg {
  203. armode := header.FileInfo().Mode()
  204. err := extractFile(header.Name, armode, tr, dest)
  205. if err != nil {
  206. return fmt.Errorf("extract %s: %v", header.Name, err)
  207. }
  208. }
  209. }
  210. }
  211. // extractZip unpacks the given .zip file.
  212. func extractZip(ar *os.File, dest string) error {
  213. info, err := ar.Stat()
  214. if err != nil {
  215. return err
  216. }
  217. zr, err := zip.NewReader(ar, info.Size())
  218. if err != nil {
  219. return err
  220. }
  221. for _, zf := range zr.File {
  222. if !zf.Mode().IsRegular() {
  223. continue
  224. }
  225. data, err := zf.Open()
  226. if err != nil {
  227. return err
  228. }
  229. err = extractFile(zf.Name, zf.Mode(), data, dest)
  230. data.Close()
  231. if err != nil {
  232. return fmt.Errorf("extract %s: %v", zf.Name, err)
  233. }
  234. }
  235. return nil
  236. }
  237. // extractFile extracts a single file from an archive.
  238. func extractFile(arpath string, armode os.FileMode, data io.Reader, dest string) error {
  239. // Check that path is inside destination directory.
  240. target := filepath.Join(dest, filepath.FromSlash(arpath))
  241. if !strings.HasPrefix(target, filepath.Clean(dest)+string(os.PathSeparator)) {
  242. return fmt.Errorf("path %q escapes archive destination", target)
  243. }
  244. // Ensure the destination directory exists.
  245. if err := os.MkdirAll(filepath.Dir(target), 0755); err != nil {
  246. return err
  247. }
  248. // Copy file data.
  249. file, err := os.OpenFile(target, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, armode)
  250. if err != nil {
  251. return err
  252. }
  253. if _, err := io.Copy(file, data); err != nil {
  254. file.Close()
  255. os.Remove(target)
  256. return err
  257. }
  258. return file.Close()
  259. }