Repository: conda/conda

Issue:
conda update icu (54.1-0 --> 56.1-4 conda-forge)
In a new installation, it appears that going from icu 54 to 56 will fail unless the following is done (at least on linux):

bash Anaconda2-4.2.0-Linux-x86_64.sh
conda remove icu
rm -r $HOME/anaconda2/lib/icu
conda install -c conda-forge icu=56.1

In other words, using the first and fourth lines alone fails with:

CondaOSError: OS error: failed to link (src=u'/home/anaconda2/pkgs/icu-56.1-4/lib/icu/current', dst='/home/anaconda2/lib/icu/current', type=3, error=OSError(17, 'File exists'))



Discussion:
@rickedanielson could you share the output from `conda info` and the full stack trace for the case when you get the error?

```
> conda info
Current conda install:

               platform : linux-64
          conda version : 4.2.9
       conda is private : False
      conda-env version : 4.2.9
    conda-build version : 2.0.2
         python version : 2.7.12.final.0
       requests version : 2.11.1
       root environment : $HOME/anaconda2  (writable)
    default environment : $HOME/anaconda2
       envs directories : $HOME/anaconda2/envs
          package cache : $HOME/anaconda2/pkgs
           channel URLs : https://conda.anaconda.org/conda-forge/linux-64/
                          https://conda.anaconda.org/conda-forge/noarch/
                          https://repo.continuum.io/pkgs/free/linux-64/
                          https://repo.continuum.io/pkgs/free/noarch/
                          https://repo.continuum.io/pkgs/pro/linux-64/
                          https://repo.continuum.io/pkgs/pro/noarch/
            config file : $HOME/.condarc
           offline mode : False

> conda install -c conda-forge icu=56.1
Fetching package metadata .........
Solving package specifications: ..........
Package plan for installation in environment $HOME/anaconda2:
The following packages will be downloaded:
    package                    |            build
    ---------------------------|-----------------
    icu-56.1                   |                4        21.9 MB  conda-forge
    conda-env-2.5.2            |           py27_0          26 KB  conda-forge
    conda-4.1.11               |           py27_1         204 KB  conda-forge
    ------------------------------------------------------------
                                           Total:        22.1 MB
The following NEW packages will be INSTALLED:
    conda-env: 2.5.2-py27_0 conda-forge
The following packages will be UPDATED:
    icu:       54.1-0                   --> 56.1-4        conda-forge
The following packages will be SUPERCEDED by a higher-priority channel:
    conda:     4.2.9-py27_0             --> 4.1.11-py27_1 conda-forge
Proceed ([y]/n)? 
Fetching packages ...
icu-56.1-4.tar 100% |#############################################################################################################################################| Time: 0:00:39 583.39 kB/s
conda-env-2.5. 100% |#############################################################################################################################################| Time: 0:00:00 164.50 kB/s
conda-4.1.11-p 100% |#############################################################################################################################################| Time: 0:00:00 303.18 kB/s
Extracting packages ...
[      COMPLETE      ]|################################################################################################################################################################| 100%
Unlinking packages ...
[      COMPLETE      ]|################################################################################################################################################################| 100%
Linking packages ...
CondaOSError: OS error: failed to link (src=u'$HOME/anaconda2/pkgs/icu-56.1-4/lib/icu/current', dst='$HOME/anaconda2/lib/icu/current', type=3, error=OSError(17, 'File exists'))

> ls -al $HOME/anaconda2/pkgs/icu-56.1-4/lib/icu/current $HOME/anaconda2/lib/icu/current
lrwxrwxrwx 1 here now 4 okt.  10 22:14 $HOME/anaconda2/lib/icu/current -> 54.1
lrwxrwxrwx 1 here now 4 okt.  10 22:16 $HOME/anaconda2/pkgs/icu-56.1-4/lib/icu/current -> 56.1

> cat $HOME/.condarc
channels:
  - conda-forge
  - defaults
```


Resolving patch:
diff --git a/conda/common/disk.py b/conda/common/disk.py
--- a/conda/common/disk.py
+++ b/conda/common/disk.py
@@ -6,7 +6,7 @@
 from itertools import chain
 from logging import getLogger
 from os import W_OK, access, chmod, getpid, listdir, lstat, makedirs, rename, unlink, walk
-from os.path import abspath, basename, dirname, isdir, join, lexists
+from os.path import abspath, basename, dirname, isdir, isfile, islink, join, lexists
 from shutil import rmtree
 from stat import S_IEXEC, S_IMODE, S_ISDIR, S_ISLNK, S_ISREG, S_IWRITE
 from time import sleep
@@ -192,10 +192,10 @@ def rm_rf(path, max_retries=5, trash=True):
                 backoff_rmdir(path)
             finally:
                 # If path was removed, ensure it's not in linked_data_
-                if not isdir(path):
+                if islink(path) or isfile(path):
                     from conda.install import delete_linked_data_any
                     delete_linked_data_any(path)
-        elif lexists(path):
+        if lexists(path):
             try:
                 backoff_unlink(path)
                 return True


Test patch:
diff --git a/tests/common/test_disk.py b/tests/common/test_disk.py
new file mode 100644
--- /dev/null
+++ b/tests/common/test_disk.py
@@ -0,0 +1,58 @@
+import unittest
+import pytest
+from os.path import join, abspath
+import os
+
+from conda.utils import on_win
+from conda.compat import text_type
+from conda.common.disk import rm_rf
+from conda.base.context import context
+
+
+def can_not_symlink():
+    return on_win and context.default_python[0] == '2'
+
+
+def _write_file(path, content):
+    with open(path, "w") as fh:
+        fh.write(content)
+
+
+def test_remove_file(tmpdir):
+    test_file = "test.txt"
+    path = join(text_type(tmpdir), test_file)
+    _write_file(path, "welcome to the ministry of silly walks")
+    assert rm_rf(path) is True
+
+
+@pytest.mark.skipif(not on_win, reason="Testing case for windows is different then Unix")
+def test_remove_file_to_trash(tmpdir):
+    test_file = "test.txt"
+    path = join(text_type(tmpdir), test_file)
+    _write_file(path, "welcome to the ministry of silly walks")
+    assert rm_rf(path) is True
+
+
+def test_remove_dir(tmpdir):
+    test_dir = "test"
+    tmpdir.mkdir(test_dir)
+    path = join(text_type(tmpdir), test_dir)
+    assert rm_rf(path) is True
+
+
+@pytest.mark.skipif(can_not_symlink(), reason="symlink function not available")
+def test_remove_link_to_file(tmpdir):
+    dst_link = join(text_type(tmpdir), "test_link")
+    src_file = join(text_type(tmpdir), "test_file")
+    _write_file(src_file, "welcome to the ministry of silly walks")
+    os.symlink(src_file, dst_link)
+    assert rm_rf(dst_link) is True
+
+
+@pytest.mark.skipif(can_not_symlink(), reason="symlink function not available")
+def test_remove_link_to_dir(tmpdir):
+    dst_link = join(text_type(tmpdir), "test_link")
+    src_dir = join(text_type(tmpdir), "test_dir")
+    tmpdir.mkdir("test_dir")
+    os.symlink(src_dir, dst_link)
+    assert rm_rf(dst_link) is True