Tizen_4.0 base
[platform/upstream/docker-engine.git] / pkg / symlink / fs.go
1 // Copyright 2012 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE.BSD file.
4
5 // This code is a modified version of path/filepath/symlink.go from the Go standard library.
6
7 package symlink
8
9 import (
10         "bytes"
11         "errors"
12         "os"
13         "path/filepath"
14         "strings"
15
16         "github.com/docker/docker/pkg/system"
17 )
18
19 // FollowSymlinkInScope is a wrapper around evalSymlinksInScope that returns an
20 // absolute path. This function handles paths in a platform-agnostic manner.
21 func FollowSymlinkInScope(path, root string) (string, error) {
22         path, err := filepath.Abs(filepath.FromSlash(path))
23         if err != nil {
24                 return "", err
25         }
26         root, err = filepath.Abs(filepath.FromSlash(root))
27         if err != nil {
28                 return "", err
29         }
30         return evalSymlinksInScope(path, root)
31 }
32
33 // evalSymlinksInScope will evaluate symlinks in `path` within a scope `root` and return
34 // a result guaranteed to be contained within the scope `root`, at the time of the call.
35 // Symlinks in `root` are not evaluated and left as-is.
36 // Errors encountered while attempting to evaluate symlinks in path will be returned.
37 // Non-existing paths are valid and do not constitute an error.
38 // `path` has to contain `root` as a prefix, or else an error will be returned.
39 // Trying to break out from `root` does not constitute an error.
40 //
41 // Example:
42 //   If /foo/bar -> /outside,
43 //   FollowSymlinkInScope("/foo/bar", "/foo") == "/foo/outside" instead of "/oustide"
44 //
45 // IMPORTANT: it is the caller's responsibility to call evalSymlinksInScope *after* relevant symlinks
46 // are created and not to create subsequently, additional symlinks that could potentially make a
47 // previously-safe path, unsafe. Example: if /foo/bar does not exist, evalSymlinksInScope("/foo/bar", "/foo")
48 // would return "/foo/bar". If one makes /foo/bar a symlink to /baz subsequently, then "/foo/bar" should
49 // no longer be considered safely contained in "/foo".
50 func evalSymlinksInScope(path, root string) (string, error) {
51         root = filepath.Clean(root)
52         if path == root {
53                 return path, nil
54         }
55         if !strings.HasPrefix(path, root) {
56                 return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root)
57         }
58         const maxIter = 255
59         originalPath := path
60         // given root of "/a" and path of "/a/b/../../c" we want path to be "/b/../../c"
61         path = path[len(root):]
62         if root == string(filepath.Separator) {
63                 path = string(filepath.Separator) + path
64         }
65         if !strings.HasPrefix(path, string(filepath.Separator)) {
66                 return "", errors.New("evalSymlinksInScope: " + path + " is not in " + root)
67         }
68         path = filepath.Clean(path)
69         // consume path by taking each frontmost path element,
70         // expanding it if it's a symlink, and appending it to b
71         var b bytes.Buffer
72         // b here will always be considered to be the "current absolute path inside
73         // root" when we append paths to it, we also append a slash and use
74         // filepath.Clean after the loop to trim the trailing slash
75         for n := 0; path != ""; n++ {
76                 if n > maxIter {
77                         return "", errors.New("evalSymlinksInScope: too many links in " + originalPath)
78                 }
79
80                 // find next path component, p
81                 i := strings.IndexRune(path, filepath.Separator)
82                 var p string
83                 if i == -1 {
84                         p, path = path, ""
85                 } else {
86                         p, path = path[:i], path[i+1:]
87                 }
88
89                 if p == "" {
90                         continue
91                 }
92
93                 // this takes a b.String() like "b/../" and a p like "c" and turns it
94                 // into "/b/../c" which then gets filepath.Cleaned into "/c" and then
95                 // root gets prepended and we Clean again (to remove any trailing slash
96                 // if the first Clean gave us just "/")
97                 cleanP := filepath.Clean(string(filepath.Separator) + b.String() + p)
98                 if isDriveOrRoot(cleanP) {
99                         // never Lstat "/" itself, or drive letters on Windows
100                         b.Reset()
101                         continue
102                 }
103                 fullP := filepath.Clean(root + cleanP)
104
105                 fi, err := os.Lstat(fullP)
106                 if os.IsNotExist(err) {
107                         // if p does not exist, accept it
108                         b.WriteString(p)
109                         b.WriteRune(filepath.Separator)
110                         continue
111                 }
112                 if err != nil {
113                         return "", err
114                 }
115                 if fi.Mode()&os.ModeSymlink == 0 {
116                         b.WriteString(p)
117                         b.WriteRune(filepath.Separator)
118                         continue
119                 }
120
121                 // it's a symlink, put it at the front of path
122                 dest, err := os.Readlink(fullP)
123                 if err != nil {
124                         return "", err
125                 }
126                 if system.IsAbs(dest) {
127                         b.Reset()
128                 }
129                 path = dest + string(filepath.Separator) + path
130         }
131
132         // see note above on "fullP := ..." for why this is double-cleaned and
133         // what's happening here
134         return filepath.Clean(root + filepath.Clean(string(filepath.Separator)+b.String())), nil
135 }
136
137 // EvalSymlinks returns the path name after the evaluation of any symbolic
138 // links.
139 // If path is relative the result will be relative to the current directory,
140 // unless one of the components is an absolute symbolic link.
141 // This version has been updated to support long paths prepended with `\\?\`.
142 func EvalSymlinks(path string) (string, error) {
143         return evalSymlinks(path)
144 }