Hi, I'm modifying util-linux's login(1) to work with utmps. Specifically to update the database on logout, directly without relying on s6-linux-init-logouthookd.
I'm piggybacking on the existing login.c code that updates the entry possibly previously created by getty(8) or telnetd or whatever. This code assumes the position of the database cursor after a call to getutxent(3), getutxid(3), or getutxline(3) is pointing at the found/read entry that was returned, not at the next entry after it. So it calls pututxline(3) immediately to update the found entry, without rewinding first with setutxent(3). With utmps this causes a new entry to be appended to the database instead of the "current" entry to be overwritten. The POSIX specification [1] is rather ambiguous, using such wording as "shall search forward from the current point in the database", "shall read the next entry" and "shall stop when it finds an entry", without actually defining where is "the current point in the database". What's your take on this? My interpretation of the POSIX doc is that pututxline(3) should start searching from the last entry found/read, not from the next entry after it. In that regard, it would seem that utmps is not behaving correctly. But at the same time I have no other point of reference and it's always dangerous to rely on ambiguous definitions - in that particular case util-linux may just be relying on glibc's specific behavior. Attached is a trivial test to illustrate the issue. Expected result is one DEAD_PROCESS entry. Actual result is one USER_PROCESS and one DEAD_PROCESS entry in the database. Thanks. [1] <https://pubs.opengroup.org/onlinepubs/9699919799/functions/endutxent.html>
#include <stdio.h> #include <string.h> #include <unistd.h> #include <utmpx.h> int main(void) { struct utmpx ut; struct utmpx * utp; const char * user = "foobar"; const char * line = "offline"; memset(&ut, 0, sizeof(ut)); memcpy(&ut.ut_user[0], user, strlen(user)); memcpy(&ut.ut_id[0], "1234", 4); memcpy(&ut.ut_line[0], line, strlen(line)); ut.ut_pid = getpid(); ut.ut_type = USER_PROCESS; setutxent(); /* Create entry */ if (pututxline(&ut) == NULL) { puts("putuxtline failed. are you superuser?"); return 1; } setutxent(); utp = getutxent(); while ((utp != NULL) && (utp->ut_pid != ut.ut_pid)) utp = getutxent(); if (utp == NULL) { puts("entry not found"); return 1; } utp->ut_type = DEAD_PROCESS; /* Should overwrite existing? */ pututxline(utp); setutxent(); utp = getutxid(&ut); if (utp == NULL) { puts("entry not found"); return 1; } utp->ut_type = DEAD_PROCESS; /* Should overwrite existing? */ pututxline(utp); setutxent(); utp = getutxline(&ut); if (utp == NULL) { puts("entry not found"); return 1; } utp->ut_type = DEAD_PROCESS; /* Should overwrite existing? */ pututxline(utp); return 0; }