os.getuid

The real uid is who started the process; the effective uid decides permissions (it differs in setuid programs); the saved uid lets a program switch back. All of these are Unix only. os.getlogin() also exists on Windows, but getpass.getuser() is usually the better way to get a user name.

os functionPython 3.0+ (getresuid/setresuid/initgroups 3.2+, getgrouplist 3.3+)
Common call
os.geteuid() == 0
Returns
True when running with root privileges
Replaces
parsing the output of the id command
Watch out
Unix only: guard with hasattr or os.name on cross-platform code
os.getuid() / os.geteuid() / os.getgid() / os.getegid() / os.getgroups() / os.setuid(uid, /)
→ int

Parameters

NameTypeRequiredDescription
uid / gidintyessetuid / seteuid / setgid / setegid: the new id.
ruid, euid (, suid)intyessetreuid / setresuid (and the gid twins): real, effective (and saved) ids; -1 leaves one unchanged.
groupssequence of intyessetgroups only: the new supplementary group list (normally needs root).
user, groupstr, intyesgetgrouplist(user, group) / initgroups(username, gid): a user name and its primary group id.

Return value

int — get*id: a numeric id; getgroups / getgrouplist: list of ints; getresuid / getresgid: (real, effective, saved); getlogin: str. The set* calls return None.

Common patterns

Am I root? (Unix)
Permissions follow the effective uid.
import os
is_root = hasattr(os, 'geteuid') and os.geteuid() == 0
Drop root privileges for good
Groups first, then gid, then uid last: once the uid is gone you can no longer change groups.
import os, pwd
user = pwd.getpwnam('www-data')
os.setgroups([])
os.setgid(user.pw_gid)
os.setuid(user.pw_uid)
User name, portably
getpass checks LOGNAME, USER, LNAME and USERNAME, then the password database.
import getpass
name = getpass.getuser()
Run a child as another user
subprocess switches the child to that user and group before running it (Unix, needs privileges).
import subprocess
subprocess.run(['id'], user='nobody', group='nogroup', extra_groups=[])

Examples

1. Unix only
import os all(hasattr(os, n) == (os.name == 'posix') for n in ('getuid', 'geteuid', 'getgid', 'getegid', 'getgroups', 'setuid'))
Returns
True
2. getlogin exists on Unix and Windows
import os hasattr(os, 'getlogin')
Returns
True
3. getpass.getuser() returns a str
import getpass isinstance(getpass.getuser(), str)
Returns
True
4. Root is uid 0: the effective id decides
ruid, euid = 1000, 0 # a setuid-root program started by user 1000 (ruid == 0, euid == 0)
Returns
(False, True)
5. EPERM: what an unprivileged set* call fails with
import errno (errno.EPERM, errno.errorcode[errno.EPERM])
Returns
(1, 'EPERM')

Pitfalls

1. Dropping the uid before the gid
Changing the gid needs privileges. After setuid(1000) the process is no longer root, so the following setgid fails and the process keeps the root group. This model applies the kernel rule; on Linux the real call raises the same PermissionError.
setuid, then setgid
ids = {'uid': 0, 'gid': 0}
def setuid(uid):
    ids['uid'] = uid
def setgid(gid):
    if ids['uid'] != 0:
        raise PermissionError('[Errno 1] Operation not permitted')
    ids['gid'] = gid
setuid(1000)
setgid(1000)
PermissionError: [Errno 1] Operation not permitted
setgid, then setuid
ids = {'uid': 0, 'gid': 0}
def setuid(uid):
    ids['uid'] = uid
def setgid(gid):
    if ids['uid'] != 0:
        raise PermissionError('[Errno 1] Operation not permitted')
    ids['gid'] = gid
setgid(1000)
setuid(1000)
ids
{'uid': 1000, 'gid': 1000}
2. Checking the real uid for root
A setuid-root program has real uid = the caller and effective uid = 0. File permissions use the effective id, so test geteuid().
getuid() == 0
ruid, euid = 1000, 0
ruid == 0
False
geteuid() == 0
ruid, euid = 1000, 0
euid == 0
True
3. Calling os.getuid() on Windows
It does not exist there. Guard the call, or use getpass.getuser() when all you need is a name.
unguarded
import os
os.name == 'posix' or not hasattr(os, 'getuid')  # on Windows os.getuid() raises AttributeError
True
guarded
import os
uid = os.getuid() if hasattr(os, 'getuid') else None
uid is None or isinstance(uid, int)
True

When to use

Use it
  • Refusing to run as root, or requiring root
  • Servers that start as root to bind a port, then drop to an unprivileged user
  • Checking group membership with getgroups()
Reach for something else
  • Getting the user name → getpass.getuser() (works on Windows too)
  • Mapping ids to names → pwd.getpwuid(uid).pw_name / grp.getgrgid(gid).gr_name
  • Running a child as another user → subprocess user= / group= / extra_groups=

Notes

CPython impl
Direct wrappers of the getuid(2) / setuid(2) family; errors become OSError subclasses (PermissionError for EPERM)
Availability
Unix only for everything except getlogin (Unix, Windows). getresuid / getresgid / setresuid / setresgid: not macOS, not iOS. setuid / seteuid / setgid / setegid / setreuid / setregid / initgroups: not Android
getlogin
Returns the user logged in on the controlling terminal; on Linux it raises OSError when there is none (cron, services, our WSL check: errno 25). getpass.getuser() reads LOGNAME / USER / LNAME / USERNAME, then falls back to the password database
NGROUPS_MAX
Maximum number of supplementary groups (65536 on our Linux test system, the same as sysconf("SC_NGROUPS_MAX")); not defined on Windows
macOS getgroups
May return the group access list of the effective user rather than the list set with setgroups(), and is not limited to 16 entries on modern builds

FAQ

These functions are Unix only (docs: Availability: Unix), the ids differ per machine, and the set* calls change the identity of the whole process. The examples show availability, the id arithmetic and a model of the drop-privileges order instead.