mount_linux.go 19 KB

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