APUECh04

5th July 2017 at 10:07am
APUE

这一章有很多类似的函数:

  • stat, fstat, fstatat, lstat
  • chmod, fchmod, fchmodat
  • chown, fchown, fchownat, lchown

依次是:

  • 无任何前缀后缀,对一个路径做操作
  • f 前缀,对一个 fd 做操作
  • f 前缀,at 后缀,对一个 fd 的相对路径做操作
  • l 前缀,对软链接做操作

4.2 stat, fstat, fstatat, and lstat Functions

#include <sys/stat.h>
int stat(const char *restrict pathname, struct stat *restrict buf );
int fstat(int fd, struct stat *buf );
int lstat(const char *restrict pathname, struct stat *restrict buf );
int fstatat(int fd, const char *restrict pathname,
struct stat *restrict buf, int flag);
// All four return: 0 if OK, −1 on error
  • stat 的对象如果是个软链,会跟过去;lstat 则不会,读的是软链本身
  • stat, lstat 的参数是个路径字符串,而 fstat 是个 fd;同时 fstat 无法读取一个软链,因为 open 无法打开一个软链本身
  • fstatat 类似 openat,跟相对路径有关

4.3 File Types

  1. Regular file
  2. Directory file
  3. Block special file: 提供带缓冲的 I/O 访问,例如磁盘设备
  4. Character special file: 提供不带缓冲的 I/O 访问,例如终端设备。所有设备,要么是 block 要么是 character
  5. FIFO: 也称命名管道 named pipe
  6. Socket
  7. Symbolic link

可以用 <sys/stat.h> 中提供的宏,通过 stat 结构的 st_mode 参数判断一个文件是什么类型的。比如 S_ISLNK(s.st_mode)

4.4 Set-User-ID and Set-Group-ID

一个 进程 有这些 ID 跟它相关:

real uid, real gidwho we really are
effective uid, effective gid, supplementary gidused for file access permission check
saved set-user-ID, saved set-group-IDsaved by exec functions
  • real uid / gid
    • 这两个 id 就是你的 uid, gid
    • 不能被改变
  • effective uid / gid
    • 一般跟 real uid / gid 是一样的
    • 被系统用来做文件访问权限检查(下一节讲)
    • 假如进程的可执行文件被设上了 set-user-ID / set-group-ID 位,那么这两个 ID 就是文件的 owner uid / gid,而不是你自己了
    • passwd(1) 是个例子,因为它改密码时需要修改一些 root 的文件。可以用 stat /usr/bin/passwd 看看
  • supplementary gid 在下一节讲
  • saved set-user-ID, saved set-group-ID 在第 8 章讲

4.5 File Access Permissions

前面讲到的 7 种类型的文件,都有 3 组权限:(user / group / other) x (read / write / execute)。对于不同类型的文件,read / write / execute 这三个动作有不同的语义。

这里面主要讲了文件和目录的访问权限。文件的访问权限比较简单,目录的比较复杂。书里讲得不够简练,ArchWiki 中的条目更好:

需要留意的有:

  1. open(2) / stat(2) 一个文件时,会对文件路径做 path resolution,你需要有路径的每一个 component 目录的执行权限。比如对于 /usr/include/stdio.h,你需要 /, /usr, /usr/include 三者的执行权限。
  2. 就算你 open(2) 的是一个当前目录下的文件,也需要当前目录的执行权限。vim README.txt implies vim ./README.txt
  3. 目录的执行权限很重要,如果对一个目录没有执行权限,那你基本上什么事情都做不了,包括读 / 写这个目录里的文件,创建 / 删除这个目录中的文件等等,ls / rm 都将不工作。这些命令往往会使用 stat 函数,而这导致需要做 path resolution,从而使内核去检查目录的执行权限。

内核怎样判断一个进程是否可以对文件做操作呢?

  1. 如果你的 effective user id 是 root,啥都可以做
  2. 如果你的 effective user id 跟文件的 owner id 一样,那么看文件权限的 user 部分
  3. 如果你的 effective group id / supplementary group id 跟文件的 group id 一样,那么看文件权限的 group 部分
  4. 上面都不满足,那看 other 部分

setuid and setgid

一个文件 / 目录可以被打上 setuid ("set user ID upon execution")和 setgid ("set group ID upon execution")位。chmod 命令行可以做这个事情:

$ chmod u+s test
$ ls -l test
-rwSr--r-- 1 onlyicelin users 0 2016-10-31 21:22 test
$ chmod +x test
$ ls -l test
-rwsr--r-- 1 onlyicelin users 0 2016-10-31 21:22 test

留意上面 ls 输出结果中,有大写的 S 和小写的 s。大写的 S 表示 setuid 位被置上,但是执行权限没有被置上;这表示 setuid 位基本上是无用的。小写的 s 表示两个位都有被置上。

