Imagine a user logs in as ravi, then uses su to switch to root. Now, your program is running as root (UID 0). If you call getuid() and look it up in /etc/passwd, you’ll get “root” โ but the actual person sitting at the keyboard is ravi.
The getlogin() function solves this. It checks the utmp file to find who originally logged in on this terminal โ not who the process is running as.
#include <unistd.h>
char *getlogin(void);
/* Returns: pointer to login name string on success,
* NULL on error (errno set) */
/* Reentrant version (SUSv3 specified) */
int getlogin_r(char *buf, size_t bufsize);
/* Returns: 0 on success, positive error number on error */
The non-reentrant version returns a pointer to a static internal buffer. The reentrant version writes into your provided buf of bufsize bytes โ use this in multi-threaded programs.
The function performs these steps:
ttyname(STDIN_FILENO) to get the name of the terminal associated with standard input (e.g., /dev/pts/0)/dev/ prefix from the terminal name to get just pts/0 (the ut_line value)ut_line matches this terminal nameut_user field from the matching record โ this is the original login name| Situation | errno | Why |
|---|---|---|
| Process is a daemon (no terminal) | ENOTTY | ttyname() fails because stdin is not a terminal |
| Terminal emulator doesn’t update utmp | varies | Some terminal emulators (xterm with certain options) skip writing utmp records |
| Running in a container/VM | varies | Containers may not maintain utmp properly |
| utmp file is corrupted or deleted | ENOENT | File not found when trying to open it |
| Method | Returns | Problem | Secure? |
|---|---|---|---|
| getlogin() | Original login name from utmp | Fails for daemons; may fail if utmp not updated | โ Yes |
| getpwuid(getuid()) | First name in /etc/passwd for this UID | Multiple login names with same UID โ returns first one only. After su, returns “root”, not the original user. |
โ Partial |
| getenv(“LOGNAME”) | Login name from environment variable | User can change this variable: export LOGNAME=root. Never trust it for security decisions. |
โ No |
Best practice: Use getlogin() when you need to identify who logged in. Fall back to getpwuid(getuid()) if getlogin() fails. Never rely on LOGNAME for security.
Shows getlogin() in action, with fallback to getpwuid().
/* show_login.c
* Demonstrates getlogin() and compares with getpwuid()
* Compile: gcc show_login.c -o show_login
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h> /* getlogin() */
#include <pwd.h> /* getpwuid() */
#include <errno.h>
#include <string.h>
int main(void)
{
char *login_name;
struct passwd *pw;
uid_t uid;
/* --- Method 1: getlogin() --- */
login_name = getlogin();
if (login_name != NULL) {
printf("getlogin() : %s\n", login_name);
} else {
printf("getlogin() : FAILED (%s)\n", strerror(errno));
printf(" This can happen if:\n");
printf(" - Process has no controlling terminal (daemon)\n");
printf(" - Terminal emulator did not update utmp\n");
}
/* --- Method 2: getpwuid(getuid()) --- */
uid = getuid();
pw = getpwuid(uid);
if (pw != NULL) {
printf("getpwuid(getuid()) : %s (UID=%d)\n", pw->pw_name, uid);
} else {
printf("getpwuid(getuid()) : FAILED\n");
}
/* --- Method 3: LOGNAME env var (NOT secure) --- */
char *logname_env = getenv("LOGNAME");
printf("LOGNAME env var : %s (WARNING: user can fake this!)\n",
logname_env ? logname_env : "(not set)");
/* Practical: use getlogin with fallback */
printf("\n--- Recommended approach ---\n");
login_name = getlogin();
if (login_name == NULL) {
/* Fallback to passwd entry */
pw = getpwuid(getuid());
login_name = (pw != NULL) ? pw->pw_name : "unknown";
printf("Identified user (fallback): %s\n", login_name);
} else {
printf("Identified user (getlogin): %s\n", login_name);
}
return 0;
}
In multi-threaded programs, use getlogin_r() to avoid the shared static buffer problem.
/* getlogin_threadsafe.c
* Thread-safe login name retrieval using getlogin_r()
* Compile: gcc getlogin_threadsafe.c -o getlogin_threadsafe -lpthread
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
#include <string.h>
#include <errno.h>
#define LOGIN_BUF_SIZE 256
void *worker_thread(void *arg)
{
int thread_id = *(int *)arg;
char login_buf[LOGIN_BUF_SIZE]; /* Each thread has its own buffer */
int ret;
/* getlogin_r() is the thread-safe version */
ret = getlogin_r(login_buf, sizeof(login_buf));
if (ret == 0) {
printf("Thread %d: login name = %s\n", thread_id, login_buf);
} else {
printf("Thread %d: getlogin_r() failed: %s\n",
thread_id, strerror(ret));
}
return NULL;
}
int main(void)
{
pthread_t tid[3];
int ids[3] = {1, 2, 3};
int i;
printf("Spawning 3 threads to call getlogin_r() safely...\n\n");
for (i = 0; i < 3; i++)
pthread_create(&tid[i], NULL, worker_thread, &ids[i]);
for (i = 0; i < 3; i++)
pthread_join(tid[i], NULL);
printf("\nAll threads used separate buffers โ no race conditions.\n");
return 0;
}
This exercise from the TLPI book (Exercise 40-1) shows exactly how getlogin() works by implementing it using ttyname() and utmpx functions.
/* my_getlogin.c
* Manual implementation of getlogin() using utmpx
* This is Exercise 40-1 from TLPI
* Compile: gcc my_getlogin.c -o my_getlogin
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <utmpx.h>
#include <errno.h>
static char login_buf[32]; /* Static buffer for return value */
/*
* my_getlogin() - reimplementation of getlogin()
*
* How it works:
* 1. Find the terminal name for stdin using ttyname()
* 2. Strip the "/dev/" prefix to get ut_line format
* 3. Scan utmp for a USER_PROCESS or LOGIN_PROCESS record
* with matching ut_line
* 4. Return ut_user from that record
*/
char *my_getlogin(void)
{
struct utmpx search_ut, *result;
char *tty;
/* Step 1: Get the terminal device name */
tty = ttyname(STDIN_FILENO);
if (tty == NULL) {
errno = ENOTTY; /* stdin is not a terminal */
return NULL;
}
/* Step 2: Strip "/dev/" prefix (5 characters) */
if (strncmp(tty, "/dev/", 5) == 0)
tty += 5;
/* Step 3: Set up search criteria */
memset(&search_ut, 0, sizeof(struct utmpx));
strncpy(search_ut.ut_line, tty, sizeof(search_ut.ut_line) - 1);
search_ut.ut_type = USER_PROCESS;
setutxent();
result = getutxline(&search_ut);
endutxent();
if (result == NULL) {
errno = ENOENT; /* No matching record in utmp */
return NULL;
}
/* Step 4: Copy username to static buffer */
strncpy(login_buf, result->ut_user, sizeof(login_buf) - 1);
login_buf[sizeof(login_buf) - 1] = '\0';
return login_buf;
}
int main(void)
{
char *name;
/* Test our implementation */
name = my_getlogin();
if (name != NULL) {
printf("my_getlogin() : %s\n", name);
} else {
perror("my_getlogin()");
}
/* Compare with system getlogin() */
name = getlogin();
if (name != NULL) {
printf("system getlogin: %s\n", name);
} else {
perror("system getlogin");
}
return 0;
}
Answer: It calls ttyname() to find the terminal associated with stdin, strips the “/dev/” prefix, then searches the utmp file for a USER_PROCESS or LOGIN_PROCESS record whose ut_line matches. If found, it returns the ut_user field.
Answer: After su or setuid(), getuid() returns the new UID (e.g., 0 for root), so getpwuid(getuid()) returns “root”, not the original user. getlogin() reads from utmp which records who originally logged in on this terminal, regardless of UID changes. Also, if multiple login names share the same UID, getpwuid() always returns the first one in /etc/passwd, while getlogin() returns the actual name used to log in.
Answer: No. Any user can set LOGNAME to anything with export LOGNAME=root. It’s convenient for informational purposes but must never be used for authentication or authorization decisions. Use getlogin() or UID-based methods for secure identity checks.
Answer: Daemon processes detach from their controlling terminal (usually by calling setsid()). When getlogin() internally calls ttyname(STDIN_FILENO), it fails with ENOTTY because stdin is typically redirected to /dev/null in daemons โ not a real terminal.
Answer: getlogin() returns a pointer to an internal static buffer shared across the process. getlogin_r() writes the login name into a caller-provided buffer, making it reentrant and safe for use in multi-threaded programs. getlogin_r() returns 0 on success or an error number on failure (not -1 with errno).
