Tizen_4.0 base
[platform/upstream/docker-engine.git] / pkg / archive / copy.go
1 package archive
2
3 import (
4         "archive/tar"
5         "errors"
6         "io"
7         "io/ioutil"
8         "os"
9         "path/filepath"
10         "strings"
11
12         "github.com/Sirupsen/logrus"
13         "github.com/docker/docker/pkg/system"
14 )
15
16 // Errors used or returned by this file.
17 var (
18         ErrNotDirectory      = errors.New("not a directory")
19         ErrDirNotExists      = errors.New("no such directory")
20         ErrCannotCopyDir     = errors.New("cannot copy directory")
21         ErrInvalidCopySource = errors.New("invalid copy source content")
22 )
23
24 // PreserveTrailingDotOrSeparator returns the given cleaned path (after
25 // processing using any utility functions from the path or filepath stdlib
26 // packages) and appends a trailing `/.` or `/` if its corresponding  original
27 // path (from before being processed by utility functions from the path or
28 // filepath stdlib packages) ends with a trailing `/.` or `/`. If the cleaned
29 // path already ends in a `.` path segment, then another is not added. If the
30 // clean path already ends in a path separator, then another is not added.
31 func PreserveTrailingDotOrSeparator(cleanedPath, originalPath string) string {
32         // Ensure paths are in platform semantics
33         cleanedPath = normalizePath(cleanedPath)
34         originalPath = normalizePath(originalPath)
35
36         if !specifiesCurrentDir(cleanedPath) && specifiesCurrentDir(originalPath) {
37                 if !hasTrailingPathSeparator(cleanedPath) {
38                         // Add a separator if it doesn't already end with one (a cleaned
39                         // path would only end in a separator if it is the root).
40                         cleanedPath += string(filepath.Separator)
41                 }
42                 cleanedPath += "."
43         }
44
45         if !hasTrailingPathSeparator(cleanedPath) && hasTrailingPathSeparator(originalPath) {
46                 cleanedPath += string(filepath.Separator)
47         }
48
49         return cleanedPath
50 }
51
52 // assertsDirectory returns whether the given path is
53 // asserted to be a directory, i.e., the path ends with
54 // a trailing '/' or `/.`, assuming a path separator of `/`.
55 func assertsDirectory(path string) bool {
56         return hasTrailingPathSeparator(path) || specifiesCurrentDir(path)
57 }
58
59 // hasTrailingPathSeparator returns whether the given
60 // path ends with the system's path separator character.
61 func hasTrailingPathSeparator(path string) bool {
62         return len(path) > 0 && os.IsPathSeparator(path[len(path)-1])
63 }
64
65 // specifiesCurrentDir returns whether the given path specifies
66 // a "current directory", i.e., the last path segment is `.`.
67 func specifiesCurrentDir(path string) bool {
68         return filepath.Base(path) == "."
69 }
70
71 // SplitPathDirEntry splits the given path between its directory name and its
72 // basename by first cleaning the path but preserves a trailing "." if the
73 // original path specified the current directory.
74 func SplitPathDirEntry(path string) (dir, base string) {
75         cleanedPath := filepath.Clean(normalizePath(path))
76
77         if specifiesCurrentDir(path) {
78                 cleanedPath += string(filepath.Separator) + "."
79         }
80
81         return filepath.Dir(cleanedPath), filepath.Base(cleanedPath)
82 }
83
84 // TarResource archives the resource described by the given CopyInfo to a Tar
85 // archive. A non-nil error is returned if sourcePath does not exist or is
86 // asserted to be a directory but exists as another type of file.
87 //
88 // This function acts as a convenient wrapper around TarWithOptions, which
89 // requires a directory as the source path. TarResource accepts either a
90 // directory or a file path and correctly sets the Tar options.
91 func TarResource(sourceInfo CopyInfo) (content io.ReadCloser, err error) {
92         return TarResourceRebase(sourceInfo.Path, sourceInfo.RebaseName)
93 }
94
95 // TarResourceRebase is like TarResource but renames the first path element of
96 // items in the resulting tar archive to match the given rebaseName if not "".
97 func TarResourceRebase(sourcePath, rebaseName string) (content io.ReadCloser, err error) {
98         sourcePath = normalizePath(sourcePath)
99         if _, err = os.Lstat(sourcePath); err != nil {
100                 // Catches the case where the source does not exist or is not a
101                 // directory if asserted to be a directory, as this also causes an
102                 // error.
103                 return
104         }
105
106         // Separate the source path between its directory and
107         // the entry in that directory which we are archiving.
108         sourceDir, sourceBase := SplitPathDirEntry(sourcePath)
109
110         filter := []string{sourceBase}
111
112         logrus.Debugf("copying %q from %q", sourceBase, sourceDir)
113
114         return TarWithOptions(sourceDir, &TarOptions{
115                 Compression:      Uncompressed,
116                 IncludeFiles:     filter,
117                 IncludeSourceDir: true,
118                 RebaseNames: map[string]string{
119                         sourceBase: rebaseName,
120                 },
121         })
122 }
123
124 // CopyInfo holds basic info about the source
125 // or destination path of a copy operation.
126 type CopyInfo struct {
127         Path       string
128         Exists     bool
129         IsDir      bool
130         RebaseName string
131 }
132
133 // CopyInfoSourcePath stats the given path to create a CopyInfo
134 // struct representing that resource for the source of an archive copy
135 // operation. The given path should be an absolute local path. A source path
136 // has all symlinks evaluated that appear before the last path separator ("/"
137 // on Unix). As it is to be a copy source, the path must exist.
138 func CopyInfoSourcePath(path string, followLink bool) (CopyInfo, error) {
139         // normalize the file path and then evaluate the symbol link
140         // we will use the target file instead of the symbol link if
141         // followLink is set
142         path = normalizePath(path)
143
144         resolvedPath, rebaseName, err := ResolveHostSourcePath(path, followLink)
145         if err != nil {
146                 return CopyInfo{}, err
147         }
148
149         stat, err := os.Lstat(resolvedPath)
150         if err != nil {
151                 return CopyInfo{}, err
152         }
153
154         return CopyInfo{
155                 Path:       resolvedPath,
156                 Exists:     true,
157                 IsDir:      stat.IsDir(),
158                 RebaseName: rebaseName,
159         }, nil
160 }
161
162 // CopyInfoDestinationPath stats the given path to create a CopyInfo
163 // struct representing that resource for the destination of an archive copy
164 // operation. The given path should be an absolute local path.
165 func CopyInfoDestinationPath(path string) (info CopyInfo, err error) {
166         maxSymlinkIter := 10 // filepath.EvalSymlinks uses 255, but 10 already seems like a lot.
167         path = normalizePath(path)
168         originalPath := path
169
170         stat, err := os.Lstat(path)
171
172         if err == nil && stat.Mode()&os.ModeSymlink == 0 {
173                 // The path exists and is not a symlink.
174                 return CopyInfo{
175                         Path:   path,
176                         Exists: true,
177                         IsDir:  stat.IsDir(),
178                 }, nil
179         }
180
181         // While the path is a symlink.
182         for n := 0; err == nil && stat.Mode()&os.ModeSymlink != 0; n++ {
183                 if n > maxSymlinkIter {
184                         // Don't follow symlinks more than this arbitrary number of times.
185                         return CopyInfo{}, errors.New("too many symlinks in " + originalPath)
186                 }
187
188                 // The path is a symbolic link. We need to evaluate it so that the
189                 // destination of the copy operation is the link target and not the
190                 // link itself. This is notably different than CopyInfoSourcePath which
191                 // only evaluates symlinks before the last appearing path separator.
192                 // Also note that it is okay if the last path element is a broken
193                 // symlink as the copy operation should create the target.
194                 var linkTarget string
195
196                 linkTarget, err = os.Readlink(path)
197                 if err != nil {
198                         return CopyInfo{}, err
199                 }
200
201                 if !system.IsAbs(linkTarget) {
202                         // Join with the parent directory.
203                         dstParent, _ := SplitPathDirEntry(path)
204                         linkTarget = filepath.Join(dstParent, linkTarget)
205                 }
206
207                 path = linkTarget
208                 stat, err = os.Lstat(path)
209         }
210
211         if err != nil {
212                 // It's okay if the destination path doesn't exist. We can still
213                 // continue the copy operation if the parent directory exists.
214                 if !os.IsNotExist(err) {
215                         return CopyInfo{}, err
216                 }
217
218                 // Ensure destination parent dir exists.
219                 dstParent, _ := SplitPathDirEntry(path)
220
221                 parentDirStat, err := os.Lstat(dstParent)
222                 if err != nil {
223                         return CopyInfo{}, err
224                 }
225                 if !parentDirStat.IsDir() {
226                         return CopyInfo{}, ErrNotDirectory
227                 }
228
229                 return CopyInfo{Path: path}, nil
230         }
231
232         // The path exists after resolving symlinks.
233         return CopyInfo{
234                 Path:   path,
235                 Exists: true,
236                 IsDir:  stat.IsDir(),
237         }, nil
238 }
239
240 // PrepareArchiveCopy prepares the given srcContent archive, which should
241 // contain the archived resource described by srcInfo, to the destination
242 // described by dstInfo. Returns the possibly modified content archive along
243 // with the path to the destination directory which it should be extracted to.
244 func PrepareArchiveCopy(srcContent io.Reader, srcInfo, dstInfo CopyInfo) (dstDir string, content io.ReadCloser, err error) {
245         // Ensure in platform semantics
246         srcInfo.Path = normalizePath(srcInfo.Path)
247         dstInfo.Path = normalizePath(dstInfo.Path)
248
249         // Separate the destination path between its directory and base
250         // components in case the source archive contents need to be rebased.
251         dstDir, dstBase := SplitPathDirEntry(dstInfo.Path)
252         _, srcBase := SplitPathDirEntry(srcInfo.Path)
253
254         switch {
255         case dstInfo.Exists && dstInfo.IsDir:
256                 // The destination exists as a directory. No alteration
257                 // to srcContent is needed as its contents can be
258                 // simply extracted to the destination directory.
259                 return dstInfo.Path, ioutil.NopCloser(srcContent), nil
260         case dstInfo.Exists && srcInfo.IsDir:
261                 // The destination exists as some type of file and the source
262                 // content is a directory. This is an error condition since
263                 // you cannot copy a directory to an existing file location.
264                 return "", nil, ErrCannotCopyDir
265         case dstInfo.Exists:
266                 // The destination exists as some type of file and the source content
267                 // is also a file. The source content entry will have to be renamed to
268                 // have a basename which matches the destination path's basename.
269                 if len(srcInfo.RebaseName) != 0 {
270                         srcBase = srcInfo.RebaseName
271                 }
272                 return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
273         case srcInfo.IsDir:
274                 // The destination does not exist and the source content is an archive
275                 // of a directory. The archive should be extracted to the parent of
276                 // the destination path instead, and when it is, the directory that is
277                 // created as a result should take the name of the destination path.
278                 // The source content entries will have to be renamed to have a
279                 // basename which matches the destination path's basename.
280                 if len(srcInfo.RebaseName) != 0 {
281                         srcBase = srcInfo.RebaseName
282                 }
283                 return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
284         case assertsDirectory(dstInfo.Path):
285                 // The destination does not exist and is asserted to be created as a
286                 // directory, but the source content is not a directory. This is an
287                 // error condition since you cannot create a directory from a file
288                 // source.
289                 return "", nil, ErrDirNotExists
290         default:
291                 // The last remaining case is when the destination does not exist, is
292                 // not asserted to be a directory, and the source content is not an
293                 // archive of a directory. It this case, the destination file will need
294                 // to be created when the archive is extracted and the source content
295                 // entry will have to be renamed to have a basename which matches the
296                 // destination path's basename.
297                 if len(srcInfo.RebaseName) != 0 {
298                         srcBase = srcInfo.RebaseName
299                 }
300                 return dstDir, RebaseArchiveEntries(srcContent, srcBase, dstBase), nil
301         }
302
303 }
304
305 // RebaseArchiveEntries rewrites the given srcContent archive replacing
306 // an occurrence of oldBase with newBase at the beginning of entry names.
307 func RebaseArchiveEntries(srcContent io.Reader, oldBase, newBase string) io.ReadCloser {
308         if oldBase == string(os.PathSeparator) {
309                 // If oldBase specifies the root directory, use an empty string as
310                 // oldBase instead so that newBase doesn't replace the path separator
311                 // that all paths will start with.
312                 oldBase = ""
313         }
314
315         rebased, w := io.Pipe()
316
317         go func() {
318                 srcTar := tar.NewReader(srcContent)
319                 rebasedTar := tar.NewWriter(w)
320
321                 for {
322                         hdr, err := srcTar.Next()
323                         if err == io.EOF {
324                                 // Signals end of archive.
325                                 rebasedTar.Close()
326                                 w.Close()
327                                 return
328                         }
329                         if err != nil {
330                                 w.CloseWithError(err)
331                                 return
332                         }
333
334                         hdr.Name = strings.Replace(hdr.Name, oldBase, newBase, 1)
335
336                         if err = rebasedTar.WriteHeader(hdr); err != nil {
337                                 w.CloseWithError(err)
338                                 return
339                         }
340
341                         if _, err = io.Copy(rebasedTar, srcTar); err != nil {
342                                 w.CloseWithError(err)
343                                 return
344                         }
345                 }
346         }()
347
348         return rebased
349 }
350
351 // CopyResource performs an archive copy from the given source path to the
352 // given destination path. The source path MUST exist and the destination
353 // path's parent directory must exist.
354 func CopyResource(srcPath, dstPath string, followLink bool) error {
355         var (
356                 srcInfo CopyInfo
357                 err     error
358         )
359
360         // Ensure in platform semantics
361         srcPath = normalizePath(srcPath)
362         dstPath = normalizePath(dstPath)
363
364         // Clean the source and destination paths.
365         srcPath = PreserveTrailingDotOrSeparator(filepath.Clean(srcPath), srcPath)
366         dstPath = PreserveTrailingDotOrSeparator(filepath.Clean(dstPath), dstPath)
367
368         if srcInfo, err = CopyInfoSourcePath(srcPath, followLink); err != nil {
369                 return err
370         }
371
372         content, err := TarResource(srcInfo)
373         if err != nil {
374                 return err
375         }
376         defer content.Close()
377
378         return CopyTo(content, srcInfo, dstPath)
379 }
380
381 // CopyTo handles extracting the given content whose
382 // entries should be sourced from srcInfo to dstPath.
383 func CopyTo(content io.Reader, srcInfo CopyInfo, dstPath string) error {
384         // The destination path need not exist, but CopyInfoDestinationPath will
385         // ensure that at least the parent directory exists.
386         dstInfo, err := CopyInfoDestinationPath(normalizePath(dstPath))
387         if err != nil {
388                 return err
389         }
390
391         dstDir, copyArchive, err := PrepareArchiveCopy(content, srcInfo, dstInfo)
392         if err != nil {
393                 return err
394         }
395         defer copyArchive.Close()
396
397         options := &TarOptions{
398                 NoLchown:             true,
399                 NoOverwriteDirNonDir: true,
400         }
401
402         return Untar(copyArchive, dstDir, options)
403 }
404
405 // ResolveHostSourcePath decides real path need to be copied with parameters such as
406 // whether to follow symbol link or not, if followLink is true, resolvedPath will return
407 // link target of any symbol link file, else it will only resolve symlink of directory
408 // but return symbol link file itself without resolving.
409 func ResolveHostSourcePath(path string, followLink bool) (resolvedPath, rebaseName string, err error) {
410         if followLink {
411                 resolvedPath, err = filepath.EvalSymlinks(path)
412                 if err != nil {
413                         return
414                 }
415
416                 resolvedPath, rebaseName = GetRebaseName(path, resolvedPath)
417         } else {
418                 dirPath, basePath := filepath.Split(path)
419
420                 // if not follow symbol link, then resolve symbol link of parent dir
421                 var resolvedDirPath string
422                 resolvedDirPath, err = filepath.EvalSymlinks(dirPath)
423                 if err != nil {
424                         return
425                 }
426                 // resolvedDirPath will have been cleaned (no trailing path separators) so
427                 // we can manually join it with the base path element.
428                 resolvedPath = resolvedDirPath + string(filepath.Separator) + basePath
429                 if hasTrailingPathSeparator(path) && filepath.Base(path) != filepath.Base(resolvedPath) {
430                         rebaseName = filepath.Base(path)
431                 }
432         }
433         return resolvedPath, rebaseName, nil
434 }
435
436 // GetRebaseName normalizes and compares path and resolvedPath,
437 // return completed resolved path and rebased file name
438 func GetRebaseName(path, resolvedPath string) (string, string) {
439         // linkTarget will have been cleaned (no trailing path separators and dot) so
440         // we can manually join it with them
441         var rebaseName string
442         if specifiesCurrentDir(path) && !specifiesCurrentDir(resolvedPath) {
443                 resolvedPath += string(filepath.Separator) + "."
444         }
445
446         if hasTrailingPathSeparator(path) && !hasTrailingPathSeparator(resolvedPath) {
447                 resolvedPath += string(filepath.Separator)
448         }
449
450         if filepath.Base(path) != filepath.Base(resolvedPath) {
451                 // In the case where the path had a trailing separator and a symlink
452                 // evaluation has changed the last path component, we will need to
453                 // rebase the name in the archive that is being copied to match the
454                 // originally requested name.
455                 rebaseName = filepath.Base(path)
456         }
457         return resolvedPath, rebaseName
458 }