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 ─────────────────────────────────────────────────
|
# ─── Multi-User Support ─────────────────────────────────────────────────
|
||||||
|
|
||||||
def list_users():
|
def list_user_homes():
|
||||||
"""List system users with .codex/ or home dirs."""
|
"""List home directories of real users. Returns list of (home_dir, uid, gid)."""
|
||||||
users = []
|
homes = []
|
||||||
try:
|
|
||||||
import pwd
|
if IS_MACOS:
|
||||||
for pw in pwd.getpwall():
|
# macOS: pwd.getpwall() is unreliable (Directory Services).
|
||||||
home = pw.pw_dir
|
# Scan /Users/ directly + /var/root for root.
|
||||||
if not os.path.isdir(home):
|
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
|
continue
|
||||||
min_uid = 500 if IS_MACOS else 1000
|
if not os.path.isdir(base):
|
||||||
if pw.pw_uid < min_uid and pw.pw_uid != 0:
|
|
||||||
continue
|
continue
|
||||||
if pw.pw_shell in ("/usr/sbin/nologin", "/bin/false"):
|
for name in os.listdir(base):
|
||||||
continue
|
if name.startswith(".") or name == "Shared":
|
||||||
users.append(pw)
|
continue
|
||||||
except ImportError:
|
udir = os.path.join(base, name)
|
||||||
pass
|
if os.path.isdir(udir):
|
||||||
return users
|
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):
|
def patch_user(user_home, config, uid=None, gid=None):
|
||||||
@@ -748,14 +781,15 @@ def main():
|
|||||||
|
|
||||||
# Patch other users if --all
|
# Patch other users if --all
|
||||||
if args.all:
|
if args.all:
|
||||||
for user in list_users():
|
my_home = os.path.expanduser("~")
|
||||||
if user.pw_dir == os.path.expanduser("~"):
|
for home_dir, uid, gid in list_user_homes():
|
||||||
|
if home_dir == my_home:
|
||||||
continue
|
continue
|
||||||
try:
|
try:
|
||||||
patch_user(user.pw_dir, config, uid=user.pw_uid, gid=user.pw_gid)
|
patch_user(home_dir, config, uid=uid, gid=gid)
|
||||||
print(f" Patched {user.pw_name}: {user.pw_dir}/.codex/config.toml")
|
print(f" Patched: {home_dir}/.codex/config.toml (uid={uid})")
|
||||||
except Exception as e:
|
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
|
return 0 if ok else 1
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user