`getlogin()` 是通过读取`/var/run/utmp` 实现的吗?

Is `getlogin()` implemented by reading from `/var/run/utmp`?

来自https://unix.stackexchange.com/a/268388/674

logname goes up the user that owns the tty (by reading it from /var/run/utmp)

the source code of coreutils中,我发现logname.c是基于LinuxAPI函数getlogin:

实现的
#include <unistd.h>
char *getlogin(void);

我在 logname.c 中找不到 /var/run/tmp

getlogin()是从/var/run/utmp读取实现的吗?

谢谢。


在我的 Lubuntu 18.04 上,strace logname 输出:

openat(AT_FDCWD, "/var/run/utmp", O_RDONLY|O_CLOEXEC) = 3

glibc code is a bit of a maze. But the easy way to determine what any program does at the userspace-kernel interface, which includes any reading of files, is the strace 实用程序。

刚才我运行strace logname时,包含输出:

open("/var/run/utmp", O_RDONLY|O_CLOEXEC) = 3
lseek(3, 0, SEEK_SET)                   = 0

...

read(3, "[=11=][=11=][=11=][=11=][=11=][=11=][=11=]~[=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=]"..., 384) = 384
read(3, "[=11=][=11=][=11=]05[=11=][=11=][=11=]~[=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=]"..., 384) = 384
read(3, "[=11=][=11=][=11=]2[=11=][=11=]tty1[=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=]"..., 384) = 384
read(3, "[=11=][=11=][=11=]0[=11=][=11=]tty7[=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=][=11=]"..., 384) = 384
read(3, "", 384)                        = 0

...

close(3)                                = 0

所以是的,/usr/bin/logname 从文件 /var/run/utmp 中读取。

getlogin 不是 Linux API 函数而是 libc 函数,在 GNU/Linux 和 glibc 上它是 implemented here:

   /* Try to determine login name from /proc/self/loginuid and return 0
      if successful.  If /proc/self/loginuid cannot be read return -1.
      Otherwise return the error number.  */

   int
   attribute_hidden
   __getlogin_r_loginuid (char *name, size_t namesize)
   {
     int fd = __open_nocancel ("/proc/self/loginuid", O_RDONLY);
  [...]

strace也可以看出这一点:

openat(AT_FDCWD, "/proc/self/loginuid", O_RDONLY) = 3
read(3, "1000", 12)                     = 4
close(3)                                = 0

所以不,/usr/bin/logname 没有从文件 /var/run/utmp 中读取。

找出 logname 是否查看 /var/run/utmp 的最简单方法是 运行 它位于 strace 下,如下所示:

$ strace -e trace=open,openat logname 2>&1 | grep -Ev '\.so\.[0-9]+", O_RD'

-e trace=open,openat 部分使它只打印对 open(和 openat,glibc 非常喜欢在内部使用)的调用,而 grep 过滤掉共享库。这减少了输出,以至于我实际上可以连贯地解释它:

openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/proc/self/loginuid", O_RDONLY) = 3
openat(AT_FDCWD, "/etc/nsswitch.conf", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
openat(AT_FDCWD, "/etc/passwd", O_RDONLY|O_CLOEXEC) = 3
zack
+++ exited with 0 +++

所以,在我的系统上,它打开的文件是 /etc/ld.so.cache/usr/lib/locale/locale-archive,这两个文件都包含与 getlogin 无关的通用数据; /proc/self/loginuid,其中似乎包含我的用户 ID,我不知道记录在哪里; /etc/nsswitch.conf,它告诉 C 库在哪里查找用户 ID 到名称的映射;和 /etc/passwd,其中(在我的系统上)包含该映射。它没有在任何时候查看/var/run/utmp

(例如,如果您使用 su 模拟另一个用户,/proc/self/loginuid 中的值将不同于 getuid 返回的值;su root -c logname 仍然打印"zack" 对我来说。)

但是,如果我让 logname 无法查看 /proc/self/loginuid(通过在 /proc 上临时绑定挂载一个空目录),那么我会得到一些不同的东西。 (我在这里有点作弊:我 运行 strace 没有任何 -e 选项,以便找出哪些系统调用是相关的。我还手动进一步编辑了输出。 )

$ strace -e trace=access,fstat,ioctl,open,openat,readlink,stat logname 2>&1 |
     grep -Ev '\.so\.[0-9]+", O_RD'
...
openat(AT_FDCWD, "/proc/self/loginuid", O_RDONLY) = -1 ENOENT
ioctl(0, TCGETS, {B38400 opost isig icanon echo ...}) = 0
fstat(0, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
readlink("/proc/self/fd/0", 0x7ffd2adfb030, 511) = -1 ENOENT
stat("/dev/pts/", {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
openat(AT_FDCWD, "/dev/pts/", O_RDONLY|O_NONBLOCK|O_CLOEXEC|O_DIRECTORY) = 3
fstat(3, {st_mode=S_IFDIR|0755, st_size=0, ...}) = 0
stat("/dev/pts/1", {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 1), ...}) = 0
close(3)                                = 0
access("/var/run/utmpx", F_OK)          = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/var/run/utmp", O_RDONLY|O_CLOEXEC) = 3
...
logname: no login name
+++ exited with 1 +++

如果它无法打开 /proc/self/loginuid,那么它会花一些时间找到与它自己的 stdin 关联的终端的名称(简单的方法,readlink("/proc/self/fd/0"),不起作用因为我为这个测试去掉了所有 /proc)然后它 确实 /var/run/utmp 中查找它,在首先确定 /var/run/utmpx 没有之后'不存在。 (这是一个错误;测试你要打开的文件是否已经存在固有的 TOCTOU race。它应该只是打开它并检查打开是否失败。)它没有找到一个条目,所以它失败了.这是因为我使用的 Linux 发行版 (sp. Debian unstable) 已经决定每个终端 window 都有自己的 utmp 条目是愚蠢的,我的整个 X 会话应该只有一个,我是否打开了任何终端:

$ who
zack     :0           2018-05-30 17:37 (:0)

打破传统,但我认为是明智的。