mount_linux.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372
  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 mount
  15. import (
  16. "bufio"
  17. "fmt"
  18. "hash/adler32"
  19. "io"
  20. "os"
  21. "os/exec"
  22. "strconv"
  23. "strings"
  24. "syscall"
  25. "github.com/golang/glog"
  26. utilExec "k8s.io/kubernetes/pkg/util/exec"
  27. )
  28. const (
  29. // How many times to retry for a consistent read of /proc/mounts.
  30. maxListTries = 3
  31. // Number of fields per line in /proc/mounts as per the fstab man page.
  32. expectedNumFieldsPerLine = 6
  33. // Location of the mount file to use
  34. procMountsPath = "/proc/mounts"
  35. )
  36. const (
  37. // 'fsck' found errors and corrected them
  38. fsckErrorsCorrected = 1
  39. // 'fsck' found errors but exited without correcting them
  40. fsckErrorsUncorrected = 4
  41. )
  42. // Mounter provides the default implementation of mount.Interface
  43. // for the linux platform. This implementation assumes that the
  44. // kubelet is running in the host's root mount namespace.
  45. type Mounter struct{}
  46. // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must
  47. // be an emtpy string in case it's not required, e.g. for remount, or for auto filesystem
  48. // type, where kernel handles fs type for you. The mount 'options' is a list of options,
  49. // currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is
  50. // required, call Mount with an empty string list or nil.
  51. func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
  52. bind, bindRemountOpts := isBind(options)
  53. if bind {
  54. err := doMount(source, target, fstype, []string{"bind"})
  55. if err != nil {
  56. return err
  57. }
  58. return doMount(source, target, fstype, bindRemountOpts)
  59. } else {
  60. return doMount(source, target, fstype, options)
  61. }
  62. }
  63. // isBind detects whether a bind mount is being requested and makes the remount options to
  64. // use in case of bind mount, due to the fact that bind mount doesn't respect mount options.
  65. // The list equals:
  66. // options - 'bind' + 'remount' (no duplicate)
  67. func isBind(options []string) (bool, []string) {
  68. bindRemountOpts := []string{"remount"}
  69. bind := false
  70. if len(options) != 0 {
  71. for _, option := range options {
  72. switch option {
  73. case "bind":
  74. bind = true
  75. break
  76. case "remount":
  77. break
  78. default:
  79. bindRemountOpts = append(bindRemountOpts, option)
  80. }
  81. }
  82. }
  83. return bind, bindRemountOpts
  84. }
  85. // doMount runs the mount command.
  86. func doMount(source string, target string, fstype string, options []string) error {
  87. glog.V(5).Infof("Mounting %s %s %s %v", source, target, fstype, options)
  88. mountArgs := makeMountArgs(source, target, fstype, options)
  89. command := exec.Command("mount", mountArgs...)
  90. output, err := command.CombinedOutput()
  91. if err != nil {
  92. glog.Errorf("Mount failed: %v\nMounting arguments: %s %s %s %v\nOutput: %s\n", err, source, target, fstype, options, string(output))
  93. return fmt.Errorf("mount failed: %v\nMounting arguments: %s %s %s %v\nOutput: %s\n",
  94. err, source, target, fstype, options, string(output))
  95. }
  96. return err
  97. }
  98. // makeMountArgs makes the arguments to the mount(8) command.
  99. func makeMountArgs(source, target, fstype string, options []string) []string {
  100. // Build mount command as follows:
  101. // mount [-t $fstype] [-o $options] [$source] $target
  102. mountArgs := []string{}
  103. if len(fstype) > 0 {
  104. mountArgs = append(mountArgs, "-t", fstype)
  105. }
  106. if len(options) > 0 {
  107. mountArgs = append(mountArgs, "-o", strings.Join(options, ","))
  108. }
  109. if len(source) > 0 {
  110. mountArgs = append(mountArgs, source)
  111. }
  112. mountArgs = append(mountArgs, target)
  113. return mountArgs
  114. }
  115. // Unmount unmounts the target.
  116. func (mounter *Mounter) Unmount(target string) error {
  117. glog.V(5).Infof("Unmounting %s", target)
  118. command := exec.Command("umount", target)
  119. output, err := command.CombinedOutput()
  120. if err != nil {
  121. return fmt.Errorf("Unmount failed: %v\nUnmounting arguments: %s\nOutput: %s\n", err, target, string(output))
  122. }
  123. return nil
  124. }
  125. // List returns a list of all mounted filesystems.
  126. func (*Mounter) List() ([]MountPoint, error) {
  127. return listProcMounts(procMountsPath)
  128. }
  129. // IsLikelyNotMountPoint determines if a directory is not a mountpoint.
  130. // It is fast but not necessarily ALWAYS correct. If the path is in fact
  131. // a bind mount from one part of a mount to another it will not be detected.
  132. // mkdir /tmp/a /tmp/b; mount --bin /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b")
  133. // will return true. When in fact /tmp/b is a mount point. If this situation
  134. // if of interest to you, don't use this function...
  135. func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
  136. stat, err := os.Stat(file)
  137. if err != nil {
  138. return true, err
  139. }
  140. rootStat, err := os.Lstat(file + "/..")
  141. if err != nil {
  142. return true, err
  143. }
  144. // If the directory has a different device as parent, then it is a mountpoint.
  145. if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
  146. return false, nil
  147. }
  148. return true, nil
  149. }
  150. // DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
  151. // Returns true if open returns errno EBUSY, and false if errno is nil.
  152. // Returns an error if errno is any error other than EBUSY.
  153. // Returns with error if pathname is not a device.
  154. func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
  155. return exclusiveOpenFailsOnDevice(pathname)
  156. }
  157. // PathIsDevice uses FileInfo returned from os.Stat to check if path refers
  158. // to a device.
  159. func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
  160. return pathIsDevice(pathname)
  161. }
  162. func exclusiveOpenFailsOnDevice(pathname string) (bool, error) {
  163. if isDevice, err := pathIsDevice(pathname); !isDevice {
  164. return false, fmt.Errorf(
  165. "PathIsDevice failed for path %q: %v",
  166. pathname,
  167. err)
  168. }
  169. fd, errno := syscall.Open(pathname, syscall.O_RDONLY|syscall.O_EXCL, 0)
  170. // If the device is in use, open will return an invalid fd.
  171. // When this happens, it is expected that Close will fail and throw an error.
  172. defer syscall.Close(fd)
  173. if errno == nil {
  174. // device not in use
  175. return false, nil
  176. } else if errno == syscall.EBUSY {
  177. // device is in use
  178. return true, nil
  179. }
  180. // error during call to Open
  181. return false, errno
  182. }
  183. func pathIsDevice(pathname string) (bool, error) {
  184. finfo, err := os.Stat(pathname)
  185. // err in call to os.Stat
  186. if err != nil {
  187. return false, err
  188. }
  189. // path refers to a device
  190. if finfo.Mode()&os.ModeDevice != 0 {
  191. return true, nil
  192. }
  193. // path does not refer to device
  194. return false, nil
  195. }
  196. //GetDeviceNameFromMount: given a mount point, find the device name from its global mount point
  197. func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginDir string) (string, error) {
  198. return getDeviceNameFromMount(mounter, mountPath, pluginDir)
  199. }
  200. func listProcMounts(mountFilePath string) ([]MountPoint, error) {
  201. hash1, err := readProcMounts(mountFilePath, nil)
  202. if err != nil {
  203. return nil, err
  204. }
  205. for i := 0; i < maxListTries; i++ {
  206. mps := []MountPoint{}
  207. hash2, err := readProcMounts(mountFilePath, &mps)
  208. if err != nil {
  209. return nil, err
  210. }
  211. if hash1 == hash2 {
  212. // Success
  213. return mps, nil
  214. }
  215. hash1 = hash2
  216. }
  217. return nil, fmt.Errorf("failed to get a consistent snapshot of %v after %d tries", mountFilePath, maxListTries)
  218. }
  219. // readProcMounts reads the given mountFilePath (normally /proc/mounts) and produces a hash
  220. // of the contents. If the out argument is not nil, this fills it with MountPoint structs.
  221. func readProcMounts(mountFilePath string, out *[]MountPoint) (uint32, error) {
  222. file, err := os.Open(mountFilePath)
  223. if err != nil {
  224. return 0, err
  225. }
  226. defer file.Close()
  227. return readProcMountsFrom(file, out)
  228. }
  229. func readProcMountsFrom(file io.Reader, out *[]MountPoint) (uint32, error) {
  230. hash := adler32.New()
  231. scanner := bufio.NewReader(file)
  232. for {
  233. line, err := scanner.ReadString('\n')
  234. if err == io.EOF {
  235. break
  236. }
  237. fields := strings.Fields(line)
  238. if len(fields) != expectedNumFieldsPerLine {
  239. return 0, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line)
  240. }
  241. fmt.Fprintf(hash, "%s", line)
  242. if out != nil {
  243. mp := MountPoint{
  244. Device: fields[0],
  245. Path: fields[1],
  246. Type: fields[2],
  247. Opts: strings.Split(fields[3], ","),
  248. }
  249. freq, err := strconv.Atoi(fields[4])
  250. if err != nil {
  251. return 0, err
  252. }
  253. mp.Freq = freq
  254. pass, err := strconv.Atoi(fields[5])
  255. if err != nil {
  256. return 0, err
  257. }
  258. mp.Pass = pass
  259. *out = append(*out, mp)
  260. }
  261. }
  262. return hash.Sum32(), nil
  263. }
  264. // formatAndMount uses unix utils to format and mount the given disk
  265. func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
  266. options = append(options, "defaults")
  267. // Run fsck on the disk to fix repairable issues
  268. glog.V(4).Infof("Checking for issues with fsck on disk: %s", source)
  269. args := []string{"-a", source}
  270. cmd := mounter.Runner.Command("fsck", args...)
  271. out, err := cmd.CombinedOutput()
  272. if err != nil {
  273. ee, isExitError := err.(utilExec.ExitError)
  274. switch {
  275. case err == utilExec.ErrExecutableNotFound:
  276. glog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.")
  277. case isExitError && ee.ExitStatus() == fsckErrorsCorrected:
  278. glog.Infof("Device %s has errors which were corrected by fsck.", source)
  279. case isExitError && ee.ExitStatus() == fsckErrorsUncorrected:
  280. return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s.", source, string(out))
  281. case isExitError && ee.ExitStatus() > fsckErrorsUncorrected:
  282. glog.Infof("`fsck` error %s", string(out))
  283. }
  284. }
  285. // Try to mount the disk
  286. glog.V(4).Infof("Attempting to mount disk: %s %s %s", fstype, source, target)
  287. err = mounter.Interface.Mount(source, target, fstype, options)
  288. if err != nil {
  289. // It is possible that this disk is not formatted. Double check using diskLooksUnformatted
  290. notFormatted, err := mounter.diskLooksUnformatted(source)
  291. if err == nil && notFormatted {
  292. args = []string{source}
  293. // Disk is unformatted so format it.
  294. // Use 'ext4' as the default
  295. if len(fstype) == 0 {
  296. fstype = "ext4"
  297. }
  298. if fstype == "ext4" || fstype == "ext3" {
  299. args = []string{"-E", "lazy_itable_init=0,lazy_journal_init=0", "-F", source}
  300. }
  301. glog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args)
  302. cmd := mounter.Runner.Command("mkfs."+fstype, args...)
  303. _, err := cmd.CombinedOutput()
  304. if err == nil {
  305. // the disk has been formatted successfully try to mount it again.
  306. glog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target)
  307. return mounter.Interface.Mount(source, target, fstype, options)
  308. }
  309. glog.Errorf("format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", source, fstype, target, options, err)
  310. return err
  311. }
  312. }
  313. return err
  314. }
  315. // diskLooksUnformatted uses 'lsblk' to see if the given disk is unformated
  316. func (mounter *SafeFormatAndMount) diskLooksUnformatted(disk string) (bool, error) {
  317. args := []string{"-nd", "-o", "FSTYPE", disk}
  318. cmd := mounter.Runner.Command("lsblk", args...)
  319. glog.V(4).Infof("Attempting to determine if disk %q is formatted using lsblk with args: (%v)", disk, args)
  320. dataOut, err := cmd.CombinedOutput()
  321. output := strings.TrimSpace(string(dataOut))
  322. // TODO (#13212): check if this disk has partitions and return false, and
  323. // an error if so.
  324. if err != nil {
  325. glog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err)
  326. return false, err
  327. }
  328. return output == "", nil
  329. }