mount.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377
  1. /*
  2. Copyright 2014 The Kubernetes 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. // TODO(thockin): This whole pkg is pretty linux-centric. As soon as we have
  14. // an alternate platform, we will need to abstract further.
  15. package mount
  16. import (
  17. "fmt"
  18. "os"
  19. "path/filepath"
  20. "strings"
  21. )
  22. type FileType string
  23. const (
  24. // Default mount command if mounter path is not specified
  25. defaultMountCommand = "mount"
  26. FileTypeDirectory FileType = "Directory"
  27. FileTypeFile FileType = "File"
  28. FileTypeSocket FileType = "Socket"
  29. FileTypeCharDev FileType = "CharDevice"
  30. FileTypeBlockDev FileType = "BlockDevice"
  31. )
  32. type Interface interface {
  33. // Mount mounts source to target as fstype with given options.
  34. Mount(source string, target string, fstype string, options []string) error
  35. // Unmount unmounts given target.
  36. Unmount(target string) error
  37. // List returns a list of all mounted filesystems. This can be large.
  38. // On some platforms, reading mounts is not guaranteed consistent (i.e.
  39. // it could change between chunked reads). This is guaranteed to be
  40. // consistent.
  41. List() ([]MountPoint, error)
  42. // IsMountPointMatch determines if the mountpoint matches the dir
  43. IsMountPointMatch(mp MountPoint, dir string) bool
  44. // IsLikelyNotMountPoint uses heuristics to determine if a directory
  45. // is a mountpoint.
  46. // It should return ErrNotExist when the directory does not exist.
  47. // IsLikelyNotMountPoint does NOT properly detect all mountpoint types
  48. // most notably linux bind mounts and symbolic link.
  49. IsLikelyNotMountPoint(file string) (bool, error)
  50. // DeviceOpened determines if the device is in use elsewhere
  51. // on the system, i.e. still mounted.
  52. DeviceOpened(pathname string) (bool, error)
  53. // PathIsDevice determines if a path is a device.
  54. PathIsDevice(pathname string) (bool, error)
  55. // GetDeviceNameFromMount finds the device name by checking the mount path
  56. // to get the global mount path within its plugin directory
  57. GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error)
  58. // MakeRShared checks that given path is on a mount with 'rshared' mount
  59. // propagation. If not, it bind-mounts the path as rshared.
  60. MakeRShared(path string) error
  61. // GetFileType checks for file/directory/socket/block/character devices.
  62. // Will operate in the host mount namespace if kubelet is running in a container
  63. GetFileType(pathname string) (FileType, error)
  64. // MakeFile creates an empty file.
  65. // Will operate in the host mount namespace if kubelet is running in a container
  66. MakeFile(pathname string) error
  67. // MakeDir creates a new directory.
  68. // Will operate in the host mount namespace if kubelet is running in a container
  69. MakeDir(pathname string) error
  70. // Will operate in the host mount namespace if kubelet is running in a container.
  71. // Error is returned on any other error than "file not found".
  72. ExistsPath(pathname string) (bool, error)
  73. // EvalHostSymlinks returns the path name after evaluating symlinks.
  74. // Will operate in the host mount namespace if kubelet is running in a container.
  75. EvalHostSymlinks(pathname string) (string, error)
  76. // GetMountRefs finds all mount references to the path, returns a
  77. // list of paths. Path could be a mountpoint path, device or a normal
  78. // directory (for bind mount).
  79. GetMountRefs(pathname string) ([]string, error)
  80. // GetFSGroup returns FSGroup of the path.
  81. GetFSGroup(pathname string) (int64, error)
  82. // GetSELinuxSupport returns true if given path is on a mount that supports
  83. // SELinux.
  84. GetSELinuxSupport(pathname string) (bool, error)
  85. // GetMode returns permissions of the path.
  86. GetMode(pathname string) (os.FileMode, error)
  87. }
  88. type Subpath struct {
  89. // index of the VolumeMount for this container
  90. VolumeMountIndex int
  91. // Full path to the subpath directory on the host
  92. Path string
  93. // name of the volume that is a valid directory name.
  94. VolumeName string
  95. // Full path to the volume path
  96. VolumePath string
  97. // Path to the pod's directory, including pod UID
  98. PodDir string
  99. // Name of the container
  100. ContainerName string
  101. }
  102. // Exec executes command where mount utilities are. This can be either the host,
  103. // container where kubelet runs or even a remote pod with mount utilities.
  104. // Usual k8s.io/utils/exec interface is not used because kubelet.RunInContainer does
  105. // not provide stdin/stdout/stderr streams.
  106. type Exec interface {
  107. // Run executes a command and returns its stdout + stderr combined in one
  108. // stream.
  109. Run(cmd string, args ...string) ([]byte, error)
  110. }
  111. // Compile-time check to ensure all Mounter implementations satisfy
  112. // the mount interface
  113. var _ Interface = &Mounter{}
  114. // This represents a single line in /proc/mounts or /etc/fstab.
  115. type MountPoint struct {
  116. Device string
  117. Path string
  118. Type string
  119. Opts []string
  120. Freq int
  121. Pass int
  122. }
  123. // SafeFormatAndMount probes a device to see if it is formatted.
  124. // Namely it checks to see if a file system is present. If so it
  125. // mounts it otherwise the device is formatted first then mounted.
  126. type SafeFormatAndMount struct {
  127. Interface
  128. Exec
  129. }
  130. // FormatAndMount formats the given disk, if needed, and mounts it.
  131. // That is if the disk is not formatted and it is not being mounted as
  132. // read-only it will format it first then mount it. Otherwise, if the
  133. // disk is already formatted or it is being mounted as read-only, it
  134. // will be mounted without formatting.
  135. func (mounter *SafeFormatAndMount) FormatAndMount(source string, target string, fstype string, options []string) error {
  136. return mounter.formatAndMount(source, target, fstype, options)
  137. }
  138. // getMountRefsByDev finds all references to the device provided
  139. // by mountPath; returns a list of paths.
  140. // Note that mountPath should be path after the evaluation of any symblolic links.
  141. func getMountRefsByDev(mounter Interface, mountPath string) ([]string, error) {
  142. mps, err := mounter.List()
  143. if err != nil {
  144. return nil, err
  145. }
  146. // Finding the device mounted to mountPath
  147. diskDev := ""
  148. for i := range mps {
  149. if mountPath == mps[i].Path {
  150. diskDev = mps[i].Device
  151. break
  152. }
  153. }
  154. // Find all references to the device.
  155. var refs []string
  156. for i := range mps {
  157. if mps[i].Device == diskDev || mps[i].Device == mountPath {
  158. if mps[i].Path != mountPath {
  159. refs = append(refs, mps[i].Path)
  160. }
  161. }
  162. }
  163. return refs, nil
  164. }
  165. // GetDeviceNameFromMount: given a mnt point, find the device from /proc/mounts
  166. // returns the device name, reference count, and error code
  167. func GetDeviceNameFromMount(mounter Interface, mountPath string) (string, int, error) {
  168. mps, err := mounter.List()
  169. if err != nil {
  170. return "", 0, err
  171. }
  172. // Find the device name.
  173. // FIXME if multiple devices mounted on the same mount path, only the first one is returned
  174. device := ""
  175. // If mountPath is symlink, need get its target path.
  176. slTarget, err := filepath.EvalSymlinks(mountPath)
  177. if err != nil {
  178. slTarget = mountPath
  179. }
  180. for i := range mps {
  181. if mps[i].Path == slTarget {
  182. device = mps[i].Device
  183. break
  184. }
  185. }
  186. // Find all references to the device.
  187. refCount := 0
  188. for i := range mps {
  189. if mps[i].Device == device {
  190. refCount++
  191. }
  192. }
  193. return device, refCount, nil
  194. }
  195. // IsNotMountPoint determines if a directory is a mountpoint.
  196. // It should return ErrNotExist when the directory does not exist.
  197. // IsNotMountPoint is more expensive than IsLikelyNotMountPoint.
  198. // IsNotMountPoint detects bind mounts in linux.
  199. // IsNotMountPoint enumerates all the mountpoints using List() and
  200. // the list of mountpoints may be large, then it uses
  201. // IsMountPointMatch to evaluate whether the directory is a mountpoint
  202. func IsNotMountPoint(mounter Interface, file string) (bool, error) {
  203. // IsLikelyNotMountPoint provides a quick check
  204. // to determine whether file IS A mountpoint
  205. notMnt, notMntErr := mounter.IsLikelyNotMountPoint(file)
  206. if notMntErr != nil && os.IsPermission(notMntErr) {
  207. // We were not allowed to do the simple stat() check, e.g. on NFS with
  208. // root_squash. Fall back to /proc/mounts check below.
  209. notMnt = true
  210. notMntErr = nil
  211. }
  212. if notMntErr != nil {
  213. return notMnt, notMntErr
  214. }
  215. // identified as mountpoint, so return this fact
  216. if notMnt == false {
  217. return notMnt, nil
  218. }
  219. // Resolve any symlinks in file, kernel would do the same and use the resolved path in /proc/mounts
  220. resolvedFile, err := mounter.EvalHostSymlinks(file)
  221. if err != nil {
  222. return true, err
  223. }
  224. // check all mountpoints since IsLikelyNotMountPoint
  225. // is not reliable for some mountpoint types
  226. mountPoints, mountPointsErr := mounter.List()
  227. if mountPointsErr != nil {
  228. return notMnt, mountPointsErr
  229. }
  230. for _, mp := range mountPoints {
  231. if mounter.IsMountPointMatch(mp, resolvedFile) {
  232. notMnt = false
  233. break
  234. }
  235. }
  236. return notMnt, nil
  237. }
  238. // IsBind detects whether a bind mount is being requested and makes the remount options to
  239. // use in case of bind mount, due to the fact that bind mount doesn't respect mount options.
  240. // The list equals:
  241. // options - 'bind' + 'remount' (no duplicate)
  242. func IsBind(options []string) (bool, []string, []string) {
  243. // Because we have an FD opened on the subpath bind mount, the "bind" option
  244. // needs to be included, otherwise the mount target will error as busy if you
  245. // remount as readonly.
  246. //
  247. // As a consequence, all read only bind mounts will no longer change the underlying
  248. // volume mount to be read only.
  249. bindRemountOpts := []string{"bind", "remount"}
  250. bind := false
  251. bindOpts := []string{"bind"}
  252. // _netdev is a userspace mount option and does not automatically get added when
  253. // bind mount is created and hence we must carry it over.
  254. if checkForNetDev(options) {
  255. bindOpts = append(bindOpts, "_netdev")
  256. }
  257. for _, option := range options {
  258. switch option {
  259. case "bind":
  260. bind = true
  261. break
  262. case "remount":
  263. break
  264. default:
  265. bindRemountOpts = append(bindRemountOpts, option)
  266. }
  267. }
  268. return bind, bindOpts, bindRemountOpts
  269. }
  270. func checkForNetDev(options []string) bool {
  271. for _, option := range options {
  272. if option == "_netdev" {
  273. return true
  274. }
  275. }
  276. return false
  277. }
  278. // TODO: this is a workaround for the unmount device issue caused by gci mounter.
  279. // In GCI cluster, if gci mounter is used for mounting, the container started by mounter
  280. // script will cause additional mounts created in the container. Since these mounts are
  281. // irrelevant to the original mounts, they should be not considered when checking the
  282. // mount references. Current solution is to filter out those mount paths that contain
  283. // the string of original mount path.
  284. // Plan to work on better approach to solve this issue.
  285. func HasMountRefs(mountPath string, mountRefs []string) bool {
  286. count := 0
  287. for _, ref := range mountRefs {
  288. if !strings.Contains(ref, mountPath) {
  289. count = count + 1
  290. }
  291. }
  292. return count > 0
  293. }
  294. // PathWithinBase checks if give path is within given base directory.
  295. func PathWithinBase(fullPath, basePath string) bool {
  296. rel, err := filepath.Rel(basePath, fullPath)
  297. if err != nil {
  298. return false
  299. }
  300. if StartsWithBackstep(rel) {
  301. // Needed to escape the base path
  302. return false
  303. }
  304. return true
  305. }
  306. // StartsWithBackstep checks if the given path starts with a backstep segment
  307. func StartsWithBackstep(rel string) bool {
  308. // normalize to / and check for ../
  309. return rel == ".." || strings.HasPrefix(filepath.ToSlash(rel), "../")
  310. }
  311. // getFileType checks for file/directory/socket and block/character devices
  312. func getFileType(pathname string) (FileType, error) {
  313. var pathType FileType
  314. info, err := os.Stat(pathname)
  315. if os.IsNotExist(err) {
  316. return pathType, fmt.Errorf("path %q does not exist", pathname)
  317. }
  318. // err in call to os.Stat
  319. if err != nil {
  320. return pathType, err
  321. }
  322. // checks whether the mode is the target mode
  323. isSpecificMode := func(mode, targetMode os.FileMode) bool {
  324. return mode&targetMode == targetMode
  325. }
  326. mode := info.Mode()
  327. if mode.IsDir() {
  328. return FileTypeDirectory, nil
  329. } else if mode.IsRegular() {
  330. return FileTypeFile, nil
  331. } else if isSpecificMode(mode, os.ModeSocket) {
  332. return FileTypeSocket, nil
  333. } else if isSpecificMode(mode, os.ModeDevice) {
  334. if isSpecificMode(mode, os.ModeCharDevice) {
  335. return FileTypeCharDev, nil
  336. }
  337. return FileTypeBlockDev, nil
  338. }
  339. return pathType, fmt.Errorf("only recognise file, directory, socket, block device and character device")
  340. }