Repository: spack/spack

Issue:
Multiple users of spack on a system broken (permissions errors)
Today, I cloned spack (default branch "develop") into ~/SPACK, and went through the usual steps.

I failed very early, with neither "spack bootstrap" nor "spack install $package" working.


*Explain, in a clear and concise way, the command you ran and the result you were trying to achieve.
Example: "I ran Spack find to list all the installed packages and..."*



### Steps to reproduce the issue

```console
[myname@ac922 ~]$ . ~/SPACK/share/spack/setup-env.sh
[myname@ac922 ~]$ spack bootstrap
==> Installing environment-modules@4.3.0%gcc@4.8.5~X arch=linux-rhel7-ppc64le ^tcl@8.6.8%gcc@4.8.5 arch=linux-rhel7-ppc64le ^zlib@1.2.11%gcc@4.8.5+optimize+pic+shared arch=linux-rhel7-ppc64le to satisfy requirement for environment-modules
==> Installing zlib
==> Searching for binary cache of zlib
==> Warning: No Spack mirrors are currently configured
==> No binary for zlib found: installing from source
==> Error: StageError: No accessible stage paths in:
    ['$tempdir/spack-stage']

/home/myname/SPACK/lib/spack/spack/package.py:753, in _make_root_stage:
        750            dynamic_fetcher = fs.from_list_url(self)
        751            return [dynamic_fetcher] if dynamic_fetcher else []
        752
  >>    753        stage = Stage(fetcher, mirror_path=mp, name=stage_name, path=self.path,
        754                      search_fn=download_search)
        755        return stage


==> Error: No accessible stage paths in:
Traceback (most recent call last):
  File "/home/myname/SPACK/bin/spack", line 48, in <module>
    sys.exit(spack.main.main())
  File "/home/myname/SPACK/lib/spack/spack/main.py", line 701, in main
    e.die()  # gracefully die on any SpackErrors
  File "/home/myname/SPACK/lib/spack/spack/error.py", line 74, in die
    self.print_context()
  File "/home/myname/SPACK/lib/spack/spack/error.py", line 57, in print_context
    sys.stderr.write(self.long_message)
TypeError: expected a character buffer object
[myname@ac922 ~]$ spack install screen
==> Error: No accessible stage paths in:
Traceback (most recent call last):
  File "/home/myname/SPACK/bin/spack", line 48, in <module>
    sys.exit(spack.main.main())
  File "/home/myname/SPACK/lib/spack/spack/main.py", line 701, in main
    e.die()  # gracefully die on any SpackErrors
  File "/home/myname/SPACK/lib/spack/spack/error.py", line 74, in die
    self.print_context()
  File "/home/myname/SPACK/lib/spack/spack/error.py", line 57, in print_context
    sys.stderr.write(self.long_message)
TypeError: expected a character buffer object
```

### Error Message

(included above)

