fix(codex): replace pwd.getpwall() with directory scan for macOS
macOS Directory Services makes pwd.getpwall() unreliable — regular users may not be returned. Now scans /Users/* directly on macOS, /home/* on Linux. Also fixes chown to use actual directory ownership. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -659,24 +659,57 @@ def rollback(home_dir=None):
|
||||
|
||||
# ─── Multi-User Support ─────────────────────────────────────────────────
|
||||
|
||||
def list_users():
|
||||
"""List system users with .codex/ or home dirs."""
|
||||
users = []
|
||||
try:
|
||||
import pwd
|
||||
for pw in pwd.getpwall():
|
||||
home = pw.pw_dir
|
||||
if not os.path.isdir(home):
|
||||
def list_user_homes():
|
||||
"""List home directories of real users. Returns list of (home_dir, uid, gid)."""
|
||||
homes = []
|
||||
|
||||
if IS_MACOS:
|
||||
# macOS: pwd.getpwall() is unreliable (Directory Services).
|
||||
# Scan /Users/ directly + /var/root for root.
|
||||
import stat as st
|
||||
for base in ["/Users", "/var/root"]:
|
||||
if base == "/var/root":
|
||||
if os.path.isdir(base):
|
||||
info = os.stat(base)
|
||||
homes.append((base, info.st_uid, info.st_gid))
|
||||
continue
|
||||
min_uid = 500 if IS_MACOS else 1000
|
||||
if pw.pw_uid < min_uid and pw.pw_uid != 0:
|
||||
if not os.path.isdir(base):
|
||||
continue
|
||||
if pw.pw_shell in ("/usr/sbin/nologin", "/bin/false"):
|
||||
continue
|
||||
users.append(pw)
|
||||
except ImportError:
|
||||
pass
|
||||
return users
|
||||
for name in os.listdir(base):
|
||||
if name.startswith(".") or name == "Shared":
|
||||
continue
|
||||
udir = os.path.join(base, name)
|
||||
if os.path.isdir(udir):
|
||||
info = os.stat(udir)
|
||||
homes.append((udir, info.st_uid, info.st_gid))
|
||||
else:
|
||||
# Linux: use pwd
|
||||
try:
|
||||
import pwd
|
||||
for pw in pwd.getpwall():
|
||||
if not os.path.isdir(pw.pw_dir):
|
||||
continue
|
||||
if pw.pw_uid < 1000 and pw.pw_uid != 0:
|
||||
continue
|
||||
if pw.pw_shell in ("/usr/sbin/nologin", "/bin/false"):
|
||||
continue
|
||||
homes.append((pw.pw_dir, pw.pw_uid, pw.pw_gid))
|
||||
except ImportError:
|
||||
# Fallback: scan /home + /root
|
||||
for base in ["/home", "/root"]:
|
||||
if base == "/root":
|
||||
if os.path.isdir(base):
|
||||
homes.append((base, 0, 0))
|
||||
continue
|
||||
if not os.path.isdir(base):
|
||||
continue
|
||||
for name in os.listdir(base):
|
||||
udir = os.path.join(base, name)
|
||||
if os.path.isdir(udir):
|
||||
info = os.stat(udir)
|
||||
homes.append((udir, info.st_uid, info.st_gid))
|
||||
|
||||
return homes
|
||||
|
||||
|
||||
def patch_user(user_home, config, uid=None, gid=None):
|
||||
@@ -748,14 +781,15 @@ def main():
|
||||
|
||||
# Patch other users if --all
|
||||
if args.all:
|
||||
for user in list_users():
|
||||
if user.pw_dir == os.path.expanduser("~"):
|
||||
my_home = os.path.expanduser("~")
|
||||
for home_dir, uid, gid in list_user_homes():
|
||||
if home_dir == my_home:
|
||||
continue
|
||||
try:
|
||||
patch_user(user.pw_dir, config, uid=user.pw_uid, gid=user.pw_gid)
|
||||
print(f" Patched {user.pw_name}: {user.pw_dir}/.codex/config.toml")
|
||||
patch_user(home_dir, config, uid=uid, gid=gid)
|
||||
print(f" Patched: {home_dir}/.codex/config.toml (uid={uid})")
|
||||
except Exception as e:
|
||||
print(f" {RED}Failed {user.pw_name}: {e}{RESET}")
|
||||
print(f" {RED}Failed {home_dir}: {e}{RESET}")
|
||||
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
Reference in New Issue
Block a user