4.6 Ownership of New Files and Directories

POSIX.1 要求,一个进程创建新文件时,新文件的 user ID 需要是进程的 effective user ID,而 group ID 可以在以下两种任选一种:

  1. 进程的 effective group ID
  2. 新文件所在的文件夹的 group ID

对于 Linux 来说(参考 man 2 chown 中的 Ownership of new files):

  1. 如果文件系统被 mount 上时置了 grpid 选项,那么新文件的 group ID 为所在文件夹的 group ID;
  2. 如果文件系统被 mount 上时置了 nogrpid 选项,而且所在文件夹被置上了 setgid 位,那么新文件的 group ID 就是所在文件夹的 group ID
  3. 非上述两种情况,为进程的 effective group ID

4.7 access and faccessat Functions

内核判断一个进程对文件的权限是看 effective user/group ID,而 accessfacessat 提供了让进程判断自身的 real user/group ID 有无访问某文件的能力。这两个函数很少需要被用到。

4.8 umask Function

umask 函数可以改变一个进程创建文件时的文件访问权限。你用 umask 函数设定 S_IRGRP 这类位后,open / creat 参数创建的文件的权限里面,会将这些位 去掉

一个进程会默认继承它的父进程的 umask 值。你可以在 Shell 中使用 umask(1) 命令查看和修改 umask 值。比如我的 Arch Linux 默认为 0022,即新创建的文件,默认是没有给 group 和 other 写权限的,它的权限应该是 -rwxr-xr-x

注意:

  1. 编程上,如果你希望控制你创建文件的权限,那么你需要手动置 umask(0)
  2. vim / touch 命令等创建出来的文件,考虑到安全,默认是在 umask 的基础上再去掉执行权限。默认是 -rw-r--r--

4.9 chmod, fchmod, and fchmodat Functions

用来修改文件权限。进程的 effective user ID 需要是文件的 uid 或 root。考虑到安全,有一些情况下内核会帮你清掉某些位:

  1. 一个程序为普通文件设置 sticky bit 时,Solaris 会自动清掉这个位;Linux / Mac OS X 不会。普通文件的 sticky bit 没有作用。
  2. 由于创建一个新文件时,它的 group ID 可能跟当前进程的 effective group ID & supplementary group ID 都不一样(回顾前面 set-group-ID 目录);所以这时候如果你没有 root 权限,那么你没法给这个新文件设上 set-group-ID 位。这样可以避免你新建了一个带有 set-group-ID 位、但是又不属于你自身的 groups 的一个文件;这样别人运行它时,也无法以它的 group ID 作为 effective group ID,避免一些安全问题。简单地说,你没法为一个不属于你的组的文件设置 set-group-ID 位,尽管这个文件是你创建的。

Linux

4.10 Sticky Bit

Sticky bit 在代码中为 S_ISVTX(saved-text bit)。以前磁盘存储器非常慢时,运行一个带有 sticky bit 的程序时,内核会把程序放进 swap 区以提升程序性能(顺序访问快过随机访问)。现在的系统实现无视在普通文件上的这个位。

但是新的系统给了它新的含义,你可以为一个目录设 sticky bit。这样意味着这个目录的文件只在下列三个条件至少满足一个时可以被写入 / 删除 / 重命名等:

  1. 你拥有这个文件
  2. 你拥有整个目录
  3. 你是超级用户(root)

这基本上是设计给 /tmp / /var/tmp 这种目录用的,多人使用但是又需要互不干扰。

ls 命令看一个带有 sticky bit 的目录权限时,最后一位会是 t,比如 drwxrwxrwt。可以用 chmod +t 为一个目录设置 sticky bit。

4.11 chown, fchown, fchownat, and lchown Functions

修改文件所有者 / 组的函数。注意几点:

  1. 超级用户可以改任何文件的所有者
  2. 非超级用户并且拥有该文件,看文件的 pathconf _POSIX_CHOWN_RESTRICTED 是否被应用上:
    1. 如果被应用上,你没法改文件的 uid,只能改 gid 到你自己的组(group + supplementary groups)
    2. 如果没被应用上,你可以改文件的 uid / gid 到别的 uid / gid
    3. Linux 默认应用上这个限制
  3. 基于安全考虑,大多数情况下,chown 后 set-user-ID 和 set-group-ID 会被清掉

4.12 File Size

一个文件有两个大小:

  1. 文件大小(空洞也算进里面)
  2. 文件所占的 block(表示实际存储消耗的块,不算进空洞)

空洞会被 read(2) 成为一串 \0 的序列。

4.13 File Truncation

truncate, ftruncate可以把一个文件 truncate 到特定长度。如果这个长度值大于文件本身长度,很可能会创建一个空洞。

为啥没有 ftruncateat 函数?感觉 API 设计不一致。

Refernce

  1. man 7 symlink 应该是一个关于软链接的综合文档