```console
$ spack -d --stacktrace install screen
lib/spack/spack/config.py:684 ==> [2019-08-20-17:25:04.194732] Reading config file /home/myname/SPACK/etc/spack/defaults/modules.yaml
lib/spack/spack/config.py:684 ==> [2019-08-20-17:25:04.210065] Reading config file /home/myname/SPACK/etc/spack/defaults/linux/modules.yaml
lib/spack/spack/config.py:684 ==> [2019-08-20-17:25:04.215119] Reading config file /home/myname/SPACK/etc/spack/defaults/config.yaml
lib/spack/spack/cmd/__init__.py:98 ==> [2019-08-20-17:25:04.242894] Imported install from built-in commands
lib/spack/spack/cmd/__init__.py:98 ==> [2019-08-20-17:25:04.244661] Imported install from built-in commands
lib/spack/spack/config.py:684 ==> [2019-08-20-17:25:04.256966] Reading config file /home/myname/SPACK/etc/spack/defaults/repos.yaml
lib/spack/spack/config.py:684 ==> [2019-08-20-17:25:04.329868] Reading config file /home/myname/SPACK/etc/spack/defaults/packages.yaml
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.358382] READ LOCK: /home/myname/.spack/cache/tags/.builtin-index.json.lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.358814] READ LOCK: /home/myname/.spack/cache/tags/.builtin-index.json.lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.360267] READ LOCK: /home/myname/.spack/cache/tags/.builtin-index.json.lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.363832] READ LOCK: /home/myname/.spack/cache/patches/.builtin-index.json.lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.364240] READ LOCK: /home/myname/.spack/cache/patches/.builtin-index.json.lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.388318] READ LOCK: /home/myname/.spack/cache/patches/.builtin-index.json.lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.392465] READ LOCK: /home/myname/.spack/cache/providers/.builtin-index.json.lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.392874] READ LOCK: /home/myname/.spack/cache/providers/.builtin-index.json.lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.433396] READ LOCK: /home/myname/.spack/cache/providers/.builtin-index.json.lock[0:0] [Released]
lib/spack/spack/config.py:684 ==> [2019-08-20-17:25:04.455639] Reading config file /home/myname/.spack/linux/compilers.yaml
lib/spack/spack/database.py:214 ==> [2019-08-20-17:25:04.497466] DATABASE LOCK TIMEOUT: 120s
lib/spack/spack/database.py:218 ==> [2019-08-20-17:25:04.497742] PACKAGE LOCK TIMEOUT: No timeout
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.498639] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.498978] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.499402] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.499798] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.500166] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.500611] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.501073] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.501445] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.501897] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.503410] WRITE LOCK: /home/myname/SPACK/opt/spack/.spack-db/prefix_lock[8654365307056604415:1] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.503748] WRITE LOCK: /home/myname/SPACK/opt/spack/.spack-db/prefix_lock[8654365307056604415:1] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.504055] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.504405] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.504846] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.505166] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquiring]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.505530] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Acquired]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.505986] READ LOCK: /home/myname/SPACK/opt/spack/.spack-db/lock[0:0] [Released]
lib/spack/llnl/util/lock.py:334 ==> [2019-08-20-17:25:04.510727] WRITE LOCK: /home/myname/SPACK/opt/spack/.spack-db/prefix_lock[8654365307056604415:1] [Released]
lib/spack/spack/main.py:700 ==> [2019-08-20-17:25:04.529440] StageError: No accessible stage paths in:
    ['$tempdir/spack-stage']
lib/spack/spack/error.py:55 ==> [2019-08-20-17:25:04.530285] Error: No accessible stage paths in:
Traceback (most recent call last):
  File "/home/myname/SPACK/bin/spack", line 48, in <module>
    sys.exit(spack.main.main())
  File "/home/myname/SPACK/lib/spack/spack/main.py", line 701, in main
    e.die()  # gracefully die on any SpackErrors
  File "/home/myname/SPACK/lib/spack/spack/error.py", line 74, in die
    self.print_context()
  File "/home/myname/SPACK/lib/spack/spack/error.py", line 57, in print_context
    sys.stderr.write(self.long_message)
TypeError: expected a character buffer object
```


### Information on your system

POWER9 (ppc64le), first SPACK setup

Any advice is welcome!


Discussion:
Hi @steffen-AEI, could you try https://github.com/spack/spack/pull/12492?

I think the issue on your system is that the temp directory is not editable by you, but unfortunately the error message which would inform you of that is currently broken (a Spack bug). #12492 will not fix your issue but will confirm that Spack cannot find a temporary directory that you have write permissions for. With that PR it should now say something like:

```
Error: no accessible stage paths in:
<the temp dir it tried to use>
```

