zip_artifact.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174
  1. // Copyright 2019 Google Inc. All rights reserved.
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package main
  15. import (
  16. "archive/zip"
  17. "context"
  18. "fmt"
  19. "hash/crc32"
  20. "io"
  21. "io/ioutil"
  22. "os"
  23. "path/filepath"
  24. )
  25. // ZipArtifact represents a zip file that may be local or remote.
  26. type ZipArtifact interface {
  27. // Files returns the list of files contained in the zip file.
  28. Files() ([]*ZipArtifactFile, error)
  29. // Close closes the zip file artifact.
  30. Close()
  31. }
  32. // localZipArtifact is a handle to a local zip file artifact.
  33. type localZipArtifact struct {
  34. zr *zip.ReadCloser
  35. files []*ZipArtifactFile
  36. }
  37. // NewLocalZipArtifact returns a ZipArtifact for a local zip file..
  38. func NewLocalZipArtifact(name string) (ZipArtifact, error) {
  39. zr, err := zip.OpenReader(name)
  40. if err != nil {
  41. return nil, err
  42. }
  43. var files []*ZipArtifactFile
  44. for _, zf := range zr.File {
  45. files = append(files, &ZipArtifactFile{zf})
  46. }
  47. return &localZipArtifact{
  48. zr: zr,
  49. files: files,
  50. }, nil
  51. }
  52. // Files returns the list of files contained in the local zip file artifact.
  53. func (z *localZipArtifact) Files() ([]*ZipArtifactFile, error) {
  54. return z.files, nil
  55. }
  56. // Close closes the buffered reader of the local zip file artifact.
  57. func (z *localZipArtifact) Close() {
  58. z.zr.Close()
  59. }
  60. // ZipArtifactFile contains a zip.File handle to the data inside the remote *-target_files-*.zip
  61. // build artifact.
  62. type ZipArtifactFile struct {
  63. *zip.File
  64. }
  65. // Extract begins extract a file from inside a ZipArtifact. It returns an
  66. // ExtractedZipArtifactFile handle.
  67. func (zf *ZipArtifactFile) Extract(ctx context.Context, dir string,
  68. limiter chan bool) *ExtractedZipArtifactFile {
  69. d := &ExtractedZipArtifactFile{
  70. initCh: make(chan struct{}),
  71. }
  72. go func() {
  73. defer close(d.initCh)
  74. limiter <- true
  75. defer func() { <-limiter }()
  76. zr, err := zf.Open()
  77. if err != nil {
  78. d.err = err
  79. return
  80. }
  81. defer zr.Close()
  82. crc := crc32.NewIEEE()
  83. r := io.TeeReader(zr, crc)
  84. if filepath.Clean(zf.Name) != zf.Name {
  85. d.err = fmt.Errorf("invalid filename %q", zf.Name)
  86. return
  87. }
  88. path := filepath.Join(dir, zf.Name)
  89. err = os.MkdirAll(filepath.Dir(path), 0777)
  90. if err != nil {
  91. d.err = err
  92. return
  93. }
  94. err = os.Remove(path)
  95. if err != nil && !os.IsNotExist(err) {
  96. d.err = err
  97. return
  98. }
  99. if zf.Mode().IsRegular() {
  100. w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, zf.Mode())
  101. if err != nil {
  102. d.err = err
  103. return
  104. }
  105. defer w.Close()
  106. _, err = io.Copy(w, r)
  107. if err != nil {
  108. d.err = err
  109. return
  110. }
  111. } else if zf.Mode()&os.ModeSymlink != 0 {
  112. target, err := ioutil.ReadAll(r)
  113. if err != nil {
  114. d.err = err
  115. return
  116. }
  117. err = os.Symlink(string(target), path)
  118. if err != nil {
  119. d.err = err
  120. return
  121. }
  122. } else {
  123. d.err = fmt.Errorf("unknown mode %q", zf.Mode())
  124. return
  125. }
  126. if crc.Sum32() != zf.CRC32 {
  127. d.err = fmt.Errorf("crc mismatch for %v", zf.Name)
  128. return
  129. }
  130. d.path = path
  131. }()
  132. return d
  133. }
  134. // ExtractedZipArtifactFile is a handle to a downloaded file from a remoteZipArtifact. The download
  135. // may still be in progress, and will be complete with Path() returns.
  136. type ExtractedZipArtifactFile struct {
  137. initCh chan struct{}
  138. err error
  139. path string
  140. }
  141. // Path returns the path to the downloaded file and any errors that occurred during the download.
  142. // It will block until the download is complete.
  143. func (d *ExtractedZipArtifactFile) Path() (string, error) {
  144. <-d.initCh
  145. return d.path, d.err
  146. }