mount_windows.go 9.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  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/filepath"
  20. "strings"
  21. "k8s.io/klog"
  22. utilexec "k8s.io/utils/exec"
  23. "k8s.io/utils/keymutex"
  24. utilpath "k8s.io/utils/path"
  25. )
  26. // Mounter provides the default implementation of mount.Interface
  27. // for the windows platform. This implementation assumes that the
  28. // kubelet is running in the host's root mount namespace.
  29. type Mounter struct {
  30. mounterPath string
  31. }
  32. // New returns a mount.Interface for the current system.
  33. // It provides options to override the default mounter behavior.
  34. // mounterPath allows using an alternative to `/bin/mount` for mounting.
  35. func New(mounterPath string) Interface {
  36. return &Mounter{
  37. mounterPath: mounterPath,
  38. }
  39. }
  40. // acquire lock for smb mount
  41. var getSMBMountMutex = keymutex.NewHashed(0)
  42. // Mount : mounts source to target with given options.
  43. // currently only supports cifs(smb), bind mount(for disk)
  44. func (mounter *Mounter) Mount(source string, target string, fstype string, options []string) error {
  45. target = NormalizeWindowsPath(target)
  46. if source == "tmpfs" {
  47. klog.V(3).Infof("mounting source (%q), target (%q), with options (%q)", source, target, options)
  48. return os.MkdirAll(target, 0755)
  49. }
  50. parentDir := filepath.Dir(target)
  51. if err := os.MkdirAll(parentDir, 0755); err != nil {
  52. return err
  53. }
  54. klog.V(4).Infof("mount options(%q) source:%q, target:%q, fstype:%q, begin to mount",
  55. options, source, target, fstype)
  56. bindSource := source
  57. // tell it's going to mount azure disk or azure file according to options
  58. if bind, _, _ := MakeBindOpts(options); bind {
  59. // mount azure disk
  60. bindSource = NormalizeWindowsPath(source)
  61. } else {
  62. if len(options) < 2 {
  63. klog.Warningf("mount options(%q) command number(%d) less than 2, source:%q, target:%q, skip mounting",
  64. options, len(options), source, target)
  65. return nil
  66. }
  67. // currently only cifs mount is supported
  68. if strings.ToLower(fstype) != "cifs" {
  69. return fmt.Errorf("only cifs mount is supported now, fstype: %q, mounting source (%q), target (%q), with options (%q)", fstype, source, target, options)
  70. }
  71. // lock smb mount for the same source
  72. getSMBMountMutex.LockKey(source)
  73. defer getSMBMountMutex.UnlockKey(source)
  74. if output, err := newSMBMapping(options[0], options[1], source); err != nil {
  75. if isSMBMappingExist(source) {
  76. klog.V(2).Infof("SMB Mapping(%s) already exists, now begin to remove and remount", source)
  77. if output, err := removeSMBMapping(source); err != nil {
  78. return fmt.Errorf("Remove-SmbGlobalMapping failed: %v, output: %q", err, output)
  79. }
  80. if output, err := newSMBMapping(options[0], options[1], source); err != nil {
  81. return fmt.Errorf("New-SmbGlobalMapping remount failed: %v, output: %q", err, output)
  82. }
  83. } else {
  84. return fmt.Errorf("New-SmbGlobalMapping failed: %v, output: %q", err, output)
  85. }
  86. }
  87. }
  88. if output, err := exec.Command("cmd", "/c", "mklink", "/D", target, bindSource).CombinedOutput(); err != nil {
  89. klog.Errorf("mklink failed: %v, source(%q) target(%q) output: %q", err, bindSource, target, string(output))
  90. return err
  91. }
  92. return nil
  93. }
  94. // do the SMB mount with username, password, remotepath
  95. // return (output, error)
  96. func newSMBMapping(username, password, remotepath string) (string, error) {
  97. if username == "" || password == "" || remotepath == "" {
  98. return "", fmt.Errorf("invalid parameter(username: %s, password: %s, remoteapth: %s)", username, password, remotepath)
  99. }
  100. // use PowerShell Environment Variables to store user input string to prevent command line injection
  101. // https://docs.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_environment_variables?view=powershell-5.1
  102. cmdLine := `$PWord = ConvertTo-SecureString -String $Env:smbpassword -AsPlainText -Force` +
  103. `;$Credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList $Env:smbuser, $PWord` +
  104. `;New-SmbGlobalMapping -RemotePath $Env:smbremotepath -Credential $Credential`
  105. cmd := exec.Command("powershell", "/c", cmdLine)
  106. cmd.Env = append(os.Environ(),
  107. fmt.Sprintf("smbuser=%s", username),
  108. fmt.Sprintf("smbpassword=%s", password),
  109. fmt.Sprintf("smbremotepath=%s", remotepath))
  110. output, err := cmd.CombinedOutput()
  111. return string(output), err
  112. }
  113. // check whether remotepath is already mounted
  114. func isSMBMappingExist(remotepath string) bool {
  115. cmd := exec.Command("powershell", "/c", `Get-SmbGlobalMapping -RemotePath $Env:smbremotepath`)
  116. cmd.Env = append(os.Environ(), fmt.Sprintf("smbremotepath=%s", remotepath))
  117. _, err := cmd.CombinedOutput()
  118. return err == nil
  119. }
  120. // remove SMB mapping
  121. func removeSMBMapping(remotepath string) (string, error) {
  122. cmd := exec.Command("powershell", "/c", `Remove-SmbGlobalMapping -RemotePath $Env:smbremotepath -Force`)
  123. cmd.Env = append(os.Environ(), fmt.Sprintf("smbremotepath=%s", remotepath))
  124. output, err := cmd.CombinedOutput()
  125. return string(output), err
  126. }
  127. // Unmount unmounts the target.
  128. func (mounter *Mounter) Unmount(target string) error {
  129. klog.V(4).Infof("azureMount: Unmount target (%q)", target)
  130. target = NormalizeWindowsPath(target)
  131. if output, err := exec.Command("cmd", "/c", "rmdir", target).CombinedOutput(); err != nil {
  132. klog.Errorf("rmdir failed: %v, output: %q", err, string(output))
  133. return err
  134. }
  135. return nil
  136. }
  137. // List returns a list of all mounted filesystems. todo
  138. func (mounter *Mounter) List() ([]MountPoint, error) {
  139. return []MountPoint{}, nil
  140. }
  141. // IsLikelyNotMountPoint determines if a directory is not a mountpoint.
  142. func (mounter *Mounter) IsLikelyNotMountPoint(file string) (bool, error) {
  143. stat, err := os.Lstat(file)
  144. if err != nil {
  145. return true, err
  146. }
  147. // If current file is a symlink, then it is a mountpoint.
  148. if stat.Mode()&os.ModeSymlink != 0 {
  149. target, err := os.Readlink(file)
  150. if err != nil {
  151. return true, fmt.Errorf("readlink error: %v", err)
  152. }
  153. exists, err := utilpath.Exists(utilpath.CheckFollowSymlink, target)
  154. if err != nil {
  155. return true, err
  156. }
  157. return !exists, nil
  158. }
  159. return true, nil
  160. }
  161. // GetMountRefs : empty implementation here since there is no place to query all mount points on Windows
  162. func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) {
  163. windowsPath := NormalizeWindowsPath(pathname)
  164. pathExists, pathErr := PathExists(windowsPath)
  165. if !pathExists {
  166. return []string{}, nil
  167. } else if IsCorruptedMnt(pathErr) {
  168. klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", windowsPath)
  169. return []string{}, nil
  170. } else if pathErr != nil {
  171. return nil, fmt.Errorf("error checking path %s: %v", windowsPath, pathErr)
  172. }
  173. return []string{pathname}, nil
  174. }
  175. func (mounter *SafeFormatAndMount) formatAndMount(source string, target string, fstype string, options []string) error {
  176. // Try to mount the disk
  177. klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, source, target)
  178. if err := ValidateDiskNumber(source); err != nil {
  179. klog.Errorf("diskMount: formatAndMount failed, err: %v", err)
  180. return err
  181. }
  182. if len(fstype) == 0 {
  183. // Use 'NTFS' as the default
  184. fstype = "NTFS"
  185. }
  186. // format disk if it is unformatted(raw)
  187. cmd := fmt.Sprintf("Get-Disk -Number %s | Where partitionstyle -eq 'raw' | Initialize-Disk -PartitionStyle MBR -PassThru"+
  188. " | New-Partition -AssignDriveLetter -UseMaximumSize | Format-Volume -FileSystem %s -Confirm:$false", source, fstype)
  189. if output, err := mounter.Exec.Command("powershell", "/c", cmd).CombinedOutput(); err != nil {
  190. return fmt.Errorf("diskMount: format disk failed, error: %v, output: %q", err, string(output))
  191. }
  192. klog.V(4).Infof("diskMount: Disk successfully formatted, disk: %q, fstype: %q", source, fstype)
  193. driveLetter, err := getDriveLetterByDiskNumber(source, mounter.Exec)
  194. if err != nil {
  195. return err
  196. }
  197. driverPath := driveLetter + ":"
  198. target = NormalizeWindowsPath(target)
  199. klog.V(4).Infof("Attempting to formatAndMount disk: %s %s %s", fstype, driverPath, target)
  200. if output, err := mounter.Exec.Command("cmd", "/c", "mklink", "/D", target, driverPath).CombinedOutput(); err != nil {
  201. klog.Errorf("mklink failed: %v, output: %q", err, string(output))
  202. return err
  203. }
  204. return nil
  205. }
  206. // Get drive letter according to windows disk number
  207. func getDriveLetterByDiskNumber(diskNum string, exec utilexec.Interface) (string, error) {
  208. cmd := fmt.Sprintf("(Get-Partition -DiskNumber %s).DriveLetter", diskNum)
  209. output, err := exec.Command("powershell", "/c", cmd).CombinedOutput()
  210. if err != nil {
  211. return "", fmt.Errorf("azureMount: Get Drive Letter failed: %v, output: %q", err, string(output))
  212. }
  213. if len(string(output)) < 1 {
  214. return "", fmt.Errorf("azureMount: Get Drive Letter failed, output is empty")
  215. }
  216. return string(output)[:1], nil
  217. }
  218. // getAllParentLinks walks all symbolic links and return all the parent targets recursively
  219. func getAllParentLinks(path string) ([]string, error) {
  220. const maxIter = 255
  221. links := []string{}
  222. for {
  223. links = append(links, path)
  224. if len(links) > maxIter {
  225. return links, fmt.Errorf("unexpected length of parent links: %v", links)
  226. }
  227. fi, err := os.Lstat(path)
  228. if err != nil {
  229. return links, fmt.Errorf("Lstat: %v", err)
  230. }
  231. if fi.Mode()&os.ModeSymlink == 0 {
  232. break
  233. }
  234. path, err = os.Readlink(path)
  235. if err != nil {
  236. return links, fmt.Errorf("Readlink error: %v", err)
  237. }
  238. }
  239. return links, nil
  240. }