If you think you *can* write to this directory, then we should update Spack to understand that. If in fact you cannot write to that directory, you can point Spack to a location that you do have write access to (I'll add that information if this is the case).
Since v0.12 (there's no 0.12.1 release branch, nor anything newer) works: what would be the directory to create/change permissions of?

After another git pull, commit 3b115ff is now on top of the log, but still
```
==> Error: No accessible stage paths in:
Traceback (most recent call last):
[...]
TypeError: expected a character buffer object
```

For now, I'll take the 0.12 version, doctor a recent bzip2 and singularity in, and try to proceed, but that cannot be the last word.

_Update: I just noticed that the patch hasn't been accepted yet. Merging it by hand shows me that `/tmp/spack-stage` is the path I'm not allowed to use - it does indeed exist, but is owned by someone else. IMHO it's a bad idea to allow only one Spack user per machine, can this setting be changed somehow (to the one used by older versions), e.g. with an env var?_

Update2: I found the place to change. In `etc/spack/defaults/config.yaml`, under build_stage, replace `$tempdir/spack-stage` with `$spack/var/spack/stage` (the old setting). `$tempdir/spack-stage-$user` also seems to work (I presume the reason to change this was to allow multiple users to work in a single Spack tree?).

_Update3: in `etc/spack/default/config.yaml` I'm now using_
```
  build_stage:
  #  - $tempdir/spack-stage
    - $tempdir/$user/spack-stage
    - $tempdir/spack-stage-$user
    - $spack/var/spack/stage
```
_which should be safe for all and provide a fallback to the old stage path._
Yes, I agree that somewhere you need to add a `user` component to the
tempdir path.

You expect `/tmp` to have mode 1777 and so you can create directories
there.  But `/tmp/spack-stage` does not.  It already exists and is
owned by another user, so this has become a race condition.  It works
for the first user and fails for the other N-1.

Also, I strongly recommend re-adding something like
`$spack/var/spack/stage` as above as a fallback.
Thanks for running that!

Adding `$user` will resolve this, but we intend that Spack will do this automatically for you. We are looking into fixing this so that you don't have to edit `config.yaml`.
> Yes, I agree that somewhere you need to add a `user` component to the
> tempdir path.
>

As I mentioned in my response to your comment on #12495 , staging *should* be adding `$user` to `$tempdir/spack-stage` when `$user` is not already a node in `$tempdir` assuming it is the "first accessible" `build_stage`.

Unfortunately, if there is only one `build_stage` entry with a path that exists but is not accessible, the current code will raise a `StageError` complaining `No accessible stage paths...`.  So I assume you're N-1 other users are getting this error for `/tmp/spack-stage`, correct?

FWIW. Configuration settings, such as`build_stage`, can be overridden by creating a `$HOME/.spack/config.yaml` file with custom options.

> You expect `/tmp` to have mode 1777 and so you can create directories
> there. But `/tmp/spack-stage` does not. It already exists and is
> owned by another user, so this has become a race condition. It works
> for the first user and fails for the other N-1.
> 

Permissions are another matter.

- What are the permissions on `/tmp`?
- What is the hash for the commit you are using?  (https://github.com/spack/spack/commit/3b115fffb132ee3a247079e8b6043c33707d20c5 should resolve the permissions issue)


> Also, I strongly recommend re-adding something like
> `$spack/var/spack/stage` as above as a fallback.

I was told to only provide the one path with the expectations that users would override the setting as needed.  At issue is the fact that some sites have security concerns with sharing `$spack/var/spack/stage`.
> _Update3: in `etc/spack/default/config.yaml` I'm now using_
> 
> ```
>   build_stage:
>   #  - $tempdir/spack-stage
>     - $tempdir/$user/spack-stage
>     - $tempdir/spack-stage-$user
>     - $spack/var/spack/stage
> ```
> 
> _which should be safe for all and provide a fallback to the old stage path._

I've been discussing this option with Todd (tgamblin).

As I mentioned above, you can customize your configuration settings with a `config.yaml` file in `etc/spack/config.yaml` or `$HOME/.spack/config.yaml` (see https://spack.readthedocs.io/en/latest/config_yaml.html#config-yaml).

Resolving patch:
diff --git a/lib/spack/spack/stage.py b/lib/spack/spack/stage.py
--- a/lib/spack/spack/stage.py
+++ b/lib/spack/spack/stage.py
@@ -4,6 +4,7 @@
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
 import os
+import re
 import stat
 import sys
 import errno
@@ -36,6 +37,39 @@
 _stage_prefix = 'spack-stage-'
 
 
+def _create_stage_root(path):
+    """Create the stage root directory and ensure appropriate access perms."""
+    assert path.startswith(os.path.sep) and len(path.strip()) > 1
+
+    curr_dir = os.path.sep
+    parts = path.strip(os.path.sep).split(os.path.sep)
+    for i, part in enumerate(parts):
+        curr_dir = os.path.join(curr_dir, part)
+        if not os.path.exists(curr_dir):
+            rest = os.path.join(os.path.sep, *parts[i + 1:])
+            user_parts = rest.partition(os.path.sep + getpass.getuser())
+            if len(user_parts[0]) > 0:
+                # Ensure access controls of subdirs created above `$user`
+                # inherit from the parent and share the group.
+                stats = os.stat(os.path.dirname(curr_dir))
+                curr_dir = ''.join([curr_dir, user_parts[0]])
+                mkdirp(curr_dir, group=stats.st_gid, mode=stats.st_mode)
+
+            user_subdirs = ''.join(user_parts[1:])
+            if len(user_subdirs) > 0:
+                # Ensure access controls of subdirs from `$user` on down are
+                # restricted to the user.
+                curr_dir = ''.join([curr_dir, user_subdirs])
+                mkdirp(curr_dir, mode=stat.S_IRWXU)
+
+            assert os.getuid() == os.stat(curr_dir).st_uid
+            break
+
+    if not can_access(path):
+        raise OSError(errno.EACCES,
+                      'Cannot access %s: Permission denied' % curr_dir)
+
+
 def _first_accessible_path(paths):
     """Find the first path that is accessible, creating it if necessary."""
     for path in paths:
@@ -45,20 +79,9 @@ def _first_accessible_path(paths):
                 if can_access(path):
                     return path
             else:
-                # The path doesn't exist so create it and adjust permissions
-                # and group as needed (e.g., shared ``$tempdir``).
-                prefix = os.path.sep
-                parts = path.strip(os.path.sep).split(os.path.sep)
-                for part in parts:
-                    prefix = os.path.join(prefix, part)
-                    if not os.path.exists(prefix):
-                        break
-                parent = os.path.dirname(prefix)
-                gid = os.stat(parent).st_gid
-                mkdirp(path, group=gid, default_perms='parents')
-
-                if can_access(path):
-                    return path
+                # Now create the stage root with the proper group/perms.
+                _create_stage_root(path)
+                return path
 
         except OSError as e:
             tty.debug('OSError while checking stage path %s: %s' % (
@@ -67,6 +90,23 @@ def _first_accessible_path(paths):
     return None
 
 
+def _resolve_paths(candidates):
+    """Resolve paths, removing extra $user from $tempdir if needed."""
+    temp_path = sup.canonicalize_path('$tempdir')
+    tmp_has_usr = getpass.getuser() in temp_path.split(os.path.sep)
+
+    paths = []
+    for path in candidates:
+        # First remove the extra `$user` node from a `$tempdir/$user` entry
+        # for hosts that automatically append `$user` to `$tempdir`.
+        if path.startswith(os.path.join('$tempdir', '$user')) and tmp_has_usr:
+            path = os.path.join('$tempdir', path[15:])
+
+        paths.append(sup.canonicalize_path(path))
+
+    return paths
+
+
 # Cached stage path root
 _stage_root = None
 
@@ -79,18 +119,19 @@ def get_stage_root():
         if isinstance(candidates, string_types):
             candidates = [candidates]
 
-        resolved_candidates = [sup.canonicalize_path(x) for x in candidates]
+        resolved_candidates = _resolve_paths(candidates)
         path = _first_accessible_path(resolved_candidates)
         if not path:
             raise StageError("No accessible stage paths in:",
                              ' '.join(resolved_candidates))
 
-        # Ensure that any temp path is unique per user, so users don't
-        # fight over shared temporary space.
+        # Ensure that path is unique per user in an attempt to avoid
+        # conflicts in shared temporary spaces.  Emulate permissions from
+        # `tempfile.mkdtemp`.
         user = getpass.getuser()
         if user not in path:
             path = os.path.join(path, user)
-            mkdirp(path)
+            mkdirp(path, mode=stat.S_IRWXU)
 
         _stage_root = path
 
@@ -636,9 +677,13 @@ def purge():
     """Remove all build directories in the top-level stage path."""
     root = get_stage_root()
     if os.path.isdir(root):
+        # TODO: Figure out a "standard" way to identify the hash length
+        # TODO: Should this support alternate base represenations?
+        dir_expr = re.compile(r'.*-[0-9a-f]{32}$')
         for stage_dir in os.listdir(root):
-            stage_path = os.path.join(root, stage_dir)
-            remove_linked_tree(stage_path)
+            if re.match(dir_expr, stage_dir) or stage_dir == '.lock':
+                stage_path = os.path.join(root, stage_dir)
+                remove_linked_tree(stage_path)
 
 
 class StageError(spack.error.SpackError):


Test patch:
diff --git a/lib/spack/spack/test/data/config.yaml b/lib/spack/spack/test/data/config.yaml
--- a/lib/spack/spack/test/data/config.yaml
+++ b/lib/spack/spack/test/data/config.yaml
@@ -5,9 +5,8 @@ config:
   - $spack/lib/spack/spack/test/data/templates
   - $spack/lib/spack/spack/test/data/templates_again
   build_stage:
-  - $tempdir/spack-stage-test
-  - /nfs/tmp2/$user
-  - $spack/var/spack/stage
+  - $tempdir/$user/spack-stage
+  - ~/.spack/stage
   source_cache: $spack/var/spack/cache
   misc_cache: ~/.spack/cache
   verify_ssl: true
diff --git a/lib/spack/spack/test/stage.py b/lib/spack/spack/test/stage.py
--- a/lib/spack/spack/test/stage.py
+++ b/lib/spack/spack/test/stage.py
@@ -4,10 +4,10 @@
 # SPDX-License-Identifier: (Apache-2.0 OR MIT)
 
 """Test that the Stage class works correctly."""
-import grp
 import os
 import collections
 import shutil
+import stat
 import tempfile
 import getpass
 
@@ -406,6 +406,30 @@ def __call__(self):
     return _Mock()
 
 
+def check_stage_dir_perms(prefix, path):
+    """Check the stage directory perms to ensure match expectations."""
+    # Ensure the path's subdirectories -- to `$user` -- have their parent's
+    # perms while those from `$user` on are owned and restricted to the
+    # user.
+    status = os.stat(prefix)
+    gid = status.st_gid
+    uid = os.getuid()
+    user = getpass.getuser()
+    parts = path[len(prefix) + 1:].split(os.path.sep)
+    have_user = False
+    for part in parts:
+        if part == user:
+            have_user = True
+        prefix = os.path.join(prefix, part)
+        prefix_status = os.stat(prefix)
+        if not have_user:
+            assert gid == prefix_status.st_gid
+            assert status.st_mode == prefix_status.st_mode
+        else:
+            assert uid == status.st_uid
+            assert status.st_mode & stat.S_IRWXU == stat.S_IRWXU
+
+
 @pytest.mark.usefixtures('mock_packages')
 class TestStage(object):
 
@@ -684,6 +708,7 @@ def test_first_accessible_path(self, tmpdir):
         path = spack.stage._first_accessible_path(files)
         assert path == name
         assert os.path.isdir(path)
+        check_stage_dir_perms(str(tmpdir), path)
 
         # Ensure an existing path is returned
         spack_subdir = spack_dir.join('existing').ensure(dir=True)
@@ -691,31 +716,33 @@ def test_first_accessible_path(self, tmpdir):
         path = spack.stage._first_accessible_path([subdir])
         assert path == subdir
 
+        # Ensure a path with a `$user` node has the right permissions
+        # for its subdirectories.
+        user = getpass.getuser()
+        user_dir = spack_dir.join(user, 'has', 'paths')
+        user_path = str(user_dir)
+        path = spack.stage._first_accessible_path([user_path])
+        assert path == user_path
+        check_stage_dir_perms(str(tmpdir), path)
+
         # Cleanup
         shutil.rmtree(str(name))
 
-    def test_first_accessible_perms(self, tmpdir):
-        """Test _first_accessible_path permissions."""
-        name = str(tmpdir.join('first', 'path'))
-        path = spack.stage._first_accessible_path([name])
+    def test_resolve_paths(self):
+        """Test _resolve_paths."""
 
-        # Ensure the non-existent path was created
-        assert path == name
-        assert os.path.isdir(path)
+        assert spack.stage._resolve_paths([]) == []
 
-        # Ensure the non-existent subdirectories have their parent's perms
-        prefix = str(tmpdir)
-        status = os.stat(prefix)
-        group = grp.getgrgid(status.st_gid)[0]
-        parts = path[len(prefix):].split(os.path.sep)
-        for part in parts:
-            prefix = os.path.join(prefix, part)
-            prefix_status = os.stat(prefix)
-            assert group == grp.getgrgid(os.stat(prefix).st_gid)[0]
-            assert status.st_mode == prefix_status.st_mode
+        paths = [os.path.join(os.path.sep, 'a', 'b', 'c')]
+        assert spack.stage._resolve_paths(paths) == paths
 
-        # Cleanup
-        shutil.rmtree(os.path.dirname(name))
+        tmp = '$tempdir'
+        paths = [os.path.join(tmp, 'stage'), os.path.join(tmp, '$user')]
+        can_paths = [canonicalize_path(paths[0]), canonicalize_path(tmp)]
+        user = getpass.getuser()
+        if user not in can_paths[1].split(os.path.sep):
+            can_paths[1] = os.path.join(can_paths[1], user)
+        assert spack.stage._resolve_paths(paths) == can_paths
 
     def test_get_stage_root_bad_path(self, clear_stage_root, bad_stage_path):
         """Ensure an invalid stage path root raises a StageError."""
@@ -756,15 +783,21 @@ def test_get_stage_root_tmp(self, clear_stage_root, tmp_path_for_stage):
         # Make sure the cached stage path values are changed appropriately.
         assert spack.stage._stage_root == path
 
-        # Add then purge a couple of directories
-        dir1 = tmpdir.join('dir1')
+        # Add then purge a few directories
+        dir1 = tmpdir.join('stage-1234567890abcdef1234567890abcdef')
         dir1.ensure(dir=True)
-        dir2 = tmpdir.join('dir2')
+        dir2 = tmpdir.join('stage-abcdef12345678900987654321fedcba')
         dir2.ensure(dir=True)
+        dir3 = tmpdir.join('stage-a1b2c3')
+        dir3.ensure(dir=True)
 
         spack.stage.purge()
         assert not os.path.exists(str(dir1))
         assert not os.path.exists(str(dir2))
+        assert os.path.exists(str(dir3))
+
+        # Cleanup
+        shutil.rmtree(str(dir3))
 
     def test_get_stage_root_in_spack(self, clear_stage_root,
                                      instance_path_for_stage):