path.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286
  1. /*
  2. Copyright The containerd Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package fs
  14. import (
  15. "bytes"
  16. "context"
  17. "io"
  18. "os"
  19. "path/filepath"
  20. "strings"
  21. "github.com/pkg/errors"
  22. )
  23. var (
  24. errTooManyLinks = errors.New("too many links")
  25. )
  26. type currentPath struct {
  27. path string
  28. f os.FileInfo
  29. fullPath string
  30. }
  31. func pathChange(lower, upper *currentPath) (ChangeKind, string) {
  32. if lower == nil {
  33. if upper == nil {
  34. panic("cannot compare nil paths")
  35. }
  36. return ChangeKindAdd, upper.path
  37. }
  38. if upper == nil {
  39. return ChangeKindDelete, lower.path
  40. }
  41. // TODO: compare by directory
  42. switch i := strings.Compare(lower.path, upper.path); {
  43. case i < 0:
  44. // File in lower that is not in upper
  45. return ChangeKindDelete, lower.path
  46. case i > 0:
  47. // File in upper that is not in lower
  48. return ChangeKindAdd, upper.path
  49. default:
  50. return ChangeKindModify, upper.path
  51. }
  52. }
  53. func sameFile(f1, f2 *currentPath) (bool, error) {
  54. if os.SameFile(f1.f, f2.f) {
  55. return true, nil
  56. }
  57. equalStat, err := compareSysStat(f1.f.Sys(), f2.f.Sys())
  58. if err != nil || !equalStat {
  59. return equalStat, err
  60. }
  61. if eq, err := compareCapabilities(f1.fullPath, f2.fullPath); err != nil || !eq {
  62. return eq, err
  63. }
  64. // If not a directory also check size, modtime, and content
  65. if !f1.f.IsDir() {
  66. if f1.f.Size() != f2.f.Size() {
  67. return false, nil
  68. }
  69. t1 := f1.f.ModTime()
  70. t2 := f2.f.ModTime()
  71. if t1.Unix() != t2.Unix() {
  72. return false, nil
  73. }
  74. // If the timestamp may have been truncated in both of the
  75. // files, check content of file to determine difference
  76. if t1.Nanosecond() == 0 && t2.Nanosecond() == 0 {
  77. var eq bool
  78. if (f1.f.Mode() & os.ModeSymlink) == os.ModeSymlink {
  79. eq, err = compareSymlinkTarget(f1.fullPath, f2.fullPath)
  80. } else if f1.f.Size() > 0 {
  81. eq, err = compareFileContent(f1.fullPath, f2.fullPath)
  82. }
  83. if err != nil || !eq {
  84. return eq, err
  85. }
  86. } else if t1.Nanosecond() != t2.Nanosecond() {
  87. return false, nil
  88. }
  89. }
  90. return true, nil
  91. }
  92. func compareSymlinkTarget(p1, p2 string) (bool, error) {
  93. t1, err := os.Readlink(p1)
  94. if err != nil {
  95. return false, err
  96. }
  97. t2, err := os.Readlink(p2)
  98. if err != nil {
  99. return false, err
  100. }
  101. return t1 == t2, nil
  102. }
  103. const compareChuckSize = 32 * 1024
  104. // compareFileContent compares the content of 2 same sized files
  105. // by comparing each byte.
  106. func compareFileContent(p1, p2 string) (bool, error) {
  107. f1, err := os.Open(p1)
  108. if err != nil {
  109. return false, err
  110. }
  111. defer f1.Close()
  112. f2, err := os.Open(p2)
  113. if err != nil {
  114. return false, err
  115. }
  116. defer f2.Close()
  117. b1 := make([]byte, compareChuckSize)
  118. b2 := make([]byte, compareChuckSize)
  119. for {
  120. n1, err1 := f1.Read(b1)
  121. if err1 != nil && err1 != io.EOF {
  122. return false, err1
  123. }
  124. n2, err2 := f2.Read(b2)
  125. if err2 != nil && err2 != io.EOF {
  126. return false, err2
  127. }
  128. if n1 != n2 || !bytes.Equal(b1[:n1], b2[:n2]) {
  129. return false, nil
  130. }
  131. if err1 == io.EOF && err2 == io.EOF {
  132. return true, nil
  133. }
  134. }
  135. }
  136. func pathWalk(ctx context.Context, root string, pathC chan<- *currentPath) error {
  137. return filepath.Walk(root, func(path string, f os.FileInfo, err error) error {
  138. if err != nil {
  139. return err
  140. }
  141. // Rebase path
  142. path, err = filepath.Rel(root, path)
  143. if err != nil {
  144. return err
  145. }
  146. path = filepath.Join(string(os.PathSeparator), path)
  147. // Skip root
  148. if path == string(os.PathSeparator) {
  149. return nil
  150. }
  151. p := &currentPath{
  152. path: path,
  153. f: f,
  154. fullPath: filepath.Join(root, path),
  155. }
  156. select {
  157. case <-ctx.Done():
  158. return ctx.Err()
  159. case pathC <- p:
  160. return nil
  161. }
  162. })
  163. }
  164. func nextPath(ctx context.Context, pathC <-chan *currentPath) (*currentPath, error) {
  165. select {
  166. case <-ctx.Done():
  167. return nil, ctx.Err()
  168. case p := <-pathC:
  169. return p, nil
  170. }
  171. }
  172. // RootPath joins a path with a root, evaluating and bounding any
  173. // symlink to the root directory.
  174. func RootPath(root, path string) (string, error) {
  175. if path == "" {
  176. return root, nil
  177. }
  178. var linksWalked int // to protect against cycles
  179. for {
  180. i := linksWalked
  181. newpath, err := walkLinks(root, path, &linksWalked)
  182. if err != nil {
  183. return "", err
  184. }
  185. path = newpath
  186. if i == linksWalked {
  187. newpath = filepath.Join("/", newpath)
  188. if path == newpath {
  189. return filepath.Join(root, newpath), nil
  190. }
  191. path = newpath
  192. }
  193. }
  194. }
  195. func walkLink(root, path string, linksWalked *int) (newpath string, islink bool, err error) {
  196. if *linksWalked > 255 {
  197. return "", false, errTooManyLinks
  198. }
  199. path = filepath.Join("/", path)
  200. if path == "/" {
  201. return path, false, nil
  202. }
  203. realPath := filepath.Join(root, path)
  204. fi, err := os.Lstat(realPath)
  205. if err != nil {
  206. // If path does not yet exist, treat as non-symlink
  207. if os.IsNotExist(err) {
  208. return path, false, nil
  209. }
  210. return "", false, err
  211. }
  212. if fi.Mode()&os.ModeSymlink == 0 {
  213. return path, false, nil
  214. }
  215. newpath, err = os.Readlink(realPath)
  216. if err != nil {
  217. return "", false, err
  218. }
  219. *linksWalked++
  220. return newpath, true, nil
  221. }
  222. func walkLinks(root, path string, linksWalked *int) (string, error) {
  223. switch dir, file := filepath.Split(path); {
  224. case dir == "":
  225. newpath, _, err := walkLink(root, file, linksWalked)
  226. return newpath, err
  227. case file == "":
  228. if os.IsPathSeparator(dir[len(dir)-1]) {
  229. if dir == "/" {
  230. return dir, nil
  231. }
  232. return walkLinks(root, dir[:len(dir)-1], linksWalked)
  233. }
  234. newpath, _, err := walkLink(root, dir, linksWalked)
  235. return newpath, err
  236. default:
  237. newdir, err := walkLinks(root, dir, linksWalked)
  238. if err != nil {
  239. return "", err
  240. }
  241. newpath, islink, err := walkLink(root, filepath.Join(newdir, file), linksWalked)
  242. if err != nil {
  243. return "", err
  244. }
  245. if !islink {
  246. return newpath, nil
  247. }
  248. if filepath.IsAbs(newpath) {
  249. return newpath, nil
  250. }
  251. return filepath.Join(newdir, newpath), nil
  252. }
  253. }