mount_linux.go 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490
  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. "errors"
  17. "fmt"
  18. "os"
  19. "os/exec"
  20. "path/filepath"
  21. "strconv"
  22. "strings"
  23. "syscall"
  24. "k8s.io/klog"
  25. utilexec "k8s.io/utils/exec"
  26. utilio "k8s.io/utils/io"
  27. )
  28. const (
  29. // Number of fields per line in /proc/mounts as per the fstab man page.
  30. expectedNumFieldsPerLine = 6
  31. // Location of the mount file to use
  32. procMountsPath = "/proc/mounts"
  33. // Location of the mountinfo file
  34. procMountInfoPath = "/proc/self/mountinfo"
  35. // 'fsck' found errors and corrected them
  36. fsckErrorsCorrected = 1
  37. // 'fsck' found errors but exited without correcting them
  38. fsckErrorsUncorrected = 4
  39. )
  40. // Mounter provides the default implementation of mount.Interface
  41. // for the linux platform. This implementation assumes that the
  42. // kubelet is running in the host's root mount namespace.
  43. type Mounter struct {
  44. mounterPath string
  45. withSystemd bool
  46. }
  47. // New returns a mount.Interface for the current system.
  48. // It provides options to override the default mounter behavior.
  49. // mounterPath allows using an alternative to `/bin/mount` for mounting.
  50. func New(mounterPath string) Interface {
  51. return &Mounter{
  52. mounterPath: mounterPath,
  53. withSystemd: detectSystemd(),
  54. }
  55. }
  56. // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must
  57. // be an empty string in case it's not required, e.g. for remount, or for auto filesystem
  58. // type, where kernel handles fstype for you. The mount 'options' is a list of options,
  59. // currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is
  60. // required, call Mount with an empty string list or nil.
  61. func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
  62. // Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty.
  63. // All Linux distros are expected to be shipped with a mount utility that a support bind mounts.
  64. mounterPath := ""
  65. bind, bindOpts, bindRemountOpts := MakeBindOpts(options)
  66. if bind {
  67. err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts)
  68. if err != nil {
  69. return err
  70. }
  71. return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts)
  72. }
  73. // The list of filesystems that require containerized mounter on GCI image cluster
  74. fsTypesNeedMounter := map[string]struct{}{
  75. "nfs": {},
  76. "glusterfs": {},
  77. "ceph": {},
  78. "cifs": {},
  79. }
  80. if _, ok := fsTypesNeedMounter[fstype]; ok {
  81. mounterPath = mounter.mounterPath
  82. }
  83. return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options)
  84. }
  85. // doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used.
  86. func (mounter *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error {
  87. mountArgs := MakeMountArgs(source, target, fstype, options)
  88. if len(mounterPath) > 0 {
  89. mountArgs = append([]string{mountCmd}, mountArgs...)
  90. mountCmd = mounterPath
  91. }
  92. if mounter.withSystemd {
  93. // Try to run mount via systemd-run --scope. This will escape the
  94. // service where kubelet runs and any fuse daemons will be started in a
  95. // specific scope. kubelet service than can be restarted without killing
  96. // these fuse daemons.
  97. //
  98. // Complete command line (when mounterPath is not used):
  99. // systemd-run --description=... --scope -- mount -t <type> <what> <where>
  100. //
  101. // Expected flow:
  102. // * systemd-run creates a transient scope (=~ cgroup) and executes its
  103. // argument (/bin/mount) there.
  104. // * mount does its job, forks a fuse daemon if necessary and finishes.
  105. // (systemd-run --scope finishes at this point, returning mount's exit
  106. // code and stdout/stderr - thats one of --scope benefits).
  107. // * systemd keeps the fuse daemon running in the scope (i.e. in its own
  108. // cgroup) until the fuse daemon dies (another --scope benefit).
  109. // Kubelet service can be restarted and the fuse daemon survives.
  110. // * When the fuse daemon dies (e.g. during unmount) systemd removes the
  111. // scope automatically.
  112. //
  113. // systemd-mount is not used because it's too new for older distros
  114. // (CentOS 7, Debian Jessie).
  115. mountCmd, mountArgs = AddSystemdScope("systemd-run", target, mountCmd, mountArgs)
  116. } else {
  117. // No systemd-run on the host (or we failed to check it), assume kubelet
  118. // does not run as a systemd service.
  119. // No code here, mountCmd and mountArgs are already populated.
  120. }
  121. klog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs)
  122. command := exec.Command(mountCmd, mountArgs...)
  123. output, err := command.CombinedOutput()
  124. if err != nil {
  125. args := strings.Join(mountArgs, " ")
  126. klog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n", err, mountCmd, args, string(output))
  127. return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s",
  128. err, mountCmd, args, string(output))
  129. }
  130. return err
  131. }
  132. // detectSystemd returns true if OS runs with systemd as init. When not sure
  133. // (permission errors, ...), it returns false.
  134. // There may be different ways how to detect systemd, this one makes sure that
  135. // systemd-runs (needed by Mount()) works.
  136. func detectSystemd() bool {
  137. if _, err := exec.LookPath("systemd-run"); err != nil {
  138. klog.V(2).Infof("Detected OS without systemd")
  139. return false
  140. }
  141. // Try to run systemd-run --scope /bin/true, that should be enough
  142. // to make sure that systemd is really running and not just installed,
  143. // which happens when running in a container with a systemd-based image
  144. // but with different pid 1.
  145. cmd := exec.Command("systemd-run", "--description=Kubernetes systemd probe", "--scope", "true")
  146. output, err := cmd.CombinedOutput()
  147. if err != nil {
  148. klog.V(2).Infof("Cannot run systemd-run, assuming non-systemd OS")
  149. klog.V(4).Infof("systemd-run failed with: %v", err)
  150. klog.V(4).Infof("systemd-run output: %s", string(output))
  151. return false
  152. }
  153. klog.V(2).Infof("Detected OS with systemd")
  154. return true
  155. }
  156. // MakeMountArgs makes the arguments to the mount(8) command.
  157. // Implementation is shared with NsEnterMounter
  158. func MakeMountArgs(source, target, fstype string, options []string) []string {
  159. // Build mount command as follows:
  160. // mount [-t $fstype] [-o $options] [$source] $target
  161. mountArgs := []string{}
  162. if len(fstype) > 0 {
  163. mountArgs = append(mountArgs, "-t", fstype)
  164. }
  165. if len(options) > 0 {
  166. mountArgs = append(mountArgs, "-o", strings.Join(options, ","))
  167. }
  168. if len(source) > 0 {
  169. mountArgs = append(mountArgs, source)
  170. }
  171. mountArgs = append(mountArgs, target)
  172. return mountArgs
  173. }
  174. // AddSystemdScope adds "system-run --scope" to given command line
  175. // implementation is shared with NsEnterMounter
  176. func AddSystemdScope(systemdRunPath, mountName, command string, args []string) (string, []string) {
  177. descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
  178. systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
  179. return systemdRunPath, append(systemdRunArgs, args...)
  180. }
  181. // Unmount unmounts the target.
  182. func (mounter *Mounter) Unmount(target string) error {
  183. klog.V(4).Infof("Unmounting %s", target)
  184. command := exec.Command("umount", target)
  185. output, err := command.CombinedOutput()
  186. if err != nil {
  187. return fmt.Errorf("unmount failed: %v\nUnmounting arguments: %s\nOutput: %s", err, target, string(output))
  188. }
  189. return nil
  190. }
  191. // List returns a list of all mounted filesystems.
  192. func (*Mounter) List() ([]MountPoint, error) {
  193. return ListProcMounts(procMountsPath)
  194. }
  195. // IsLikelyNotMountPoint determines if a directory is not a mountpoint.
  196. // It is fast but not necessarily ALWAYS correct. If the path is in fact
  197. // a bind mount from one part of a mount to another it will not be detected.
  198. // It also can not distinguish between mountpoints and symbolic links.
  199. // mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b")
  200. // will return true. When in fact /tmp/b is a mount point. If this situation
  201. // is of interest to you, don't use this function...
  202. func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
  203. stat, err := os.Stat(file)
  204. if err != nil {
  205. return true, err
  206. }
  207. rootStat, err := os.Stat(filepath.Dir(strings.TrimSuffix(file, "/")))
  208. if err != nil {
  209. return true, err
  210. }
  211. // If the directory has a different device as parent, then it is a mountpoint.
  212. if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
  213. return false, nil
  214. }
  215. return true, nil
  216. }
  217. // GetMountRefs finds all mount references to pathname, returns a
  218. // list of paths. Path could be a mountpoint or a normal
  219. // directory (for bind mount).
  220. func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
  221. pathExists, pathErr := PathExists(pathname)
  222. if !pathExists {
  223. return []string{}, nil
  224. } else if IsCorruptedMnt(pathErr) {
  225. klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", pathname)
  226. return []string{}, nil
  227. } else if pathErr != nil {
  228. return nil, fmt.Errorf("error checking path %s: %v", pathname, pathErr)
  229. }
  230. realpath, err := filepath.EvalSymlinks(pathname)
  231. if err != nil {
  232. return nil, err
  233. }
  234. return SearchMountPoints(realpath, procMountInfoPath)
  235. }
  236. // formatAndMount uses unix utils to format and mount the given disk
  237. func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
  238. readOnly := false
  239. for _, option := range options {
  240. if option == "ro" {
  241. readOnly = true
  242. break
  243. }
  244. }
  245. options = append(options, "defaults")
  246. if !readOnly {
  247. // Run fsck on the disk to fix repairable issues, only do this for volumes requested as rw.
  248. klog.V(4).Infof("Checking for issues with fsck on disk: %s", source)
  249. args := []string{"-a", source}
  250. out, err := mounter.Exec.Command("fsck", args...).CombinedOutput()
  251. if err != nil {
  252. ee, isExitError := err.(utilexec.ExitError)
  253. switch {
  254. case err == utilexec.ErrExecutableNotFound:
  255. klog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.")
  256. case isExitError && ee.ExitStatus() == fsckErrorsCorrected:
  257. klog.Infof("Device %s has errors which were corrected by fsck.", source)
  258. case isExitError && ee.ExitStatus() == fsckErrorsUncorrected:
  259. return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s", source, string(out))
  260. case isExitError && ee.ExitStatus() > fsckErrorsUncorrected:
  261. klog.Infof("`fsck` error %s", string(out))
  262. }
  263. }
  264. }
  265. // Try to mount the disk
  266. klog.V(4).Infof("Attempting to mount disk: %s %s %s", fstype, source, target)
  267. mountErr := mounter.Interface.Mount(source, target, fstype, options)
  268. if mountErr != nil {
  269. // Mount failed. This indicates either that the disk is unformatted or
  270. // it contains an unexpected filesystem.
  271. existingFormat, err := mounter.GetDiskFormat(source)
  272. if err != nil {
  273. return err
  274. }
  275. if existingFormat == "" {
  276. if readOnly {
  277. // Don't attempt to format if mounting as readonly, return an error to reflect this.
  278. return errors.New("failed to mount unformatted volume as read only")
  279. }
  280. // Disk is unformatted so format it.
  281. args := []string{source}
  282. // Use 'ext4' as the default
  283. if len(fstype) == 0 {
  284. fstype = "ext4"
  285. }
  286. if fstype == "ext4" || fstype == "ext3" {
  287. args = []string{
  288. "-F", // Force flag
  289. "-m0", // Zero blocks reserved for super-user
  290. source,
  291. }
  292. }
  293. klog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args)
  294. _, err := mounter.Exec.Command("mkfs."+fstype, args...).CombinedOutput()
  295. if err == nil {
  296. // the disk has been formatted successfully try to mount it again.
  297. klog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target)
  298. return mounter.Interface.Mount(source, target, fstype, options)
  299. }
  300. klog.Errorf("format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", source, fstype, target, options, err)
  301. return err
  302. }
  303. // Disk is already formatted and failed to mount
  304. if len(fstype) == 0 || fstype == existingFormat {
  305. // This is mount error
  306. return mountErr
  307. }
  308. // Block device is formatted with unexpected filesystem, let the user know
  309. return fmt.Errorf("failed to mount the volume as %q, it already contains %s. Mount error: %v", fstype, existingFormat, mountErr)
  310. }
  311. return mountErr
  312. }
  313. // GetDiskFormat uses 'blkid' to see if the given disk is unformatted
  314. func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) {
  315. args := []string{"-p", "-s", "TYPE", "-s", "PTTYPE", "-o", "export", disk}
  316. klog.V(4).Infof("Attempting to determine if disk %q is formatted using blkid with args: (%v)", disk, args)
  317. dataOut, err := mounter.Exec.Command("blkid", args...).CombinedOutput()
  318. output := string(dataOut)
  319. klog.V(4).Infof("Output: %q, err: %v", output, err)
  320. if err != nil {
  321. if exit, ok := err.(utilexec.ExitError); ok {
  322. if exit.ExitStatus() == 2 {
  323. // Disk device is unformatted.
  324. // For `blkid`, if the specified token (TYPE/PTTYPE, etc) was
  325. // not found, or no (specified) devices could be identified, an
  326. // exit code of 2 is returned.
  327. return "", nil
  328. }
  329. }
  330. klog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err)
  331. return "", err
  332. }
  333. var fstype, pttype string
  334. lines := strings.Split(output, "\n")
  335. for _, l := range lines {
  336. if len(l) <= 0 {
  337. // Ignore empty line.
  338. continue
  339. }
  340. cs := strings.Split(l, "=")
  341. if len(cs) != 2 {
  342. return "", fmt.Errorf("blkid returns invalid output: %s", output)
  343. }
  344. // TYPE is filesystem type, and PTTYPE is partition table type, according
  345. // to https://www.kernel.org/pub/linux/utils/util-linux/v2.21/libblkid-docs/.
  346. if cs[0] == "TYPE" {
  347. fstype = cs[1]
  348. } else if cs[0] == "PTTYPE" {
  349. pttype = cs[1]
  350. }
  351. }
  352. if len(pttype) > 0 {
  353. klog.V(4).Infof("Disk %s detected partition table type: %s", disk, pttype)
  354. // Returns a special non-empty string as filesystem type, then kubelet
  355. // will not format it.
  356. return "unknown data, probably partitions", nil
  357. }
  358. return fstype, nil
  359. }
  360. // ListProcMounts is shared with NsEnterMounter
  361. func ListProcMounts(mountFilePath string) ([]MountPoint, error) {
  362. content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
  363. if err != nil {
  364. return nil, err
  365. }
  366. return parseProcMounts(content)
  367. }
  368. func parseProcMounts(content []byte) ([]MountPoint, error) {
  369. out := []MountPoint{}
  370. lines := strings.Split(string(content), "\n")
  371. for _, line := range lines {
  372. if line == "" {
  373. // the last split() item is empty string following the last \n
  374. continue
  375. }
  376. fields := strings.Fields(line)
  377. if len(fields) != expectedNumFieldsPerLine {
  378. return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line)
  379. }
  380. mp := MountPoint{
  381. Device: fields[0],
  382. Path: fields[1],
  383. Type: fields[2],
  384. Opts: strings.Split(fields[3], ","),
  385. }
  386. freq, err := strconv.Atoi(fields[4])
  387. if err != nil {
  388. return nil, err
  389. }
  390. mp.Freq = freq
  391. pass, err := strconv.Atoi(fields[5])
  392. if err != nil {
  393. return nil, err
  394. }
  395. mp.Pass = pass
  396. out = append(out, mp)
  397. }
  398. return out, nil
  399. }
  400. // SearchMountPoints finds all mount references to the source, returns a list of
  401. // mountpoints.
  402. // The source can be a mount point or a normal directory (bind mount). We
  403. // didn't support device because there is no use case by now.
  404. // Some filesystems may share a source name, e.g. tmpfs. And for bind mounting,
  405. // it's possible to mount a non-root path of a filesystem, so we need to use
  406. // root path and major:minor to represent mount source uniquely.
  407. // This implementation is shared between Linux and NsEnterMounter
  408. func SearchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
  409. mis, err := ParseMountInfo(mountInfoPath)
  410. if err != nil {
  411. return nil, err
  412. }
  413. mountID := 0
  414. rootPath := ""
  415. majorMinor := ""
  416. // Finding the underlying root path and major:minor if possible.
  417. // We need search in backward order because it's possible for later mounts
  418. // to overlap earlier mounts.
  419. for i := len(mis) - 1; i >= 0; i-- {
  420. if hostSource == mis[i].MountPoint || PathWithinBase(hostSource, mis[i].MountPoint) {
  421. // If it's a mount point or path under a mount point.
  422. mountID = mis[i].ID
  423. rootPath = filepath.Join(mis[i].Root, strings.TrimPrefix(hostSource, mis[i].MountPoint))
  424. majorMinor = mis[i].MajorMinor
  425. break
  426. }
  427. }
  428. if rootPath == "" || majorMinor == "" {
  429. return nil, fmt.Errorf("failed to get root path and major:minor for %s", hostSource)
  430. }
  431. var refs []string
  432. for i := range mis {
  433. if mis[i].ID == mountID {
  434. // Ignore mount entry for mount source itself.
  435. continue
  436. }
  437. if mis[i].Root == rootPath && mis[i].MajorMinor == majorMinor {
  438. refs = append(refs, mis[i].MountPoint)
  439. }
  440. }
  441. return refs, nil
  442. }