Change:
<!-- Please use this template while reporting a bug and provide as much info as possible. Not doing so may result in your bug not being addressed in a timely manner. Thanks! If the matter is security related, please disclose it privately via --What happened: pod with smb(cifs) mount could not be terminated on Windows when that smb account is invalid, the pod will be stuck forever What you expected to happen: How to reproduce it (as minimally and precisely as possible): a pod with azure file mount on Windows the storage account of azure file invalid the pod with azure file mount could not be terminated forever Anything else we need to know?: similar to case: Environment: Kubernetes version (use ): v1.12.6 Cloud provider or hardware configuration: OS (e.g: ): Kernel (e.g. ): Install tools: Others: /assign /sig windows

Discussion:
"exec_mount_unsupported.go", "fake.go", "mount.go", <del> "mount_helper.go", </del> <ins> "mount_helper_common.go", "mount_helper_unix.go", "mount_helper_windows.go", </ins> "mount_linux.go", "mount_unsupported.go", "mount_windows.go",

<del> /* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mount import ( "fmt" "os" "syscall" "k8s.io/klog" ) // CleanupMountPoint unmounts the given path and // deletes the remaining directory if successful. // if extensiveMountPointCheck is true // IsNotMountPoint will be called instead of IsLikelyNotMountPoint. // IsNotMountPoint is more expensive but properly handles bind mounts within the same fs. func CleanupMountPoint(mountPath string, mounter Interface, extensiveMountPointCheck bool) error { // mounter.ExistsPath cannot be used because for containerized kubelet, we need to check // the path in the kubelet container, not on the host. pathExists, pathErr := PathExists(mountPath) if !pathExists { klog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath) return nil } corruptedMnt := IsCorruptedMnt(pathErr) if pathErr != nil && !corruptedMnt { return fmt.Errorf("Error checking path: %v", pathErr) } return doCleanupMountPoint(mountPath, mounter, extensiveMountPointCheck, corruptedMnt) } // doCleanupMountPoint unmounts the given path and // deletes the remaining directory if successful. // if extensiveMountPointCheck is true // IsNotMountPoint will be called instead of IsLikelyNotMountPoint. // IsNotMountPoint is more expensive but properly handles bind mounts within the same fs. // if corruptedMnt is true, it means that the mountPath is a corrupted mountpoint, and the mount point check // will be skipped func doCleanupMountPoint(mountPath string, mounter Interface, extensiveMountPointCheck bool, corruptedMnt bool) error { if !corruptedMnt { var notMnt bool var err error if extensiveMountPointCheck { notMnt, err = mounter.IsNotMountPoint(mountPath) } else { notMnt, err = mounter.IsLikelyNotMountPoint(mountPath) } if err != nil { return err } if notMnt { klog.Warningf("Warning: %q is not a mountpoint, deleting", mountPath) return os.Remove(mountPath) } } // Unmount the mount path klog.V(4).Infof("%q is a mountpoint, unmounting", mountPath) if err := mounter.Unmount(mountPath); err != nil { return err } notMnt, mntErr := mounter.IsLikelyNotMountPoint(mountPath) if mntErr != nil { return mntErr } if notMnt { klog.V(4).Infof("%q is unmounted, deleting the directory", mountPath) return os.Remove(mountPath) } return fmt.Errorf("Failed to unmount path %v", mountPath) } // TODO: clean this up to use pkg/util/file/FileExists // PathExists returns true if the specified path exists. func PathExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } else if os.IsNotExist(err) { return false, nil } else if IsCorruptedMnt(err) { return true, err } else { return false, err } } // IsCorruptedMnt return true if err is about corrupted mount point func IsCorruptedMnt(err error) bool { if err == nil { return false } var underlyingError error switch pe := err.(type) { case nil: return false case *os.PathError: underlyingError = pe.Err case *os.LinkError: underlyingError = pe.Err case *os.SyscallError: underlyingError = pe.Err } return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES } </del>

<ins> /* Copyright 2018 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mount import ( "fmt" "os" "k8s.io/klog" ) // CleanupMountPoint unmounts the given path and // deletes the remaining directory if successful. // if extensiveMountPointCheck is true // IsNotMountPoint will be called instead of IsLikelyNotMountPoint. // IsNotMountPoint is more expensive but properly handles bind mounts within the same fs. func CleanupMountPoint(mountPath string, mounter Interface, extensiveMountPointCheck bool) error { // mounter.ExistsPath cannot be used because for containerized kubelet, we need to check // the path in the kubelet container, not on the host. pathExists, pathErr := PathExists(mountPath) if !pathExists { klog.Warningf("Warning: Unmount skipped because path does not exist: %v", mountPath) return nil } corruptedMnt := IsCorruptedMnt(pathErr) if pathErr != nil && !corruptedMnt { return fmt.Errorf("Error checking path: %v", pathErr) } return doCleanupMountPoint(mountPath, mounter, extensiveMountPointCheck, corruptedMnt) } // doCleanupMountPoint unmounts the given path and // deletes the remaining directory if successful. // if extensiveMountPointCheck is true // IsNotMountPoint will be called instead of IsLikelyNotMountPoint. // IsNotMountPoint is more expensive but properly handles bind mounts within the same fs. // if corruptedMnt is true, it means that the mountPath is a corrupted mountpoint, and the mount point check // will be skipped func doCleanupMountPoint(mountPath string, mounter Interface, extensiveMountPointCheck bool, corruptedMnt bool) error { if !corruptedMnt { var notMnt bool var err error if extensiveMountPointCheck { notMnt, err = mounter.IsNotMountPoint(mountPath) } else { notMnt, err = mounter.IsLikelyNotMountPoint(mountPath) } if err != nil { return err } if notMnt { klog.Warningf("Warning: %q is not a mountpoint, deleting", mountPath) return os.Remove(mountPath) } } // Unmount the mount path klog.V(4).Infof("%q is a mountpoint, unmounting", mountPath) if err := mounter.Unmount(mountPath); err != nil { return err } notMnt, mntErr := mounter.IsLikelyNotMountPoint(mountPath) if mntErr != nil { return mntErr } if notMnt { klog.V(4).Infof("%q is unmounted, deleting the directory", mountPath) return os.Remove(mountPath) } return fmt.Errorf("Failed to unmount path %v", mountPath) } // TODO: clean this up to use pkg/util/file/FileExists // PathExists returns true if the specified path exists. func PathExists(path string) (bool, error) { _, err := os.Stat(path) if err == nil { return true, nil } else if os.IsNotExist(err) { return false, nil } else if IsCorruptedMnt(err) { return true, err } else { return false, err } } </ins>

<ins> // +build !windows /* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mount import ( "os" "syscall" ) // IsCorruptedMnt return true if err is about corrupted mount point func IsCorruptedMnt(err error) bool { if err == nil { return false } var underlyingError error switch pe := err.(type) { case nil: return false case *os.PathError: underlyingError = pe.Err case *os.LinkError: underlyingError = pe.Err case *os.SyscallError: underlyingError = pe.Err } return underlyingError == syscall.ENOTCONN || underlyingError == syscall.ESTALE || underlyingError == syscall.EIO || underlyingError == syscall.EACCES } </ins>

<ins> // +build windows /* Copyright 2019 The Kubernetes Authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package mount import ( "os" "syscall" "k8s.io/klog" ) // following failure codes are from https://docs.microsoft.com/en-us/windows/desktop/debug/system-error-codes--1300-1699- // ERROR_BAD_NETPATH                 = 53 // ERROR_NETWORK_BUSY                = 54 // ERROR_UNEXP_NET_ERR               = 59 // ERROR_NETNAME_DELETED             = 64 // ERROR_NETWORK_ACCESS_DENIED       = 65 // ERROR_BAD_DEV_TYPE                = 66 // ERROR_BAD_NET_NAME                = 67 // ERROR_SESSION_CREDENTIAL_CONFLICT = 1219 // ERROR_LOGON_FAILURE               = 1326 var errorNoList = [...]int{53, 54, 59, 64, 65, 66, 67, 1219, 1326} // IsCorruptedMnt return true if err is about corrupted mount point func IsCorruptedMnt(err error) bool { if err == nil { return false } var underlyingError error switch pe := err.(type) { case nil: return false case *os.PathError: underlyingError = pe.Err case *os.LinkError: underlyingError = pe.Err case *os.SyscallError: underlyingError = pe.Err } if ee, ok := underlyingError.(syscall.Errno); ok { for _, errno := range errorNoList { if int(ee) == errno { klog.Warningf("IsCorruptedMnt failed with error: %v, error code: %v", err, errno) return true } } } return false } </ins>

// GetMountRefs : empty implementation here since there is no place to query all mount points on Windows func (mounter *Mounter) GetMountRefs(pathname string) ([]string, error) { <del> pathExists, pathErr := PathExists(normalizeWindowsPath(pathname)) // TODO(#75012): Need a Windows specific IsCorruptedMnt function that checks against whatever errno's // Windows emits when we try to Stat a corrupted mount // https://golang.org/pkg/syscall/?GOOS=windows&GOARCH=amd64#Errno </del> <ins> windowsPath := normalizeWindowsPath(pathname) pathExists, pathErr := PathExists(windowsPath) </ins> if !pathExists { return []string{}, nil <ins> } else if IsCorruptedMnt(pathErr) { klog.Warningf("GetMountRefs found corrupted mount at %s, treating as unmounted path", windowsPath) return []string{}, nil </ins> } else if pathErr != nil { <del> return nil, fmt.Errorf("error checking path %s: %v", normalizeWindowsPath(pathname), pathErr) </del> <ins> return nil, fmt.Errorf("error checking path %s: %v", windowsPath, pathErr) </ins> } return []string{pathname}, nil }