mount_windows.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. // +build windows
  2. /*
  3. Copyright 2017 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"
  20. "path/filepath"
  21. "strconv"
  22. "strings"
  23. "k8s.io/klog"
  24. "k8s.io/utils/keymutex"
  25. utilpath "k8s.io/utils/path"
  26. )
  27. // Mounter provides the default implementation of mount.Interface
  28. // for the windows platform. This implementation assumes that the
  29. // kubelet is running in the host's root mount namespace.
  30. type Mounter struct {
  31. mounterPath string
  32. }
  33. // New returns a mount.Interface for the current system.
  34. // It provides options to override the default mounter behavior.
  35. // mounterPath allows using an alternative to `/bin/mount` for mounting.
  36. func New(mounterPath string) Interface {
  37. return &Mounter{
  38. mounterPath: mounterPath,
  39. }
  40. }
  41. // acquire lock for smb mount
  42. var getSMBMountMutex = keymutex.NewHashed(0)
  43. // Mount : mounts source to target with given options.
  44. // currently only supports cifs(smb), bind mount(for disk)
  45. func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
  46. target = normalizeWindowsPath(target)
  47. if source == "tmpfs" {
  48. klog.V(3).Infof("mounting source (%q), target (%q), with options (%q)", source, target, options)
  49. return os.MkdirAll(target, 0755)
  50. }
  51. parentDir := filepath.Dir(target)
  52. if err := os.MkdirAll(parentDir, 0755); err != nil {
  53. return err
  54. }
  55. klog.V(4).Infof("mount options(%q) source:%q, target:%q, fstype:%q, begin to mount",
  56. options, source, target, fstype)
  57. bindSource := source
  58. // tell it's going to mount azure disk or azure file according to options
  59. if bind, _, _ := IsBind(options); bind {
  60. // mount azure disk
  61. bindSource = normalizeWindowsPath(source)
  62. } else {
  63. if len(options) < 2 {
  64. klog.Warningf("mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
  65. options, len(options), source, target)
  66. return nil
  67. }
  68. // currently only cifs mount is supported
  69. if strings.ToLower(fstype) != "cifs" {
  70. return fmt.Errorf("only cifs mount is supported now, fstype: %q, mounting source (%q), target (%q), with options (%q)", fstype, source, target, options)
  71. }
  72. // lock smb mount for the same source
  73. getSMBMountMutex.LockKey(source)
  74. defer getSMBMountMutex.UnlockKey(source)
  75. if output, err := newSMBMapping(options[0], options[1], source); err != nil {
  76. if isSMBMappingExist(source) {
  77. klog.V(2).Infof("SMB Mapping(%s) already exists, now begin to remove and remount", source)
  78. if output, err := removeSMBMapping(source); err != nil {
  79. return fmt.Errorf("Remove-SmbGlobalMapping failed: %v, output: %q", err, output)
  80. }
  81. if output, err := newSMBMapping(options[0], options[1], source); err != nil {
  82. return fmt.Errorf("New-SmbGlobalMapping remount failed: %v, output: %q", err, output)
  83. }
  84. } else {
  85. return fmt.Errorf("New-SmbGlobalMapping failed: %v, output: %q", err, output)
  86. }
  87. }
  88. }
  89. if output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput(); err != nil {
  90. klog.Errorf("mklink failed: %v, source(%q) target(%q) output: %q", err, bindSource, target, string(output))
  91. return err
  92. }
  93. return nil
  94. }
  95. // do the SMB mount with username, password, remotepath
  96. // return (output, error)
  97. func newSMBMapping(username, password, remotepath string) (string, error) {
  98. if username == "" || password == "" || remotepath == "" {
  99. return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remoteapth: %s)", username, password, remotepath)
  100. }
  101. // use PowerShell Environment Variables to store user input string to prevent command line injection
  102. // https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
  103. cmdLine := `$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force` +
  104. `;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord` +
  105. `;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential`
  106. cmd := exec.Command("powershell", "/c", cmdLine)
  107. cmd.Env = append(os.Environ(),
  108. fmt.Sprintf("smbuser=%s", username),
  109. fmt.Sprintf("smbpassword=%s", password),
  110. fmt.Sprintf("smbremotepath=%s", remotepath))
  111. output, err := cmd.CombinedOutput()
  112. return string(output), err
  113. }
  114. // check whether remotepath is already mounted
  115. func isSMBMappingExist(remotepath string) bool {
  116. cmd := exec.Command("powershell", "/c", `Get-SmbGlobalMapping -RemotePath $Env:smbremotepath`)
  117. cmd.Env = append(os.Environ(), fmt.Sprintf("smbremotepath=%s", remotepath))
  118. _, err := cmd.CombinedOutput()
  119. return err == nil
  120. }
  121. // remove SMB mapping
  122. func removeSMBMapping(remotepath string) (string, error) {
  123. cmd := exec.Command("powershell", "/c", `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force`)
  124. cmd.Env = append(os.Environ(), fmt.Sprintf("smbremotepath=%s", remotepath))
  125. output, err := cmd.CombinedOutput()
  126. return string(output), err
  127. }
  128. // Unmount unmounts the target.
  129. func (mounter *Mounter) Unmount(target string) error {
  130. klog.V(4).Infof("azureMount: Unmount target (%q)", target)
  131. target = normalizeWindowsPath(target)
  132. if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil {
  133. klog.Errorf("rmdir failed: %v, output: %q", err, string(output))
  134. return err
  135. }
  136. return nil
  137. }
  138. // List returns a list of all mounted filesystems. todo
  139. func (mounter *Mounter) List() ([]MountPoint, error) {
  140. return []MountPoint{}, nil
  141. }
  142. // IsMountPointMatch determines if the mountpoint matches the dir
  143. func (mounter *Mounter) IsMountPointMatch(mp MountPoint, dir string) bool {
  144. return mp.Path == dir
  145. }
  146. // IsLikelyNotMountPoint determines if a directory is not a mountpoint.
  147. func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
  148. stat, err := os.Lstat(file)
  149. if err != nil {
  150. return true, err
  151. }
  152. // If current file is a symlink, then it is a mountpoint.
  153. if stat.Mode()&os.ModeSymlink != 0 {
  154. target, err := os.Readlink(file)
  155. if err != nil {
  156. return true, fmt.Errorf("readlink error: %v", err)
  157. }
  158. exists, err := mounter.ExistsPath(target)
  159. if err != nil {
  160. return true, err
  161. }
  162. return !exists, nil
  163. }
  164. return true, nil
  165. }
  166. // GetDeviceNameFromMount given a mnt point, find the device
  167. func (mounter *Mounter) GetDeviceNameFromMount(mountPath, pluginMountDir string) (string, error) {
  168. return getDeviceNameFromMount(mounter, mountPath, pluginMountDir)
  169. }
  170. // getDeviceNameFromMount find the device(drive) name in which
  171. // the mount path reference should match the given plugin mount directory. In case no mount path reference
  172. // matches, returns the volume name taken from its given mountPath
  173. func getDeviceNameFromMount(mounter Interface, mountPath, pluginMountDir string) (string, error) {
  174. refs, err := mounter.GetMountRefs(mountPath)
  175. if err != nil {
  176. klog.V(4).Infof("GetMountRefs failed for mount path %q: %v", mountPath, err)
  177. return "", err
  178. }
  179. if len(refs) == 0 {
  180. return "", fmt.Errorf("directory %s is not mounted", mountPath)
  181. }
  182. basemountPath := normalizeWindowsPath(pluginMountDir)
  183. for _, ref := range refs {
  184. if strings.Contains(ref, basemountPath) {
  185. volumeID, err := filepath.Rel(normalizeWindowsPath(basemountPath), ref)
  186. if err != nil {
  187. klog.Errorf("Failed to get volume id from mount %s - %v", mountPath, err)
  188. return "", err
  189. }
  190. return volumeID, nil
  191. }
  192. }
  193. return path.Base(mountPath), nil
  194. }
  195. // DeviceOpened determines if the device is in use elsewhere
  196. func (mounter *Mounter) DeviceOpened(pathname string) (bool, error) {
  197. return false, nil
  198. }
  199. // PathIsDevice determines if a path is a device.
  200. func (mounter *Mounter) PathIsDevice(pathname string) (bool, error) {
  201. return false, nil
  202. }
  203. // MakeRShared checks that given path is on a mount with 'rshared' mount
  204. // propagation. Empty implementation here.
  205. func (mounter *Mounter) MakeRShared(path string) error {
  206. return nil
  207. }
  208. // GetFileType checks for sockets/block/character devices
  209. func (mounter *Mounter) GetFileType(pathname string) (FileType, error) {
  210. return getFileType(pathname)
  211. }
  212. // MakeFile creates a new directory
  213. func (mounter *Mounter) MakeDir(pathname string) error {
  214. err := os.MkdirAll(pathname, os.FileMode(0755))
  215. if err != nil {
  216. if !os.IsExist(err) {
  217. return err
  218. }
  219. }
  220. return nil
  221. }
  222. // MakeFile creates an empty file
  223. func (mounter *Mounter) MakeFile(pathname string) error {
  224. f, err := os.OpenFile(pathname, os.O_CREATE, os.FileMode(0644))
  225. defer f.Close()
  226. if err != nil {
  227. if !os.IsExist(err) {
  228. return err
  229. }
  230. }
  231. return nil
  232. }
  233. // ExistsPath checks whether the path exists
  234. func (mounter *Mounter) ExistsPath(pathname string) (bool, error) {
  235. return utilpath.Exists(utilpath.CheckFollowSymlink, pathname)
  236. }
  237. // EvalHostSymlinks returns the path name after evaluating symlinks
  238. func (mounter *Mounter) EvalHostSymlinks(pathname string) (string, error) {
  239. return filepath.EvalSymlinks(pathname)
  240. }
  241. func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
  242. // Try to mount the disk
  243. klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)
  244. if err := ValidateDiskNumber(source); err != nil {
  245. klog.Errorf("diskMount: formatAndMount failed, err: %v", err)
  246. return err
  247. }
  248. if len(fstype) == 0 {
  249. // Use 'NTFS' as the default
  250. fstype = "NTFS"
  251. }
  252. // format disk if it is unformatted(raw)
  253. cmd := fmt.Sprintf("Get-Disk -Number %s | Where partitionstyle -eq 'raw' | Initialize-Disk -PartitionStyle MBR -PassThru"+
  254. " | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem %s -Confirm:$false", source, fstype)
  255. if output, err := mounter.Exec.Run("powershell", "/c", cmd); err != nil {
  256. return fmt.Errorf("diskMount: format disk failed, error: %v, output: %q", err, string(output))
  257. }
  258. klog.V(4).Infof("diskMount: Disk successfully formatted, disk: %q, fstype: %q", source, fstype)
  259. driveLetter, err := getDriveLetterByDiskNumber(source, mounter.Exec)
  260. if err != nil {
  261. return err
  262. }
  263. driverPath := driveLetter + ":"
  264. target = normalizeWindowsPath(target)
  265. klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, target)
  266. if output, err := mounter.Exec.Run("cmd", "/c", "mklink", "/D", target, driverPath); err != nil {
  267. klog.Errorf("mklink failed: %v, output: %q", err, string(output))
  268. return err
  269. }
  270. return nil
  271. }
  272. func normalizeWindowsPath(path string) string {
  273. normalizedPath := strings.Replace(path, "/", "\\", -1)
  274. if strings.HasPrefix(normalizedPath, "\\") {
  275. normalizedPath = "c:" + normalizedPath
  276. }
  277. return normalizedPath
  278. }
  279. // ValidateDiskNumber : disk number should be a number in [0, 99]
  280. func ValidateDiskNumber(disk string) error {
  281. diskNum, err := strconv.Atoi(disk)
  282. if err != nil {
  283. return fmt.Errorf("wrong disk number format: %q, err:%v", disk, err)
  284. }
  285. if diskNum < 0 || diskNum > 99 {
  286. return fmt.Errorf("disk number out of range: %q", disk)
  287. }
  288. return nil
  289. }
  290. // Get drive letter according to windows disk number
  291. func getDriveLetterByDiskNumber(diskNum string, exec Exec) (string, error) {
  292. cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
  293. output, err := exec.Run("powershell", "/c", cmd)
  294. if err != nil {
  295. return "", fmt.Errorf("azureMount: Get Drive Letter failed: %v, output: %q", err, string(output))
  296. }
  297. if len(string(output)) < 1 {
  298. return "", fmt.Errorf("azureMount: Get Drive Letter failed, output is empty")
  299. }
  300. return string(output)[:1], nil
  301. }
  302. // getAllParentLinks walks all symbolic links and return all the parent targets recursively
  303. func getAllParentLinks(path string) ([]string, error) {
  304. const maxIter = 255
  305. links := []string{}
  306. for {
  307. links = append(links, path)
  308. if len(links) > maxIter {
  309. return links, fmt.Errorf("unexpected length of parent links: %v", links)
  310. }
  311. fi, err := os.Lstat(path)
  312. if err != nil {
  313. return links, fmt.Errorf("Lstat: %v", err)
  314. }
  315. if fi.Mode()&os.ModeSymlink == 0 {
  316. break
  317. }
  318. path, err = os.Readlink(path)
  319. if err != nil {
  320. return links, fmt.Errorf("Readlink error: %v", err)
  321. }
  322. }
  323. return links, nil
  324. }
  325. // GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
  326. func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
  327. windowsPath := normalizeWindowsPath(pathname)
  328. pathExists, pathErr := PathExists(windowsPath)
  329. if !pathExists {
  330. return []string{}, nil
  331. } else if IsCorruptedMnt(pathErr) {
  332. klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", windowsPath)
  333. return []string{}, nil
  334. } else if pathErr != nil {
  335. return nil, fmt.Errorf("error checking path %s: %v", windowsPath, pathErr)
  336. }
  337. return []string{pathname}, nil
  338. }
  339. // Note that on windows, it always returns 0. We actually don't set FSGroup on
  340. // windows platform, see SetVolumeOwnership implementation.
  341. func (mounter *Mounter) GetFSGroup(pathname string) (int64, error) {
  342. return 0, nil
  343. }
  344. func (mounter *Mounter) GetSELinuxSupport(pathname string) (bool, error) {
  345. // Windows does not support SELinux.
  346. return false, nil
  347. }
  348. func (mounter *Mounter) GetMode(pathname string) (os.FileMode, error) {
  349. info, err := os.Stat(pathname)
  350. if err != nil {
  351. return 0, err
  352. }
  353. return info.Mode(), nil
  354. }