subpath_linux.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. // +build linux
  2. /*
  3. Copyright 2014 The Kubernetes Authors.
  4. Licensed under the Apache License, Version 2.0 (the "License");
  5. you may not use this file except in compliance with the License.
  6. You may obtain a copy of the License at
  7. http://www.apache.org/licenses/LICENSE-2.0
  8. Unless required by applicable law or agreed to in writing, software
  9. distributed under the License is distributed on an "AS IS" BASIS,
  10. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. See the License for the specific language governing permissions and
  12. limitations under the License.
  13. */
  14. package subpath
  15. import (
  16. "fmt"
  17. "io/ioutil"
  18. "os"
  19. "path/filepath"
  20. "strconv"
  21. "strings"
  22. "syscall"
  23. "golang.org/x/sys/unix"
  24. "k8s.io/klog"
  25. "k8s.io/utils/mount"
  26. )
  27. const (
  28. // place for subpath mounts
  29. // TODO: pass in directory using kubelet_getters instead
  30. containerSubPathDirectoryName = "volume-subpaths"
  31. // syscall.Openat flags used to traverse directories not following symlinks
  32. nofollowFlags = unix.O_RDONLY | unix.O_NOFOLLOW
  33. // flags for getting file descriptor without following the symlink
  34. openFDFlags = unix.O_NOFOLLOW | unix.O_PATH
  35. )
  36. type subpath struct {
  37. mounter mount.Interface
  38. }
  39. // New returns a subpath.Interface for the current system
  40. func New(mounter mount.Interface) Interface {
  41. return &subpath{
  42. mounter: mounter,
  43. }
  44. }
  45. func (sp *subpath) CleanSubPaths(podDir string, volumeName string) error {
  46. return doCleanSubPaths(sp.mounter, podDir, volumeName)
  47. }
  48. func (sp *subpath) SafeMakeDir(subdir string, base string, perm os.FileMode) error {
  49. realBase, err := filepath.EvalSymlinks(base)
  50. if err != nil {
  51. return fmt.Errorf("error resolving symlinks in %s: %s", base, err)
  52. }
  53. realFullPath := filepath.Join(realBase, subdir)
  54. return doSafeMakeDir(realFullPath, realBase, perm)
  55. }
  56. func (sp *subpath) PrepareSafeSubpath(subPath Subpath) (newHostPath string, cleanupAction func(), err error) {
  57. newHostPath, err = doBindSubPath(sp.mounter, subPath)
  58. // There is no action when the container starts. Bind-mount will be cleaned
  59. // when container stops by CleanSubPaths.
  60. cleanupAction = nil
  61. return newHostPath, cleanupAction, err
  62. }
  63. // This implementation is shared between Linux and NsEnter
  64. func safeOpenSubPath(mounter mount.Interface, subpath Subpath) (int, error) {
  65. if !mount.PathWithinBase(subpath.Path, subpath.VolumePath) {
  66. return -1, fmt.Errorf("subpath %q not within volume path %q", subpath.Path, subpath.VolumePath)
  67. }
  68. fd, err := doSafeOpen(subpath.Path, subpath.VolumePath)
  69. if err != nil {
  70. return -1, fmt.Errorf("error opening subpath %v: %v", subpath.Path, err)
  71. }
  72. return fd, nil
  73. }
  74. // prepareSubpathTarget creates target for bind-mount of subpath. It returns
  75. // "true" when the target already exists and something is mounted there.
  76. // Given Subpath must have all paths with already resolved symlinks and with
  77. // paths relevant to kubelet (when it runs in a container).
  78. // This function is called also by NsEnterMounter. It works because
  79. // /var/lib/kubelet is mounted from the host into the container with Kubelet as
  80. // /var/lib/kubelet too.
  81. func prepareSubpathTarget(mounter mount.Interface, subpath Subpath) (bool, string, error) {
  82. // Early check for already bind-mounted subpath.
  83. bindPathTarget := getSubpathBindTarget(subpath)
  84. notMount, err := mount.IsNotMountPoint(mounter, bindPathTarget)
  85. if err != nil {
  86. if !os.IsNotExist(err) {
  87. return false, "", fmt.Errorf("error checking path %s for mount: %s", bindPathTarget, err)
  88. }
  89. // Ignore ErrorNotExist: the file/directory will be created below if it does not exist yet.
  90. notMount = true
  91. }
  92. if !notMount {
  93. // It's already mounted
  94. klog.V(5).Infof("Skipping bind-mounting subpath %s: already mounted", bindPathTarget)
  95. return true, bindPathTarget, nil
  96. }
  97. // bindPathTarget is in /var/lib/kubelet and thus reachable without any
  98. // translation even to containerized kubelet.
  99. bindParent := filepath.Dir(bindPathTarget)
  100. err = os.MkdirAll(bindParent, 0750)
  101. if err != nil && !os.IsExist(err) {
  102. return false, "", fmt.Errorf("error creating directory %s: %s", bindParent, err)
  103. }
  104. t, err := os.Lstat(subpath.Path)
  105. if err != nil {
  106. return false, "", fmt.Errorf("lstat %s failed: %s", subpath.Path, err)
  107. }
  108. if t.Mode()&os.ModeDir > 0 {
  109. if err = os.Mkdir(bindPathTarget, 0750); err != nil && !os.IsExist(err) {
  110. return false, "", fmt.Errorf("error creating directory %s: %s", bindPathTarget, err)
  111. }
  112. } else {
  113. // "/bin/touch <bindPathTarget>".
  114. // A file is enough for all possible targets (symlink, device, pipe,
  115. // socket, ...), bind-mounting them into a file correctly changes type
  116. // of the target file.
  117. if err = ioutil.WriteFile(bindPathTarget, []byte{}, 0640); err != nil {
  118. return false, "", fmt.Errorf("error creating file %s: %s", bindPathTarget, err)
  119. }
  120. }
  121. return false, bindPathTarget, nil
  122. }
  123. func getSubpathBindTarget(subpath Subpath) string {
  124. // containerName is DNS label, i.e. safe as a directory name.
  125. return filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName, strconv.Itoa(subpath.VolumeMountIndex))
  126. }
  127. func doBindSubPath(mounter mount.Interface, subpath Subpath) (hostPath string, err error) {
  128. // Linux, kubelet runs on the host:
  129. // - safely open the subpath
  130. // - bind-mount /proc/<pid of kubelet>/fd/<fd> to subpath target
  131. // User can't change /proc/<pid of kubelet>/fd/<fd> to point to a bad place.
  132. // Evaluate all symlinks here once for all subsequent functions.
  133. newVolumePath, err := filepath.EvalSymlinks(subpath.VolumePath)
  134. if err != nil {
  135. return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.VolumePath, err)
  136. }
  137. newPath, err := filepath.EvalSymlinks(subpath.Path)
  138. if err != nil {
  139. return "", fmt.Errorf("error resolving symlinks in %q: %v", subpath.Path, err)
  140. }
  141. klog.V(5).Infof("doBindSubPath %q (%q) for volumepath %q", subpath.Path, newPath, subpath.VolumePath)
  142. subpath.VolumePath = newVolumePath
  143. subpath.Path = newPath
  144. fd, err := safeOpenSubPath(mounter, subpath)
  145. if err != nil {
  146. return "", err
  147. }
  148. defer syscall.Close(fd)
  149. alreadyMounted, bindPathTarget, err := prepareSubpathTarget(mounter, subpath)
  150. if err != nil {
  151. return "", err
  152. }
  153. if alreadyMounted {
  154. return bindPathTarget, nil
  155. }
  156. success := false
  157. defer func() {
  158. // Cleanup subpath on error
  159. if !success {
  160. klog.V(4).Infof("doBindSubPath() failed for %q, cleaning up subpath", bindPathTarget)
  161. if cleanErr := cleanSubPath(mounter, subpath); cleanErr != nil {
  162. klog.Errorf("Failed to clean subpath %q: %v", bindPathTarget, cleanErr)
  163. }
  164. }
  165. }()
  166. kubeletPid := os.Getpid()
  167. mountSource := fmt.Sprintf("/proc/%d/fd/%v", kubeletPid, fd)
  168. // Do the bind mount
  169. options := []string{"bind"}
  170. klog.V(5).Infof("bind mounting %q at %q", mountSource, bindPathTarget)
  171. if err = mounter.Mount(mountSource, bindPathTarget, "" /*fstype*/, options); err != nil {
  172. return "", fmt.Errorf("error mounting %s: %s", subpath.Path, err)
  173. }
  174. success = true
  175. klog.V(3).Infof("Bound SubPath %s into %s", subpath.Path, bindPathTarget)
  176. return bindPathTarget, nil
  177. }
  178. // This implementation is shared between Linux and NsEnter
  179. func doCleanSubPaths(mounter mount.Interface, podDir string, volumeName string) error {
  180. // scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/*
  181. subPathDir := filepath.Join(podDir, containerSubPathDirectoryName, volumeName)
  182. klog.V(4).Infof("Cleaning up subpath mounts for %s", subPathDir)
  183. containerDirs, err := ioutil.ReadDir(subPathDir)
  184. if err != nil {
  185. if os.IsNotExist(err) {
  186. return nil
  187. }
  188. return fmt.Errorf("error reading %s: %s", subPathDir, err)
  189. }
  190. for _, containerDir := range containerDirs {
  191. if !containerDir.IsDir() {
  192. klog.V(4).Infof("Container file is not a directory: %s", containerDir.Name())
  193. continue
  194. }
  195. klog.V(4).Infof("Cleaning up subpath mounts for container %s", containerDir.Name())
  196. // scan /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/*
  197. fullContainerDirPath := filepath.Join(subPathDir, containerDir.Name())
  198. err = filepath.Walk(fullContainerDirPath, func(path string, info os.FileInfo, err error) error {
  199. if path == fullContainerDirPath {
  200. // Skip top level directory
  201. return nil
  202. }
  203. // pass through errors and let doCleanSubPath handle them
  204. if err = doCleanSubPath(mounter, fullContainerDirPath, filepath.Base(path)); err != nil {
  205. return err
  206. }
  207. // We need to check that info is not nil. This may happen when the incoming err is not nil due to stale mounts or permission errors.
  208. if info != nil && info.IsDir() {
  209. // skip subdirs of the volume: it only matters the first level to unmount, otherwise it would try to unmount subdir of the volume
  210. return filepath.SkipDir
  211. }
  212. return nil
  213. })
  214. if err != nil {
  215. return fmt.Errorf("error processing %s: %s", fullContainerDirPath, err)
  216. }
  217. // Whole container has been processed, remove its directory.
  218. if err := os.Remove(fullContainerDirPath); err != nil {
  219. return fmt.Errorf("error deleting %s: %s", fullContainerDirPath, err)
  220. }
  221. klog.V(5).Infof("Removed %s", fullContainerDirPath)
  222. }
  223. // Whole pod volume subpaths have been cleaned up, remove its subpath directory.
  224. if err := os.Remove(subPathDir); err != nil {
  225. return fmt.Errorf("error deleting %s: %s", subPathDir, err)
  226. }
  227. klog.V(5).Infof("Removed %s", subPathDir)
  228. // Remove entire subpath directory if it's the last one
  229. podSubPathDir := filepath.Join(podDir, containerSubPathDirectoryName)
  230. if err := os.Remove(podSubPathDir); err != nil && !os.IsExist(err) {
  231. return fmt.Errorf("error deleting %s: %s", podSubPathDir, err)
  232. }
  233. klog.V(5).Infof("Removed %s", podSubPathDir)
  234. return nil
  235. }
  236. // doCleanSubPath tears down the single subpath bind mount
  237. func doCleanSubPath(mounter mount.Interface, fullContainerDirPath, subPathIndex string) error {
  238. // process /var/lib/kubelet/pods/<uid>/volume-subpaths/<volume>/<container name>/<subPathName>
  239. klog.V(4).Infof("Cleaning up subpath mounts for subpath %v", subPathIndex)
  240. fullSubPath := filepath.Join(fullContainerDirPath, subPathIndex)
  241. if err := mount.CleanupMountPoint(fullSubPath, mounter, true); err != nil {
  242. return fmt.Errorf("error cleaning subpath mount %s: %s", fullSubPath, err)
  243. }
  244. klog.V(4).Infof("Successfully cleaned subpath directory %s", fullSubPath)
  245. return nil
  246. }
  247. // cleanSubPath will teardown the subpath bind mount and any remove any directories if empty
  248. func cleanSubPath(mounter mount.Interface, subpath Subpath) error {
  249. containerDir := filepath.Join(subpath.PodDir, containerSubPathDirectoryName, subpath.VolumeName, subpath.ContainerName)
  250. // Clean subdir bindmount
  251. if err := doCleanSubPath(mounter, containerDir, strconv.Itoa(subpath.VolumeMountIndex)); err != nil && !os.IsNotExist(err) {
  252. return err
  253. }
  254. // Recusively remove directories if empty
  255. if err := removeEmptyDirs(subpath.PodDir, containerDir); err != nil {
  256. return err
  257. }
  258. return nil
  259. }
  260. // removeEmptyDirs works backwards from endDir to baseDir and removes each directory
  261. // if it is empty. It stops once it encounters a directory that has content
  262. func removeEmptyDirs(baseDir, endDir string) error {
  263. if !mount.PathWithinBase(endDir, baseDir) {
  264. return fmt.Errorf("endDir %q is not within baseDir %q", endDir, baseDir)
  265. }
  266. for curDir := endDir; curDir != baseDir; curDir = filepath.Dir(curDir) {
  267. s, err := os.Stat(curDir)
  268. if err != nil {
  269. if os.IsNotExist(err) {
  270. klog.V(5).Infof("curDir %q doesn't exist, skipping", curDir)
  271. continue
  272. }
  273. return fmt.Errorf("error stat %q: %v", curDir, err)
  274. }
  275. if !s.IsDir() {
  276. return fmt.Errorf("path %q not a directory", curDir)
  277. }
  278. err = os.Remove(curDir)
  279. if os.IsExist(err) {
  280. klog.V(5).Infof("Directory %q not empty, not removing", curDir)
  281. break
  282. } else if err != nil {
  283. return fmt.Errorf("error removing directory %q: %v", curDir, err)
  284. }
  285. klog.V(5).Infof("Removed directory %q", curDir)
  286. }
  287. return nil
  288. }
  289. // This implementation is shared between Linux and NsEnterMounter. Both pathname
  290. // and base must be either already resolved symlinks or thet will be resolved in
  291. // kubelet's mount namespace (in case it runs containerized).
  292. func doSafeMakeDir(pathname string, base string, perm os.FileMode) error {
  293. klog.V(4).Infof("Creating directory %q within base %q", pathname, base)
  294. if !mount.PathWithinBase(pathname, base) {
  295. return fmt.Errorf("path %s is outside of allowed base %s", pathname, base)
  296. }
  297. // Quick check if the directory already exists
  298. s, err := os.Stat(pathname)
  299. if err == nil {
  300. // Path exists
  301. if s.IsDir() {
  302. // The directory already exists. It can be outside of the parent,
  303. // but there is no race-proof check.
  304. klog.V(4).Infof("Directory %s already exists", pathname)
  305. return nil
  306. }
  307. return &os.PathError{Op: "mkdir", Path: pathname, Err: syscall.ENOTDIR}
  308. }
  309. // Find all existing directories
  310. existingPath, toCreate, err := findExistingPrefix(base, pathname)
  311. if err != nil {
  312. return fmt.Errorf("error opening directory %s: %s", pathname, err)
  313. }
  314. // Ensure the existing directory is inside allowed base
  315. fullExistingPath, err := filepath.EvalSymlinks(existingPath)
  316. if err != nil {
  317. return fmt.Errorf("error opening directory %s: %s", existingPath, err)
  318. }
  319. if !mount.PathWithinBase(fullExistingPath, base) {
  320. return fmt.Errorf("path %s is outside of allowed base %s", fullExistingPath, err)
  321. }
  322. klog.V(4).Infof("%q already exists, %q to create", fullExistingPath, filepath.Join(toCreate...))
  323. parentFD, err := doSafeOpen(fullExistingPath, base)
  324. if err != nil {
  325. return fmt.Errorf("cannot open directory %s: %s", existingPath, err)
  326. }
  327. childFD := -1
  328. defer func() {
  329. if parentFD != -1 {
  330. if err = syscall.Close(parentFD); err != nil {
  331. klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
  332. }
  333. }
  334. if childFD != -1 {
  335. if err = syscall.Close(childFD); err != nil {
  336. klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", childFD, pathname, err)
  337. }
  338. }
  339. }()
  340. currentPath := fullExistingPath
  341. // create the directories one by one, making sure nobody can change
  342. // created directory into symlink.
  343. for _, dir := range toCreate {
  344. currentPath = filepath.Join(currentPath, dir)
  345. klog.V(4).Infof("Creating %s", dir)
  346. err = syscall.Mkdirat(parentFD, currentPath, uint32(perm))
  347. if err != nil {
  348. return fmt.Errorf("cannot create directory %s: %s", currentPath, err)
  349. }
  350. // Dive into the created directory
  351. childFD, err = syscall.Openat(parentFD, dir, nofollowFlags|unix.O_CLOEXEC, 0)
  352. if err != nil {
  353. return fmt.Errorf("cannot open %s: %s", currentPath, err)
  354. }
  355. // We can be sure that childFD is safe to use. It could be changed
  356. // by user after Mkdirat() and before Openat(), however:
  357. // - it could not be changed to symlink - we use nofollowFlags
  358. // - it could be changed to a file (or device, pipe, socket, ...)
  359. // but either subsequent Mkdirat() fails or we mount this file
  360. // to user's container. Security is no violated in both cases
  361. // and user either gets error or the file that it can already access.
  362. if err = syscall.Close(parentFD); err != nil {
  363. klog.V(4).Infof("Closing FD %v failed for safemkdir(%v): %v", parentFD, pathname, err)
  364. }
  365. parentFD = childFD
  366. childFD = -1
  367. }
  368. // Everything was created. mkdirat(..., perm) above was affected by current
  369. // umask and we must apply the right permissions to the last directory
  370. // (that's the one that will be available to the container as subpath)
  371. // so user can read/write it. This is the behavior of previous code.
  372. // TODO: chmod all created directories, not just the last one.
  373. // parentFD is the last created directory.
  374. // Translate perm (os.FileMode) to uint32 that fchmod() expects
  375. kernelPerm := uint32(perm & os.ModePerm)
  376. if perm&os.ModeSetgid > 0 {
  377. kernelPerm |= syscall.S_ISGID
  378. }
  379. if perm&os.ModeSetuid > 0 {
  380. kernelPerm |= syscall.S_ISUID
  381. }
  382. if perm&os.ModeSticky > 0 {
  383. kernelPerm |= syscall.S_ISVTX
  384. }
  385. if err = syscall.Fchmod(parentFD, kernelPerm); err != nil {
  386. return fmt.Errorf("chmod %q failed: %s", currentPath, err)
  387. }
  388. return nil
  389. }
  390. // findExistingPrefix finds prefix of pathname that exists. In addition, it
  391. // returns list of remaining directories that don't exist yet.
  392. func findExistingPrefix(base, pathname string) (string, []string, error) {
  393. rel, err := filepath.Rel(base, pathname)
  394. if err != nil {
  395. return base, nil, err
  396. }
  397. dirs := strings.Split(rel, string(filepath.Separator))
  398. // Do OpenAt in a loop to find the first non-existing dir. Resolve symlinks.
  399. // This should be faster than looping through all dirs and calling os.Stat()
  400. // on each of them, as the symlinks are resolved only once with OpenAt().
  401. currentPath := base
  402. fd, err := syscall.Open(currentPath, syscall.O_RDONLY|syscall.O_CLOEXEC, 0)
  403. if err != nil {
  404. return pathname, nil, fmt.Errorf("error opening %s: %s", currentPath, err)
  405. }
  406. defer func() {
  407. if err = syscall.Close(fd); err != nil {
  408. klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
  409. }
  410. }()
  411. for i, dir := range dirs {
  412. // Using O_PATH here will prevent hangs in case user replaces directory with
  413. // fifo
  414. childFD, err := syscall.Openat(fd, dir, unix.O_PATH|unix.O_CLOEXEC, 0)
  415. if err != nil {
  416. if os.IsNotExist(err) {
  417. return currentPath, dirs[i:], nil
  418. }
  419. return base, nil, err
  420. }
  421. if err = syscall.Close(fd); err != nil {
  422. klog.V(4).Infof("Closing FD %v failed for findExistingPrefix(%v): %v", fd, pathname, err)
  423. }
  424. fd = childFD
  425. currentPath = filepath.Join(currentPath, dir)
  426. }
  427. return pathname, []string{}, nil
  428. }
  429. // This implementation is shared between Linux and NsEnterMounter
  430. // Open path and return its fd.
  431. // Symlinks are disallowed (pathname must already resolve symlinks),
  432. // and the path must be within the base directory.
  433. func doSafeOpen(pathname string, base string) (int, error) {
  434. pathname = filepath.Clean(pathname)
  435. base = filepath.Clean(base)
  436. // Calculate segments to follow
  437. subpath, err := filepath.Rel(base, pathname)
  438. if err != nil {
  439. return -1, err
  440. }
  441. segments := strings.Split(subpath, string(filepath.Separator))
  442. // Assumption: base is the only directory that we have under control.
  443. // Base dir is not allowed to be a symlink.
  444. parentFD, err := syscall.Open(base, nofollowFlags|unix.O_CLOEXEC, 0)
  445. if err != nil {
  446. return -1, fmt.Errorf("cannot open directory %s: %s", base, err)
  447. }
  448. defer func() {
  449. if parentFD != -1 {
  450. if err = syscall.Close(parentFD); err != nil {
  451. klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", parentFD, pathname, err)
  452. }
  453. }
  454. }()
  455. childFD := -1
  456. defer func() {
  457. if childFD != -1 {
  458. if err = syscall.Close(childFD); err != nil {
  459. klog.V(4).Infof("Closing FD %v failed for safeopen(%v): %v", childFD, pathname, err)
  460. }
  461. }
  462. }()
  463. currentPath := base
  464. // Follow the segments one by one using openat() to make
  465. // sure the user cannot change already existing directories into symlinks.
  466. for _, seg := range segments {
  467. currentPath = filepath.Join(currentPath, seg)
  468. if !mount.PathWithinBase(currentPath, base) {
  469. return -1, fmt.Errorf("path %s is outside of allowed base %s", currentPath, base)
  470. }
  471. klog.V(5).Infof("Opening path %s", currentPath)
  472. childFD, err = syscall.Openat(parentFD, seg, openFDFlags|unix.O_CLOEXEC, 0)
  473. if err != nil {
  474. return -1, fmt.Errorf("cannot open %s: %s", currentPath, err)
  475. }
  476. var deviceStat unix.Stat_t
  477. err := unix.Fstat(childFD, &deviceStat)
  478. if err != nil {
  479. return -1, fmt.Errorf("Error running fstat on %s with %v", currentPath, err)
  480. }
  481. fileFmt := deviceStat.Mode & syscall.S_IFMT
  482. if fileFmt == syscall.S_IFLNK {
  483. return -1, fmt.Errorf("Unexpected symlink found %s", currentPath)
  484. }
  485. // Close parentFD
  486. if err = syscall.Close(parentFD); err != nil {
  487. return -1, fmt.Errorf("closing fd for %q failed: %v", filepath.Dir(currentPath), err)
  488. }
  489. // Set child to new parent
  490. parentFD = childFD
  491. childFD = -1
  492. }
  493. // We made it to the end, return this fd, don't close it
  494. finalFD := parentFD
  495. parentFD = -1
  496. return finalFD, nil
  497. }