mount_linux.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828
  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"
  21. "path/filepath"
  22. "strconv"
  23. "strings"
  24. "syscall"
  25. "golang.org/x/sys/unix"
  26. "k8s.io/klog"
  27. utilexec "k8s.io/utils/exec"
  28. utilio "k8s.io/utils/io"
  29. utilpath "k8s.io/utils/path"
  30. )
  31. const (
  32. // How many times to retry for a consistent read of /proc/mounts.
  33. maxListTries = 3
  34. // Number of fields per line in /proc/mounts as per the fstab man page.
  35. expectedNumFieldsPerLine = 6
  36. // At least number of fields per line in /proc/<pid>/mountinfo.
  37. expectedAtLeastNumFieldsPerMountInfo = 10
  38. // Location of the mount file to use
  39. procMountsPath = "/proc/mounts"
  40. // Location of the mountinfo file
  41. procMountInfoPath = "/proc/self/mountinfo"
  42. // 'fsck' found errors and corrected them
  43. fsckErrorsCorrected = 1
  44. // 'fsck' found errors but exited without correcting them
  45. fsckErrorsUncorrected = 4
  46. )
  47. // Mounter provides the default implementation of mount.Interface
  48. // for the linux platform. This implementation assumes that the
  49. // kubelet is running in the host's root mount namespace.
  50. type Mounter struct {
  51. mounterPath string
  52. withSystemd bool
  53. }
  54. // New returns a mount.Interface for the current system.
  55. // It provides options to override the default mounter behavior.
  56. // mounterPath allows using an alternative to `/bin/mount` for mounting.
  57. func New(mounterPath string) Interface {
  58. return &Mounter{
  59. mounterPath: mounterPath,
  60. withSystemd: detectSystemd(),
  61. }
  62. }
  63. // Mount mounts source to target as fstype with given options. 'source' and 'fstype' must
  64. // be an empty string in case it's not required, e.g. for remount, or for auto filesystem
  65. // type, where kernel handles fstype for you. The mount 'options' is a list of options,
  66. // currently come from mount(8), e.g. "ro", "remount", "bind", etc. If no more option is
  67. // required, call Mount with an empty string list or nil.
  68. func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
  69. // Path to mounter binary if containerized mounter is needed. Otherwise, it is set to empty.
  70. // All Linux distros are expected to be shipped with a mount utility that a support bind mounts.
  71. mounterPath := ""
  72. bind, bindOpts, bindRemountOpts := IsBind(options)
  73. if bind {
  74. err := mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindOpts)
  75. if err != nil {
  76. return err
  77. }
  78. return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, bindRemountOpts)
  79. }
  80. // The list of filesystems that require containerized mounter on GCI image cluster
  81. fsTypesNeedMounter := map[string]struct{}{
  82. "nfs": {},
  83. "glusterfs": {},
  84. "ceph": {},
  85. "cifs": {},
  86. }
  87. if _, ok := fsTypesNeedMounter[fstype]; ok {
  88. mounterPath = mounter.mounterPath
  89. }
  90. return mounter.doMount(mounterPath, defaultMountCommand, source, target, fstype, options)
  91. }
  92. // doMount runs the mount command. mounterPath is the path to mounter binary if containerized mounter is used.
  93. func (m *Mounter) doMount(mounterPath string, mountCmd string, source string, target string, fstype string, options []string) error {
  94. mountArgs := MakeMountArgs(source, target, fstype, options)
  95. if len(mounterPath) > 0 {
  96. mountArgs = append([]string{mountCmd}, mountArgs...)
  97. mountCmd = mounterPath
  98. }
  99. if m.withSystemd {
  100. // Try to run mount via systemd-run --scope. This will escape the
  101. // service where kubelet runs and any fuse daemons will be started in a
  102. // specific scope. kubelet service than can be restarted without killing
  103. // these fuse daemons.
  104. //
  105. // Complete command line (when mounterPath is not used):
  106. // systemd-run --description=... --scope -- mount -t <type> <what> <where>
  107. //
  108. // Expected flow:
  109. // * systemd-run creates a transient scope (=~ cgroup) and executes its
  110. // argument (/bin/mount) there.
  111. // * mount does its job, forks a fuse daemon if necessary and finishes.
  112. // (systemd-run --scope finishes at this point, returning mount's exit
  113. // code and stdout/stderr - thats one of --scope benefits).
  114. // * systemd keeps the fuse daemon running in the scope (i.e. in its own
  115. // cgroup) until the fuse daemon dies (another --scope benefit).
  116. // Kubelet service can be restarted and the fuse daemon survives.
  117. // * When the fuse daemon dies (e.g. during unmount) systemd removes the
  118. // scope automatically.
  119. //
  120. // systemd-mount is not used because it's too new for older distros
  121. // (CentOS 7, Debian Jessie).
  122. mountCmd, mountArgs = AddSystemdScope("systemd-run", target, mountCmd, mountArgs)
  123. } else {
  124. // No systemd-run on the host (or we failed to check it), assume kubelet
  125. // does not run as a systemd service.
  126. // No code here, mountCmd and mountArgs are already populated.
  127. }
  128. klog.V(4).Infof("Mounting cmd (%s) with arguments (%s)", mountCmd, mountArgs)
  129. command := exec.Command(mountCmd, mountArgs...)
  130. output, err := command.CombinedOutput()
  131. if err != nil {
  132. args := strings.Join(mountArgs, " ")
  133. klog.Errorf("Mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n", err, mountCmd, args, string(output))
  134. return fmt.Errorf("mount failed: %v\nMounting command: %s\nMounting arguments: %s\nOutput: %s\n",
  135. err, mountCmd, args, string(output))
  136. }
  137. return err
  138. }
  139. // detectSystemd returns true if OS runs with systemd as init. When not sure
  140. // (permission errors, ...), it returns false.
  141. // There may be different ways how to detect systemd, this one makes sure that
  142. // systemd-runs (needed by Mount()) works.
  143. func detectSystemd() bool {
  144. if _, err := exec.LookPath("systemd-run"); err != nil {
  145. klog.V(2).Infof("Detected OS without systemd")
  146. return false
  147. }
  148. // Try to run systemd-run --scope /bin/true, that should be enough
  149. // to make sure that systemd is really running and not just installed,
  150. // which happens when running in a container with a systemd-based image
  151. // but with different pid 1.
  152. cmd := exec.Command("systemd-run", "--description=Kubernetes systemd probe", "--scope", "true")
  153. output, err := cmd.CombinedOutput()
  154. if err != nil {
  155. klog.V(2).Infof("Cannot run systemd-run, assuming non-systemd OS")
  156. klog.V(4).Infof("systemd-run failed with: %v", err)
  157. klog.V(4).Infof("systemd-run output: %s", string(output))
  158. return false
  159. }
  160. klog.V(2).Infof("Detected OS with systemd")
  161. return true
  162. }
  163. // MakeMountArgs makes the arguments to the mount(8) command.
  164. // Implementation is shared with NsEnterMounter
  165. func MakeMountArgs(source, target, fstype string, options []string) []string {
  166. // Build mount command as follows:
  167. // mount [-t $fstype] [-o $options] [$source] $target
  168. mountArgs := []string{}
  169. if len(fstype) > 0 {
  170. mountArgs = append(mountArgs, "-t", fstype)
  171. }
  172. if len(options) > 0 {
  173. mountArgs = append(mountArgs, "-o", strings.Join(options, ","))
  174. }
  175. if len(source) > 0 {
  176. mountArgs = append(mountArgs, source)
  177. }
  178. mountArgs = append(mountArgs, target)
  179. return mountArgs
  180. }
  181. // AddSystemdScope adds "system-run --scope" to given command line
  182. // implementation is shared with NsEnterMounter
  183. func AddSystemdScope(systemdRunPath, mountName, command string, args []string) (string, []string) {
  184. descriptionArg := fmt.Sprintf("--description=Kubernetes transient mount for %s", mountName)
  185. systemdRunArgs := []string{descriptionArg, "--scope", "--", command}
  186. return systemdRunPath, append(systemdRunArgs, args...)
  187. }
  188. // Unmount unmounts the target.
  189. func (mounter *Mounter) Unmount(target string) error {
  190. klog.V(4).Infof("Unmounting %s", target)
  191. command := exec.Command("umount", target)
  192. output, err := command.CombinedOutput()
  193. if err != nil {
  194. return fmt.Errorf("Unmount failed: %v\nUnmounting arguments: %s\nOutput: %s\n", err, target, string(output))
  195. }
  196. return nil
  197. }
  198. // List returns a list of all mounted filesystems.
  199. func (*Mounter) List() ([]MountPoint, error) {
  200. return ListProcMounts(procMountsPath)
  201. }
  202. func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool {
  203. deletedDir := fmt.Sprintf("%s\\040(deleted)", dir)
  204. return ((mp.Path == dir) || (mp.Path == deletedDir))
  205. }
  206. // IsLikelyNotMountPoint determines if a directory is not a mountpoint.
  207. // It is fast but not necessarily ALWAYS correct. If the path is in fact
  208. // a bind mount from one part of a mount to another it will not be detected.
  209. // It also can not distinguish between mountpoints and symbolic links.
  210. // mkdir /tmp/a /tmp/b; mount --bind /tmp/a /tmp/b; IsLikelyNotMountPoint("/tmp/b")
  211. // will return true. When in fact /tmp/b is a mount point. If this situation
  212. // if of interest to you, don't use this function...
  213. func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
  214. stat, err := os.Stat(file)
  215. if err != nil {
  216. return true, err
  217. }
  218. rootStat, err := os.Stat(filepath.Dir(strings.TrimSuffix(file, "/")))
  219. if err != nil {
  220. return true, err
  221. }
  222. // If the directory has a different device as parent, then it is a mountpoint.
  223. if stat.Sys().(*syscall.Stat_t).Dev != rootStat.Sys().(*syscall.Stat_t).Dev {
  224. return false, nil
  225. }
  226. return true, nil
  227. }
  228. // DeviceOpened checks if block device in use by calling Open with O_EXCL flag.
  229. // If pathname is not a device, log and return false with nil error.
  230. // If open returns errno EBUSY, return true with nil error.
  231. // If open returns nil, return false with nil error.
  232. // Otherwise, return false with error
  233. func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
  234. return ExclusiveOpenFailsOnDevice(pathname)
  235. }
  236. // PathIsDevice uses FileInfo returned from os.Stat to check if path refers
  237. // to a device.
  238. func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
  239. pathType, err := mounter.GetFileType(pathname)
  240. isDevice := pathType == FileTypeCharDev || pathType == FileTypeBlockDev
  241. return isDevice, err
  242. }
  243. // ExclusiveOpenFailsOnDevice is shared with NsEnterMounter
  244. func ExclusiveOpenFailsOnDevice(pathname string) (bool, error) {
  245. var isDevice bool
  246. finfo, err := os.Stat(pathname)
  247. if os.IsNotExist(err) {
  248. isDevice = false
  249. }
  250. // err in call to os.Stat
  251. if err != nil {
  252. return false, fmt.Errorf(
  253. "PathIsDevice failed for path %q: %v",
  254. pathname,
  255. err)
  256. }
  257. // path refers to a device
  258. if finfo.Mode()&os.ModeDevice != 0 {
  259. isDevice = true
  260. }
  261. if !isDevice {
  262. klog.Errorf("Path %q is not referring to a device.", pathname)
  263. return false, nil
  264. }
  265. fd, errno := unix.Open(pathname, unix.O_RDONLY|unix.O_EXCL, 0)
  266. // If the device is in use, open will return an invalid fd.
  267. // When this happens, it is expected that Close will fail and throw an error.
  268. defer unix.Close(fd)
  269. if errno == nil {
  270. // device not in use
  271. return false, nil
  272. } else if errno == unix.EBUSY {
  273. // device is in use
  274. return true, nil
  275. }
  276. // error during call to Open
  277. return false, errno
  278. }
  279. //GetDeviceNameFromMount: given a mount point, find the device name from its global mount point
  280. func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) {
  281. return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir)
  282. }
  283. func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
  284. return GetDeviceNameFromMountLinux(mounter, mountPath, pluginMountDir)
  285. }
  286. // GetDeviceNameFromMountLinux find the device name from /proc/mounts in which
  287. // the mount path reference should match the given plugin mount directory. In case no mount path reference
  288. // matches, returns the volume name taken from its given mountPath
  289. // This implementation is shared with NsEnterMounter
  290. func GetDeviceNameFromMountLinux(mounter Interface, mountPath, pluginMountDir string) (string, error) {
  291. refs, err := mounter.GetMountRefs(mountPath)
  292. if err != nil {
  293. klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
  294. return "", err
  295. }
  296. if len(refs) == 0 {
  297. klog.V(4).Infof("Directory %s is not mounted", mountPath)
  298. return "", fmt.Errorf("directory %s is not mounted", mountPath)
  299. }
  300. for _, ref := range refs {
  301. if strings.HasPrefix(ref, pluginMountDir) {
  302. volumeID, err := filepath.Rel(pluginMountDir, ref)
  303. if err != nil {
  304. klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
  305. return "", err
  306. }
  307. return volumeID, nil
  308. }
  309. }
  310. return path.Base(mountPath), nil
  311. }
  312. // ListProcMounts is shared with NsEnterMounter
  313. func ListProcMounts(mountFilePath string) ([]MountPoint, error) {
  314. content, err := utilio.ConsistentRead(mountFilePath, maxListTries)
  315. if err != nil {
  316. return nil, err
  317. }
  318. return parseProcMounts(content)
  319. }
  320. func parseProcMounts(content []byte) ([]MountPoint, error) {
  321. out := []MountPoint{}
  322. lines := strings.Split(string(content), "\n")
  323. for _, line := range lines {
  324. if line == "" {
  325. // the last split() item is empty string following the last \n
  326. continue
  327. }
  328. fields := strings.Fields(line)
  329. if len(fields) != expectedNumFieldsPerLine {
  330. return nil, fmt.Errorf("wrong number of fields (expected %d, got %d): %s", expectedNumFieldsPerLine, len(fields), line)
  331. }
  332. mp := MountPoint{
  333. Device: fields[0],
  334. Path: fields[1],
  335. Type: fields[2],
  336. Opts: strings.Split(fields[3], ","),
  337. }
  338. freq, err := strconv.Atoi(fields[4])
  339. if err != nil {
  340. return nil, err
  341. }
  342. mp.Freq = freq
  343. pass, err := strconv.Atoi(fields[5])
  344. if err != nil {
  345. return nil, err
  346. }
  347. mp.Pass = pass
  348. out = append(out, mp)
  349. }
  350. return out, nil
  351. }
  352. func (mounter *Mounter) MakeRShared(path string) error {
  353. return DoMakeRShared(path, procMountInfoPath)
  354. }
  355. func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
  356. return getFileType(pathname)
  357. }
  358. func (mounter *Mounter) MakeDir(pathname string) error {
  359. err := os.MkdirAll(pathname, os.FileMode(0755))
  360. if err != nil {
  361. if !os.IsExist(err) {
  362. return err
  363. }
  364. }
  365. return nil
  366. }
  367. func (mounter *Mounter) MakeFile(pathname string) error {
  368. f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
  369. defer f.Close()
  370. if err != nil {
  371. if !os.IsExist(err) {
  372. return err
  373. }
  374. }
  375. return nil
  376. }
  377. func (mounter *Mounter) ExistsPath(pathname string) (bool, error) {
  378. return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
  379. }
  380. func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) {
  381. return filepath.EvalSymlinks(pathname)
  382. }
  383. // formatAndMount uses unix utils to format and mount the given disk
  384. func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
  385. readOnly := false
  386. for _, option := range options {
  387. if option == "ro" {
  388. readOnly = true
  389. break
  390. }
  391. }
  392. options = append(options, "defaults")
  393. if !readOnly {
  394. // Run fsck on the disk to fix repairable issues, only do this for volumes requested as rw.
  395. klog.V(4).Infof("Checking for issues with fsck on disk: %s", source)
  396. args := []string{"-a", source}
  397. out, err := mounter.Exec.Run("fsck", args...)
  398. if err != nil {
  399. ee, isExitError := err.(utilexec.ExitError)
  400. switch {
  401. case err == utilexec.ErrExecutableNotFound:
  402. klog.Warningf("'fsck' not found on system; continuing mount without running 'fsck'.")
  403. case isExitError && ee.ExitStatus() == fsckErrorsCorrected:
  404. klog.Infof("Device %s has errors which were corrected by fsck.", source)
  405. case isExitError && ee.ExitStatus() == fsckErrorsUncorrected:
  406. return fmt.Errorf("'fsck' found errors on device %s but could not correct them: %s.", source, string(out))
  407. case isExitError && ee.ExitStatus() > fsckErrorsUncorrected:
  408. klog.Infof("`fsck` error %s", string(out))
  409. }
  410. }
  411. }
  412. // Try to mount the disk
  413. klog.V(4).Infof("Attempting to mount disk: %s %s %s", fstype, source, target)
  414. mountErr := mounter.Interface.Mount(source, target, fstype, options)
  415. if mountErr != nil {
  416. // Mount failed. This indicates either that the disk is unformatted or
  417. // it contains an unexpected filesystem.
  418. existingFormat, err := mounter.GetDiskFormat(source)
  419. if err != nil {
  420. return err
  421. }
  422. if existingFormat == "" {
  423. if readOnly {
  424. // Don't attempt to format if mounting as readonly, return an error to reflect this.
  425. return errors.New("failed to mount unformatted volume as read only")
  426. }
  427. // Disk is unformatted so format it.
  428. args := []string{source}
  429. // Use 'ext4' as the default
  430. if len(fstype) == 0 {
  431. fstype = "ext4"
  432. }
  433. if fstype == "ext4" || fstype == "ext3" {
  434. args = []string{
  435. "-F", // Force flag
  436. "-m0", // Zero blocks reserved for super-user
  437. source,
  438. }
  439. }
  440. klog.Infof("Disk %q appears to be unformatted, attempting to format as type: %q with options: %v", source, fstype, args)
  441. _, err := mounter.Exec.Run("mkfs."+fstype, args...)
  442. if err == nil {
  443. // the disk has been formatted successfully try to mount it again.
  444. klog.Infof("Disk successfully formatted (mkfs): %s - %s %s", fstype, source, target)
  445. return mounter.Interface.Mount(source, target, fstype, options)
  446. }
  447. klog.Errorf("format of disk %q failed: type:(%q) target:(%q) options:(%q)error:(%v)", source, fstype, target, options, err)
  448. return err
  449. } else {
  450. // Disk is already formatted and failed to mount
  451. if len(fstype) == 0 || fstype == existingFormat {
  452. // This is mount error
  453. return mountErr
  454. } else {
  455. // Block device is formatted with unexpected filesystem, let the user know
  456. return fmt.Errorf("failed to mount the volume as %q, it already contains %s. Mount error: %v", fstype, existingFormat, mountErr)
  457. }
  458. }
  459. }
  460. return mountErr
  461. }
  462. // GetDiskFormat uses 'blkid' to see if the given disk is unformatted
  463. func (mounter *SafeFormatAndMount) GetDiskFormat(disk string) (string, error) {
  464. args := []string{"-p", "-s", "TYPE", "-s", "PTTYPE", "-o", "export", disk}
  465. klog.V(4).Infof("Attempting to determine if disk %q is formatted using blkid with args: (%v)", disk, args)
  466. dataOut, err := mounter.Exec.Run("blkid", args...)
  467. output := string(dataOut)
  468. klog.V(4).Infof("Output: %q, err: %v", output, err)
  469. if err != nil {
  470. if exit, ok := err.(utilexec.ExitError); ok {
  471. if exit.ExitStatus() == 2 {
  472. // Disk device is unformatted.
  473. // For `blkid`, if the specified token (TYPE/PTTYPE, etc) was
  474. // not found, or no (specified) devices could be identified, an
  475. // exit code of 2 is returned.
  476. return "", nil
  477. }
  478. }
  479. klog.Errorf("Could not determine if disk %q is formatted (%v)", disk, err)
  480. return "", err
  481. }
  482. var fstype, pttype string
  483. lines := strings.Split(output, "\n")
  484. for _, l := range lines {
  485. if len(l) <= 0 {
  486. // Ignore empty line.
  487. continue
  488. }
  489. cs := strings.Split(l, "=")
  490. if len(cs) != 2 {
  491. return "", fmt.Errorf("blkid returns invalid output: %s", output)
  492. }
  493. // TYPE is filesystem type, and PTTYPE is partition table type, according
  494. // to https://www.kernel.org/pub/linux/utils/util-linux/v2.21/libblkid-docs/.
  495. if cs[0] == "TYPE" {
  496. fstype = cs[1]
  497. } else if cs[0] == "PTTYPE" {
  498. pttype = cs[1]
  499. }
  500. }
  501. if len(pttype) > 0 {
  502. klog.V(4).Infof("Disk %s detected partition table type: %s", disk, pttype)
  503. // Returns a special non-empty string as filesystem type, then kubelet
  504. // will not format it.
  505. return "unknown data, probably partitions", nil
  506. }
  507. return fstype, nil
  508. }
  509. // isShared returns true, if given path is on a mount point that has shared
  510. // mount propagation.
  511. func isShared(mount string, mountInfoPath string) (bool, error) {
  512. info, err := findMountInfo(mount, mountInfoPath)
  513. if err != nil {
  514. return false, err
  515. }
  516. // parse optional parameters
  517. for _, opt := range info.optionalFields {
  518. if strings.HasPrefix(opt, "shared:") {
  519. return true, nil
  520. }
  521. }
  522. return false, nil
  523. }
  524. // This represents a single line in /proc/<pid>/mountinfo.
  525. type mountInfo struct {
  526. // Unique ID for the mount (maybe reused after umount).
  527. id int
  528. // The ID of the parent mount (or of self for the root of this mount namespace's mount tree).
  529. parentID int
  530. // The value of `st_dev` for files on this filesystem.
  531. majorMinor string
  532. // The pathname of the directory in the filesystem which forms the root of this mount.
  533. root string
  534. // Mount source, filesystem-specific information. e.g. device, tmpfs name.
  535. source string
  536. // Mount point, the pathname of the mount point.
  537. mountPoint string
  538. // Optional fieds, zero or more fields of the form "tag[:value]".
  539. optionalFields []string
  540. // The filesystem type in the form "type[.subtype]".
  541. fsType string
  542. // Per-mount options.
  543. mountOptions []string
  544. // Per-superblock options.
  545. superOptions []string
  546. }
  547. // parseMountInfo parses /proc/xxx/mountinfo.
  548. func parseMountInfo(filename string) ([]mountInfo, error) {
  549. content, err := utilio.ConsistentRead(filename, maxListTries)
  550. if err != nil {
  551. return []mountInfo{}, err
  552. }
  553. contentStr := string(content)
  554. infos := []mountInfo{}
  555. for _, line := range strings.Split(contentStr, "\n") {
  556. if line == "" {
  557. // the last split() item is empty string following the last \n
  558. continue
  559. }
  560. // See `man proc` for authoritative description of format of the file.
  561. fields := strings.Fields(line)
  562. if len(fields) < expectedAtLeastNumFieldsPerMountInfo {
  563. return nil, fmt.Errorf("wrong number of fields in (expected at least %d, got %d): %s", expectedAtLeastNumFieldsPerMountInfo, len(fields), line)
  564. }
  565. id, err := strconv.Atoi(fields[0])
  566. if err != nil {
  567. return nil, err
  568. }
  569. parentID, err := strconv.Atoi(fields[1])
  570. if err != nil {
  571. return nil, err
  572. }
  573. info := mountInfo{
  574. id: id,
  575. parentID: parentID,
  576. majorMinor: fields[2],
  577. root: fields[3],
  578. mountPoint: fields[4],
  579. mountOptions: strings.Split(fields[5], ","),
  580. }
  581. // All fields until "-" are "optional fields".
  582. i := 6
  583. for ; i < len(fields) && fields[i] != "-"; i++ {
  584. info.optionalFields = append(info.optionalFields, fields[i])
  585. }
  586. // Parse the rest 3 fields.
  587. i += 1
  588. if len(fields)-i < 3 {
  589. return nil, fmt.Errorf("expect 3 fields in %s, got %d", line, len(fields)-i)
  590. }
  591. info.fsType = fields[i]
  592. info.source = fields[i+1]
  593. info.superOptions = strings.Split(fields[i+2], ",")
  594. infos = append(infos, info)
  595. }
  596. return infos, nil
  597. }
  598. func findMountInfo(path, mountInfoPath string) (mountInfo, error) {
  599. infos, err := parseMountInfo(mountInfoPath)
  600. if err != nil {
  601. return mountInfo{}, err
  602. }
  603. // process /proc/xxx/mountinfo in backward order and find the first mount
  604. // point that is prefix of 'path' - that's the mount where path resides
  605. var info *mountInfo
  606. for i := len(infos) - 1; i >= 0; i-- {
  607. if PathWithinBase(path, infos[i].mountPoint) {
  608. info = &infos[i]
  609. break
  610. }
  611. }
  612. if info == nil {
  613. return mountInfo{}, fmt.Errorf("cannot find mount point for %q", path)
  614. }
  615. return *info, nil
  616. }
  617. // DoMakeRShared is common implementation of MakeRShared on Linux. It checks if
  618. // path is shared and bind-mounts it as rshared if needed. mountCmd and
  619. // mountArgs are expected to contain mount-like command, DoMakeRShared will add
  620. // '--bind <path> <path>' and '--make-rshared <path>' to mountArgs.
  621. func DoMakeRShared(path string, mountInfoFilename string) error {
  622. shared, err := isShared(path, mountInfoFilename)
  623. if err != nil {
  624. return err
  625. }
  626. if shared {
  627. klog.V(4).Infof("Directory %s is already on a shared mount", path)
  628. return nil
  629. }
  630. klog.V(2).Infof("Bind-mounting %q with shared mount propagation", path)
  631. // mount --bind /var/lib/kubelet /var/lib/kubelet
  632. if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_BIND, "" /*data*/); err != nil {
  633. return fmt.Errorf("failed to bind-mount %s: %v", path, err)
  634. }
  635. // mount --make-rshared /var/lib/kubelet
  636. if err := syscall.Mount(path, path, "" /*fstype*/, syscall.MS_SHARED|syscall.MS_REC, "" /*data*/); err != nil {
  637. return fmt.Errorf("failed to make %s rshared: %v", path, err)
  638. }
  639. return nil
  640. }
  641. // GetSELinux is common implementation of GetSELinuxSupport on Linux.
  642. func GetSELinux(path string, mountInfoFilename string) (bool, error) {
  643. info, err := findMountInfo(path, mountInfoFilename)
  644. if err != nil {
  645. return false, err
  646. }
  647. // "seclabel" can be both in mount options and super options.
  648. for _, opt := range info.superOptions {
  649. if opt == "seclabel" {
  650. return true, nil
  651. }
  652. }
  653. for _, opt := range info.mountOptions {
  654. if opt == "seclabel" {
  655. return true, nil
  656. }
  657. }
  658. return false, nil
  659. }
  660. func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
  661. pathExists, pathErr := PathExists(pathname)
  662. if !pathExists {
  663. return []string{}, nil
  664. } else if IsCorruptedMnt(pathErr) {
  665. klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", pathname)
  666. return []string{}, nil
  667. } else if pathErr != nil {
  668. return nil, fmt.Errorf("error checking path %s: %v", pathname, pathErr)
  669. }
  670. realpath, err := filepath.EvalSymlinks(pathname)
  671. if err != nil {
  672. return nil, err
  673. }
  674. return SearchMountPoints(realpath, procMountInfoPath)
  675. }
  676. func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) {
  677. return GetSELinux(pathname, procMountInfoPath)
  678. }
  679. func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) {
  680. realpath, err := filepath.EvalSymlinks(pathname)
  681. if err != nil {
  682. return 0, err
  683. }
  684. return GetFSGroupLinux(realpath)
  685. }
  686. func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) {
  687. return GetModeLinux(pathname)
  688. }
  689. // GetFSGroupLinux is shared between Linux and NsEnterMounter
  690. // pathname must already be evaluated for symlinks
  691. func GetFSGroupLinux(pathname string) (int64, error) {
  692. info, err := os.Stat(pathname)
  693. if err != nil {
  694. return 0, err
  695. }
  696. return int64(info.Sys().(*syscall.Stat_t).Gid), nil
  697. }
  698. // GetModeLinux is shared between Linux and NsEnterMounter
  699. func GetModeLinux(pathname string) (os.FileMode, error) {
  700. info, err := os.Stat(pathname)
  701. if err != nil {
  702. return 0, err
  703. }
  704. return info.Mode(), nil
  705. }
  706. // SearchMountPoints finds all mount references to the source, returns a list of
  707. // mountpoints.
  708. // This function assumes source cannot be device.
  709. // Some filesystems may share a source name, e.g. tmpfs. And for bind mounting,
  710. // it's possible to mount a non-root path of a filesystem, so we need to use
  711. // root path and major:minor to represent mount source uniquely.
  712. // This implementation is shared between Linux and NsEnterMounter
  713. func SearchMountPoints(hostSource, mountInfoPath string) ([]string, error) {
  714. mis, err := parseMountInfo(mountInfoPath)
  715. if err != nil {
  716. return nil, err
  717. }
  718. mountID := 0
  719. rootPath := ""
  720. majorMinor := ""
  721. // Finding the underlying root path and major:minor if possible.
  722. // We need search in backward order because it's possible for later mounts
  723. // to overlap earlier mounts.
  724. for i := len(mis) - 1; i >= 0; i-- {
  725. if hostSource == mis[i].mountPoint || PathWithinBase(hostSource, mis[i].mountPoint) {
  726. // If it's a mount point or path under a mount point.
  727. mountID = mis[i].id
  728. rootPath = filepath.Join(mis[i].root, strings.TrimPrefix(hostSource, mis[i].mountPoint))
  729. majorMinor = mis[i].majorMinor
  730. break
  731. }
  732. }
  733. if rootPath == "" || majorMinor == "" {
  734. return nil, fmt.Errorf("failed to get root path and major:minor for %s", hostSource)
  735. }
  736. var refs []string
  737. for i := range mis {
  738. if mis[i].id == mountID {
  739. // Ignore mount entry for mount source itself.
  740. continue
  741. }
  742. if mis[i].root == rootPath && mis[i].majorMinor == majorMinor {
  743. refs = append(refs, mis[i].mountPoint)
  744. }
  745. }
  746. return refs, nil
  747. }