cp.go 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. // Copyright 2019 Yunion
  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 cp
  15. import (
  16. "archive/tar"
  17. "fmt"
  18. "io"
  19. "io/ioutil"
  20. "os"
  21. "path"
  22. "path/filepath"
  23. "strings"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/onecloud/pkg/mcclient"
  27. "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  28. )
  29. type ICopy interface {
  30. CopyToContainer(s *mcclient.ClientSession, srcPath string, dest ContainerFileOpt) error
  31. CopyFromContainer(s *mcclient.ClientSession, src ContainerFileOpt, destPath string) error
  32. }
  33. type sCopy struct {
  34. noPreserve bool
  35. ExecParentCmdName string
  36. }
  37. func NewCopy() ICopy {
  38. return &sCopy{}
  39. }
  40. type ContainerFileOpt struct {
  41. ContainerId string
  42. File string
  43. }
  44. var ErrFileCannotBeEmpty = errors.Error("filepath can not be empty")
  45. func (o *sCopy) CopyFromContainer(s *mcclient.ClientSession, src ContainerFileOpt, destPath string) error {
  46. if len(src.File) == 0 || len(destPath) == 0 {
  47. return ErrFileCannotBeEmpty
  48. }
  49. reader, outStream := io.Pipe()
  50. go func() {
  51. defer outStream.Close()
  52. if err := compute.Containers.CopyTarFrom(s, src.ContainerId, []string{src.File}, outStream); err != nil {
  53. log.Errorf("copy src by tar from container: %v", err)
  54. }
  55. }()
  56. prefix := getPrefix(src.File)
  57. prefix = path.Clean(prefix)
  58. // remove extraneous path shortcuts - these could occur if a path contained extra "../
  59. // and attempted to navigate beyond "/" in a remote filesystem
  60. prefix = stripPathShortcuts(prefix)
  61. return o.untarAll(src, reader, destPath, prefix)
  62. }
  63. func getPrefix(file string) string {
  64. // tar strips the leading '/' if it's there, so we will too
  65. return strings.TrimLeft(file, "/")
  66. }
  67. // stripPathShortcuts removes any leading or trailing "../" from a given path
  68. func stripPathShortcuts(p string) string {
  69. newPath := path.Clean(p)
  70. trimmed := strings.TrimPrefix(newPath, "../")
  71. for trimmed != newPath {
  72. newPath = trimmed
  73. trimmed = strings.TrimPrefix(newPath, "../")
  74. }
  75. // trim leftover {".", ".."}
  76. if newPath == "." || newPath == ".." {
  77. newPath = ""
  78. }
  79. if len(newPath) > 0 && string(newPath[0]) == "/" {
  80. return newPath[1:]
  81. }
  82. return newPath
  83. }
  84. func (o *sCopy) untarAll(src ContainerFileOpt, reader io.Reader, destDir, prefix string) error {
  85. symlinkWarningPrinted := false
  86. // TODO: use compression here?
  87. tarReader := tar.NewReader(reader)
  88. for {
  89. header, err := tarReader.Next()
  90. if err != nil {
  91. if err != io.EOF {
  92. return err
  93. }
  94. break
  95. }
  96. // All the files will start with the prefix, which is the directory where
  97. // they were located on the pod, we need to strip down that prefix, but
  98. // if the prefix is missing it means the tar was tempered with.
  99. // For the case where prefix is empty we need to ensure that the path
  100. // is not absolute, which also indicates the tar file was tempered with.
  101. if !strings.HasPrefix(header.Name, prefix) {
  102. return fmt.Errorf("tar contents corrupted")
  103. }
  104. // basic file information
  105. mode := header.FileInfo().Mode()
  106. destFileName := filepath.Join(destDir, header.Name[len(prefix):])
  107. if !isDestRelative(destDir, destFileName) {
  108. fmt.Fprintf(os.Stderr, "warning: file %q is outside target destination, skipping\n", destFileName)
  109. continue
  110. }
  111. baseName := filepath.Dir(destFileName)
  112. if err := os.MkdirAll(baseName, 0755); err != nil {
  113. return err
  114. }
  115. if header.FileInfo().IsDir() {
  116. if err := os.MkdirAll(destFileName, 0755); err != nil {
  117. return err
  118. }
  119. continue
  120. }
  121. if mode&os.ModeSymlink != 0 {
  122. if !symlinkWarningPrinted && len(o.ExecParentCmdName) > 0 {
  123. fmt.Fprintf(os.Stderr, "warning: skipping symlink: %q -> %q\n", destFileName, header.Linkname)
  124. symlinkWarningPrinted = true
  125. continue
  126. }
  127. fmt.Fprintf(os.Stderr, "warning: skipping symlink: %q -> %q\n", destFileName, header.Linkname)
  128. continue
  129. }
  130. outFile, err := os.Create(destFileName)
  131. if err != nil {
  132. return err
  133. }
  134. defer outFile.Close()
  135. if _, err := io.Copy(outFile, tarReader); err != nil {
  136. return err
  137. }
  138. if err := outFile.Close(); err != nil {
  139. return err
  140. }
  141. }
  142. return nil
  143. }
  144. // isDestRelative returns true if dest is pointing outside the base directory,
  145. // false otherwise.
  146. func isDestRelative(base, dest string) bool {
  147. relative, err := filepath.Rel(base, dest)
  148. if err != nil {
  149. return false
  150. }
  151. return relative == "." || relative == stripPathShortcuts(relative)
  152. }
  153. func (o *sCopy) CopyToContainer(s *mcclient.ClientSession, srcFile string, dest ContainerFileOpt) error {
  154. if len(srcFile) == 0 || len(dest.File) == 0 {
  155. return ErrFileCannotBeEmpty
  156. }
  157. if _, err := os.Stat(srcFile); err != nil {
  158. return errors.Wrapf(err, "check source file: %s", srcFile)
  159. }
  160. reader, writer := io.Pipe()
  161. // strip trailing slash (if any)
  162. if dest.File != "/" && strings.HasSuffix(string(dest.File[len(dest.File)-1]), "/") {
  163. dest.File = dest.File[:len(dest.File)-1]
  164. }
  165. if err := compute.Containers.CheckDestinationIsDir(s, dest.ContainerId, dest.File); err == nil {
  166. // If no error, dest.File was found to be a directory.
  167. // Copy specified src info it
  168. dest.File = dest.File + "/" + path.Base(srcFile)
  169. }
  170. go func() {
  171. defer writer.Close()
  172. if err := makeTar(srcFile, dest.File, writer); err != nil {
  173. log.Errorf("makeTar error: %v", err)
  174. }
  175. }()
  176. destDir := path.Dir(dest.File)
  177. return compute.Containers.CopyTarTo(s, dest.ContainerId, destDir, reader, o.noPreserve)
  178. }
  179. func makeTar(srcPath, destPath string, writer io.Writer) error {
  180. tarWriter := tar.NewWriter(writer)
  181. defer tarWriter.Close()
  182. srcPath = path.Clean(srcPath)
  183. destPath = path.Clean(destPath)
  184. return recursiveTar(path.Dir(srcPath), path.Base(srcPath), path.Dir(destPath), path.Base(destPath), tarWriter)
  185. }
  186. func recursiveTar(srcBase, srcFile, destBase, destFile string, tw *tar.Writer) error {
  187. srcPath := path.Join(srcBase, srcFile)
  188. matchedPaths, err := filepath.Glob(srcPath)
  189. if err != nil {
  190. return err
  191. }
  192. for _, fpath := range matchedPaths {
  193. stat, err := os.Lstat(fpath)
  194. if err != nil {
  195. return err
  196. }
  197. if stat.IsDir() {
  198. files, err := ioutil.ReadDir(fpath)
  199. if err != nil {
  200. return err
  201. }
  202. if len(files) == 0 {
  203. //case empty directory
  204. hdr, _ := tar.FileInfoHeader(stat, fpath)
  205. hdr.Name = destFile
  206. if err := tw.WriteHeader(hdr); err != nil {
  207. return err
  208. }
  209. }
  210. for _, f := range files {
  211. if err := recursiveTar(srcBase, path.Join(srcFile, f.Name()), destBase, path.Join(destFile, f.Name()), tw); err != nil {
  212. return err
  213. }
  214. }
  215. return nil
  216. } else if stat.Mode()&os.ModeSymlink != 0 {
  217. //case soft link
  218. hdr, _ := tar.FileInfoHeader(stat, fpath)
  219. target, err := os.Readlink(fpath)
  220. if err != nil {
  221. return err
  222. }
  223. hdr.Linkname = target
  224. hdr.Name = destFile
  225. if err := tw.WriteHeader(hdr); err != nil {
  226. return err
  227. }
  228. } else {
  229. //case regular file or other file type like pipe
  230. hdr, err := tar.FileInfoHeader(stat, fpath)
  231. if err != nil {
  232. return err
  233. }
  234. hdr.Name = destFile
  235. if err := tw.WriteHeader(hdr); err != nil {
  236. return err
  237. }
  238. f, err := os.Open(fpath)
  239. if err != nil {
  240. return err
  241. }
  242. defer f.Close()
  243. if _, err := io.Copy(tw, f); err != nil {
  244. return err
  245. }
  246. return f.Close()
  247. }
  248. }
  249. return nil
  250. }