path.go 6.5 KB

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