Repository: iterative/dvc
Issue: Hardlinks not correctly detected on SMB network share (NTFS?)

Using DVC version 0.80, with repo and cache both on the same network drive.
Windows setup.

I'm testing the `unprotect` command, but I assume other commands relying on the same check will also exhibit strange failures.

```
PS Q:\\dvc-test> dvc --version
0.80.0
PS Q:\\dvc-test> cat .dvc\\config
[cache]
dir = 'Q:\\DVC cache (test)'
type = "reflink,symlink,hardlink,copy"
protected = true
PS Q:\\dvc-test> dir


    Directory: Q:\\dvc-test


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       06/01/2020     16:58                .dvc
-a----       06/01/2020     14:57             24 .gitignore
-a----       06/01/2020     14:57            155 big_file.dvc
-ar---       27/12/2019     16:42      134217728 big_file

PS Q:\\dvc-test> fsutil.exe hardlink list big_file
Error:  The request is not supported.
```
As you can see, I checked out my files from git at 14:57 today; dvc checkout created a protected link to the original 128Mb file created 10 days ago, rather than a copy (as requested), but Windows can't tell me that it is a link.  Trust me, it is 😉
```
PS Q:\\dvc-test> dvc unprotect big_file
PS Q:\\dvc-test> dir


    Directory: Q:\\dvc-test


Mode                LastWriteTime         Length Name
----                -------------         ------ ----
d-----       06/01/2020     16:58                .dvc
-a----       06/01/2020     14:57             24 .gitignore
-a----       06/01/2020     14:57            155 big_file.dvc
-a----       27/12/2019     16:42      134217728 big_file

```
Strange - the `unprotect` operation was very fast, and the creation date of the file is still last year rather than today.
```
PS Q:\\dvc-test> echo Hello >> big_file
PS Q:\\dvc-test> dvc status
WARNING: corrupted cache file '..\\DVC cache (test)\\1d\\cca63ad430e16fa12716d1a9bb3a6c'.
big_file_3.dvc:
     changed outs:
                                                                                                                       not in cache:       big_file_3
```
Sure enough, modifying `big_file` corrupts the cache, since I was not modifying a copy.

Changed files:
dvc/remote/local.py, dvc/remote/base.py, dvc/command/version.py, tests/func/test_version.py

diff --git a/dvc/command/version.py b/dvc/command/version.py
index aa7d630af..60c6109d3 100644
--- a/dvc/command/version.py
+++ b/dvc/command/version.py
@@ -86,9 +86,9 @@ class CmdVersion(CmdBaseNoRepo):
     @staticmethod
     def get_linktype_support_info(repo):
         links = {
-            "reflink": System.reflink,
-            "hardlink": System.hardlink,
-            "symlink": System.symlink,
+            "reflink": (System.reflink, None),
+            "hardlink": (System.hardlink, System.is_hardlink),
+            "symlink": (System.symlink, System.is_symlink),
         }
 
         fname = "." + str(uuid.uuid4())
@@ -98,18 +98,16 @@ class CmdVersion(CmdBaseNoRepo):
 
         cache = []
 
-        for name, link in links.items():
+        for name, (link, is_link) in links.items():
             try:
                 link(src, dst)
+                status = "supported"
+                if is_link and not is_link(dst):
+                    status = "broken"
                 os.unlink(dst)
-                supported = True
             except DvcException:
-                supported = False
-            cache.append(
-                "{name} - {supported}".format(
-                    name=name, supported=True if supported else False
-                )
-            )
+                status = "not supported"
+            cache.append("{name} - {status}".format(name=name, status=status))
         os.remove(src)
 
         return ", ".join(cache)
diff --git a/dvc/remote/base.py b/dvc/remote/base.py
index 7262ba1a9..1f466dad2 100644
--- a/dvc/remote/base.py
+++ b/dvc/remote/base.py
@@ -392,13 +392,24 @@ class RemoteBASE(object):
 
         self._try_links(from_info, to_info, link_types)
 
+    def _verify_link(self, path_info, link_type):
+        if self.cache_type_confirmed:
+            return
+
+        is_link = getattr(self, "is_{}".format(link_type), None)
+        if is_link and not is_link(path_info):
+            self.remove(path_info)
+            raise DvcException("failed to verify {}".format(link_type))
+
+        self.cache_type_confirmed = True
+
     @slow_link_guard
     def _try_links(self, from_info, to_info, link_types):
         while link_types:
             link_method = getattr(self, link_types[0])
             try:
                 self._do_link(from_info, to_info, link_method)
-                self.cache_type_confirmed = True
+                self._verify_link(to_info, link_types[0])
                 return
 
             except DvcException as exc:
diff --git a/dvc/remote/local.py b/dvc/remote/local.py
index d9e0bc69c..ba06e4a1c 100644
--- a/dvc/remote/local.py
+++ b/dvc/remote/local.py
@@ -178,6 +178,10 @@ class RemoteLOCAL(RemoteBASE):
     def symlink(from_info, to_info):
         System.symlink(from_info, to_info)
 
+    @staticmethod
+    def is_symlink(path_info):
+        return System.is_symlink(path_info)
+
     def hardlink(self, from_info, to_info):
         # If there are a lot of empty files (which happens a lot in datasets),
         # and the cache type is `hardlink`, we might reach link limits and
@@ -204,6 +208,10 @@ class RemoteLOCAL(RemoteBASE):
 
         System.hardlink(from_info, to_info)
 
+    @staticmethod
+    def is_hardlink(path_info):
+        return System.is_hardlink(path_info)
+
     @staticmethod
     def reflink(from_info, to_info):
         System.reflink(from_info, to_info)
diff --git a/tests/func/test_version.py b/tests/func/test_version.py
index 5af7dba4e..31d0d7e66 100644
--- a/tests/func/test_version.py
+++ b/tests/func/test_version.py
@@ -17,7 +17,9 @@ def test_info_in_repo(tmp_dir, dvc, caplog):
     assert re.search(r"Platform: .*", caplog.text)
     assert re.search(r"Binary: (True|False)", caplog.text)
     assert re.search(r"Package: .*", caplog.text)
-    assert re.search(r"(Cache: (.*link - (True|False)(,\\s)?){3})", caplog.text)
+    assert re.search(
+        r"(Cache: (.*link - (not )?supported(,\\s)?){3})", caplog.text
+    )
 
 
 @pytest.mark.skipif(psutil is None, reason="No psutil.")
@@ -37,9 +39,7 @@ def test_info_outside_of_repo(tmp_dir, caplog):
     assert re.search(r"Platform: .*", caplog.text)
     assert re.search(r"Binary: (True|False)", caplog.text)
     assert re.search(r"Package: .*", caplog.text)
-    assert not re.search(
-        r"(Cache: (.*link - (True|False)(,\\s)?){3})", caplog.text
-    )
+    assert not re.search(r"(Cache: (.*link - (not )?(,\\s)?){3})", caplog.text)
 
 
 @pytest.mark.skipif(psutil is None, reason="No psutil.")