path.go 6.5 KB

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