|
11 | 11 |
|
12 | 12 | import platformdirs |
13 | 13 |
|
| 14 | +try: |
| 15 | + import pwd # POSIX only |
| 16 | +except ImportError: |
| 17 | + pwd = None # win32? |
| 18 | + |
14 | 19 | from .errors import Error |
15 | 20 |
|
16 | 21 | from .process import prepare_subprocess_env |
@@ -48,24 +53,67 @@ def ensure_dir(path, mode=stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO, pretty_dea |
48 | 53 | def get_base_dir(*, legacy=False): |
49 | 54 | """Get home directory / base directory for Borg: |
50 | 55 |
|
51 | | - - BORG_BASE_DIR, if set |
52 | | - - HOME, if set |
53 | | - - ~$USER, if USER is set |
54 | | - - ~ |
| 56 | + Legacy: |
| 57 | +
|
| 58 | + Preference order (while being robust against misleading environment when invoked via mount helpers): |
| 59 | +
|
| 60 | + - BORG_BASE_DIR, if set. |
| 61 | + - HOME, if it refers to the current (effective) user's home. |
| 62 | + - ~$USER, if USER is set. |
| 63 | + - The home directory of the current (effective) user from the password database (POSIX). |
| 64 | + - ~ (platform default expansion). |
| 65 | +
|
| 66 | + Not legacy: |
| 67 | +
|
| 68 | + Just return BORG_BASE_DIR. |
55 | 69 | """ |
56 | 70 | if legacy: |
57 | | - base_dir = os.environ.get("BORG_BASE_DIR") or os.environ.get("HOME") |
58 | | - # Path.expanduser() behaves differently for '~' and '~someuser' as |
59 | | - # parameters: when called with an explicit username, the possibly set |
60 | | - # environment variable HOME is no longer respected. So we have to check if |
61 | | - # it is set and only expand the user's home directory if HOME is unset. |
62 | | - if not base_dir: |
63 | | - base_dir = str(Path(f"~{os.environ.get('USER', '')}").expanduser()) |
| 71 | + # 1. Explicit override always wins. |
| 72 | + base_dir = os.environ.get("BORG_BASE_DIR") |
| 73 | + if base_dir: |
| 74 | + return base_dir |
| 75 | + |
| 76 | + # 2. Prefer HOME, but be robust against mount helpers that set HOME to root's home for non-root users. |
| 77 | + home_env = os.environ.get("HOME") |
| 78 | + if home_env and pwd is not None: # POSIX only |
| 79 | + try: |
| 80 | + # If HOME points to root's home but we are not root, prefer the invoking user's home. |
| 81 | + root_home = pwd.getpwuid(0).pw_dir |
| 82 | + uid = getattr(os, "geteuid", os.getuid)() |
| 83 | + if uid != 0 and os.path.abspath(home_env) == os.path.abspath(root_home): |
| 84 | + try: |
| 85 | + user_home = pwd.getpwuid(uid).pw_dir |
| 86 | + except Exception: |
| 87 | + user_home = None |
| 88 | + if user_home: |
| 89 | + return user_home |
| 90 | + # if we couldn't figure out the user's home, ignore HOME and continue with fallbacks |
| 91 | + home_env = None |
| 92 | + except Exception: # nosec B110 |
| 93 | + # If anything goes wrong determining root's home, keep HOME as-is. |
| 94 | + pass |
| 95 | + |
| 96 | + if home_env: |
| 97 | + return home_env |
| 98 | + |
| 99 | + # 3. Fall back to ~$USER if set (keeps previous behavior and existing tests). |
| 100 | + user = os.environ.get("USER") |
| 101 | + if user: |
| 102 | + return os.path.expanduser("~%s" % user) |
| 103 | + |
| 104 | + # 4. POSIX: use pw_home for the current uid; otherwise finally fallback to ~. |
| 105 | + if pwd is not None: |
| 106 | + try: |
| 107 | + uid = getattr(os, "geteuid", os.getuid)() |
| 108 | + return pwd.getpwuid(uid).pw_dir |
| 109 | + except Exception: # nosec B110 |
| 110 | + pass |
| 111 | + |
| 112 | + return os.path.expanduser("~") |
64 | 113 | else: |
65 | 114 | # we only care for BORG_BASE_DIR here, as it can be used to override the base dir |
66 | | - # and not use any more or less platform specific way to determine the base dir. |
67 | | - base_dir = os.environ.get("BORG_BASE_DIR") |
68 | | - return base_dir |
| 115 | + # and not use any more or less platform-specific way to determine the base dir. |
| 116 | + return os.environ.get("BORG_BASE_DIR") |
69 | 117 |
|
70 | 118 |
|
71 | 119 | def join_base_dir(*paths, **kw): |
|
0 commit comments