计算机 / 读书笔记 · 2021年12月19日 0

APIUE读书笔记chapter4

stat、fstat、fstat和lstat函数

#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);

struct stat {
	mode_t 	st_mode;	/* file type & mode (permissions) */
	ino_t	st_ino;		/* i-node number (serial number) */
	dev_t	st_dev;		/* device number (file system) */
	dev_t 	st_rdev;	/* device number for special files */
	nlink_t	st_nlink;	/* number of links */
	uid_t 	st_uid;		/* user ID of owner */
	gid_t	st_gid;		/* group ID of owner */
	off_t	st_size;	/* size in bytes, for regular files */
	struct timespec st_atim;	/* time of last access */
	struct timespec st_mtim;	/* time of last modification */
	struct timespec st_ctim;	/* time of last file status change */
	blksize_t	st_blksize;	/* best I/O block size */
	blkcnt_t	st_blocks;	/* number of disk blocks allocated */
};

stat返回指定文件的信息,fstat返回文件描述符指向的文件的信息,lstat与stat类似,但是在遇到符号链接文件时,lstat不会去找符号链接引用的文件,而是直接返回该符号链接的信息。fstatat函数则在fd代表的目录下去找pathname代表的文件(如果pathname为绝对路径就忽略fd,如果flag带了AT_FDCWD标志,则在当前目录下找pathname),flag参数则包含了是否要follow symbol link的选项(默认是要follow symbol link的)。

timespec至少有两个域:

time_t tv_sec;
long tv_nsec;

File Types

  1. Regular file.
  2. Directory file.
  3. Block special file. A type of file providing buffered I/O access in fixed-size units to devices such as disk drivers.
  4. Character special file. A type of file providing unbuffered I/O access in variable-sized units to devices. All devices on a system are either block special files or character special files.
  5. FIFO(named pipe).
  6. Socket.
  7. Symbol link. A type of file that points to another file.

判断文件类型需要用st_mode成员变量和下面的宏:

MacroType of file
S_ISREG()regular file
S_ISDIR()directory file
S_ISCHR()character special file
S_ISBLK()block special file
S_ISFIFO()pipe or FIFO
S_LINK()symbol link
S_ISSOCK()socket

由于POSIX.1允许IPC对象(如message queue、semaphore等)用文件的方式实现,也可以用下面的宏判断文件是否是IPC对象,只是下面的宏传入参数是stat结构体指针而不是st_mode成员变量:

MacroType of object
S_TYPEISMQ()message queue
S_TYPEISSEM()semaphore
S_TYPEISSHM()shared memory object

早起UNIX系统没有提供S_ISxxx这样的宏,而使用st_mode与S_IFMT相与的结果去与S_IFxxx的常量值进行比较来判断文件的类型。

Set-User-ID和Set-Group-ID

每个进程至少有6个关联的ID:

id typeexplanation
real user IDwho we really are
real group ID~
effective user IDused for file access permission checks
effective group ID~
supplementary group IDs~
saved set-user-IDsaved by exec functions
saved set-group-ID~

执行一个程序的时候,新起的进程的effective user ID和effective group ID经常就是real user ID和real group ID。但是如果该程序文件的st_mode里设置了set-user-ID位和set-group-ID位,那么新起的进程就会把effective user ID设为文件的owner,把effective group ID设为文件的group owner。
检测st_mode里的set-user-ID位和set-group-ID位可以用S_ISUID宏和S_ISGID宏。

File Access Permissions

<sys/stat.h>

st_mode maskMeaning
S_IRUSRuser-read
S_IWUSRuser-write
S_IXUSRuser-execute
:———————-:———————-
S_IRGRPgroup-read
S_IWGRPgroup-write
S_IXGRPgroup-execute
:———————-:———————-
S_IROTHother-read
S_IWOTHother-write
S_IXOTHother-execute

关于文件权限的一些规则:

  • 任何时候我们想要通过路径名打开(open)某个文件时,我们都需要具有该路径名里提到的每个目录的执行权限。所以目录的execution permission bit也常被称作search bit。如果是打开当前目录下的文件,那么也同样需要当前目录的执行权限。
  • 使用O_TRUNC标志需要写权限。
  • 在指定目录创建文件需要具有该目录的写权限和执行权限。
  • 删除指定目录里的某个文件需要具有该目录的写权限和执行权限。不需要该文件的读权限也不需要该文件的写权限。
  • 对于想用exec函数执行的文件,需要具有该文件的执行权限以及该文件必须是regular file。

进程访问文件时(open,creat,delete),判定权限是否允许的规则(内核按顺序执行判断):

  1. 如果进程的effective user ID是0(超级用户),那么允许此次file access。
  2. 如果进程的effective user ID等于文件的owner ID,那么根据该文件为owner设置的权限位判断该进程的此次file access是否被允许;
  3. 如果进程的effective group ID或者进程的supplementary group ID之一等于该文件的group ID,那么根据该文件为group设置的权限位判断此次file access是否被允许;
  4. 如果有其他特殊access bit被设置为允许此次file access,那么access is allowed。否则进程的此次访问被拒绝。

其中需要注意的是,如果进程effective user ID正好等于文件owner ID,那么就只会按owner的权限去判断不会再按照group的权限去判断。如果进程正好不拥有该文件但是属于该文件的用户组,那么也是只按照该文件的用户组权限进行判断,而不再参考其他的权限设置。

Ownership of New Files and Directories

The user ID of a new file is set to the effective user ID of the process. POSIX.1 allows an implementation to choose one of the following options to determine the group ID of a new file:

  1. The group ID of a new file can be the effective group ID of the process.
  2. The group ID of a new file can be the group ID of the directory in which the file is being created.
    It depends on the implementation.

access与faccessat函数

#include <unistd.h>

int access(const char *pathname, int mode);

int faccessat(int fd, const char *pathname, int mode, int flag);

用real user ID和real group ID进行file access test,而不是使用effective IDs。

mode的值可以取F_OK(用于检测一个文件是否存在)或者下面的值的或:

modeDescription
R_OKtest for read permission
W_OKtest for write permission
X_OKtest for execute permission

flag的值用于设置faccessat函数的行为。如果flag设为AT_EACCESS,那么就用effective user ID和effective group ID进行file access的权限检查。

umask函数(设置掩码)

#include <sys/stat.h>

mode_t umask(mode_t cmask);

Any bits that are on in the file mode creation mask are turned off in the file’s mode.

chmod、fchmod、fchmodat函数

#include <sys/stat.h>

int chmod(const char *pathname, mode_t mode);

int fchmod(int fd, mode_t mode);

int fchmodat(int fd, const char *pathname, mode_t mode, int flag);

各函数的意义基本可以顾名思义,懒得赘述。其中fchmodat函数的flag用于控制是否follow symbol link:取值AT_SYMLINK_NOFOLLOW时,fchmodat不会follow symbol link。
要改变文件的权限位需要进程的effective user ID等于文件的owner ID或者进程拥有超级用户权限。
mode的值除了可以设置为之前讲的9种值的或之外,还可以或上S_IRWXU、S_IRWXG、S_IRWXO以及S_ISUID(set-user-ID on execution)、S_ISGID(set-group-ID on execution)、S_ISVTX(saved-text, sticky bit)。

Sticky Bit

Sticky bit或者saved-text bit,S_ISVTX,在现代系统中如果某个目录设置了这个标志,那么该目录下的文件只有在以下情形之一才可以被用户删除或者重命名:

  • 用户拥有该文件
  • 用户拥有该目录
  • 用户是超级用户

通常使用这个标志的目录有/tmp和/var/tmp。

chown、fchown、fchownat和lchown函数

#include <unistd.h>

int chown(const char *pathname, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int fchownat(int fd, const char *pathname, uid_t owner, gid_t group, int flag);
int lchown(const char *pathname, uid_t owner, gid_t group);

除了lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW情况下)是改变symbol link文件本身而非指向文件的owner,这几个函数的作用没什么差别。
由于fchown函数是对已经打开的文件进行操作,因此fchown不能改变symbol link的owner,而只能改变symbol link指向文件的owner。

File Size

stat结构体中的st_size成员表示文件大小,此成员仅对普通文件、目录和symbol link有效。
对于symbol link,st_size表示的是指向文件的路径名长度(不包含结尾的null byte)。
大多数现代UNIX系统实现在stat结构体中提供了st_blocks和st_blksize成员,用以表示为此文件开辟的block数,以及用于I/O的preferred block size。
用ls命令显示的是文件大小,而用du命令可以查看文件实际占据的磁盘大小,从而检验文件中是否存在holes。用cat file > newfile方式生成的新文件不会有holes,而用cp命令则会生成和原文件一样具有holes的新文件。

File Truncation

#include <unistd.h>

int truncate(const char *pathname, off_t length);

int ftruncate(int fd, off_t length);

除了open时使用O_TRUNC标志将文件全部截断,还可以使用truncate、ftruncate函数将文件截断为指定长度,而且缩小、扩展操作都是允许的。扩展时可能会产生hole,且对文件扩展时,超出文件原有size的部分在进行read操作时都会当做0返回。

File Systems

  • 多个文件目录项可以指向同一个inode,stat结构体中的st_nlink保存了该inode的link count。
  • 对于symbol link,其文件类型为S_IFLNK。该文件的内容(data blocks)保存的是其指向文件的名字。
    所以这里就清楚了,link或者所谓的hard link是直接指向同一个inode,而symbol link或者soft link则是有自己的inode,只不过inode指向的data blocks存的是该symbol link指向的文件的路径名且该symbol link的inode里文件类型的值是S_IFLINK,通过这个值来告诉系统它是一个symbol link。
  • inode中包含了关于该文件的大多数信息,stat结构体中的信息基本上是从inode中获取的,除了两项数据:文件名文件名和inode number是从文件目录项中获得。
  • 因为文件目录项中的inode number只能指向同一个文件系统中的inode,所以硬链接不能跨文件系统。
  • 重命名时只要文件系统不改变就不会发生数据的拷贝,只是需要创建一个新的指向原inode的文件目录项然后删除原来的文件目录项即可。mv命令就采取了这种工作方式。
  • 目录的link count至少是二,因为目录的父目录的directory block中会有一个该目录的目录项指向该目录的inode,然后该目录的directory block中会有一个.目录项指向该目录的inode。如果该目录中有子目录,那么这些子目录的directory blocks中的..目录项还会指向这个目录的inode。简言之,位于”叶子节点”的目录的link count为2。

link,linkat,unlink,unlinkat和remove函数

一个文件可以有多个目录项指向其i-node。

#include <unistd.h>
int link(const char *existingpath, const char *newpath);

int linkat(int efd, const char *existingpath, int nfd, const char *newpath, int flag);

int unlink(const char *pathname);

int unlinkat(int fd, const char *pathname, int flag);

这些函数会创建一个名为newpath的新目录项指向existingpath。如果newpath已经存在则返回错误。只会创建newpath中的最后一部分(last component),前面的路径必须是已经存在的。
如果existingpath是一个符号链接,那么flag参数用于指示是否follow symbol link。如果flag为AT_SYMLINK_FOLLOW,则指向existingpath指向的文件,否则指向existingpath本身。
新目录项的创建和文件的link count的增加是一个原子操作。
大多数实现要求newpath和existingpath在同一个文件系统中。如果某个实现支持创建指向目录的硬链接,那么只有超级用户才能有此权限。这是因为这种指向目录的硬链接会造成文件系统中的回路,而大多数处理文件系统的工具处理不了这种回路。
当文件的link数降到0时,该文件会被删除,除非该文件在某进程中还处于open状态。当该文件被关闭时,内核先统计打开此文件的进程数,如果为0再统计link数,如果也是0,那么该文件就将被删掉。有些程序就利用了unlink的这种特性,先用open或creat创建临时文件并打开,然后立马unlink该文件,这样创建的临时文件不会被删除,但是当程序close该文件或者程序结束(正常结束或者异常终止)时系统close该文件时,该文件就会被自动删除。
在用unlinkat时,flag提供了unlink文件目录的选项,只要将其设置为AT_REMOVEDIR。
如果unlink接受的pathname参数是一个symbol link,那么unlink只会移除该symbol link而不是该link指向的文件。
如果系统支持,超级用户可以unlink一个目录,但是这种情况下其实应该使用rmdir函数。
也可以使用remove函数来unlink一个文件或目录。对于文件而言,remove等价于unlink;对于目录而言,remove等价于rmdir。

#include <stdio.h>

int remove(const char *pathname);

rename和renameat函数

#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);

需要特殊处理的几种情况:

  1. 如果oldname指定的是一个文件而不是目录,那么我们在重命名文件或者软链接。在这种情况下newname不能和已经存在的目录同名。如果newname和某个已经存在的文件重名,那么先删除该文件再将oldname文件/软链接重命名为newname文件/软链接。这个操作需要oldname和newname所处的目录的写权限。
  2. 如果oldname是目录,那么我们就是在重命名目录。此时newname不能和已经存在的文件/符号链接同名,可以和已经存在的目录同名,且该目录必须是一个空目录。如果存在和newname同名的空目录,那么首先将该目录删去,然后将oldname重命名为newname。另外,当重命名目录时,newname不能以oldname作为路径前缀(如将/usr/foo重命名为/usr/foo/testdir是允许的)。
  3. 如果oldname或newname是symbolic link,那么只有该symolic link被重命名或删掉,和该symbolic link指向的文件没有任何关系。
  4. 不能重命名...。更准确地说,不能让...作为oldname或newname的最后一部分。
  5. 如果oldname和newname指向同一个文件,那么函数什么都不做成功返回。

Symbolic Links

软链接与硬链接的区别:

  • 硬链接要求该硬链接和指向的文件在同一个文件系统
  • 只有超级用户可以创建指向目录的硬链接

对于是否follow symbolic link的函数的分类:

FunctionDoes not follow symbolic linkFollows symbolic link
accessy
chdiry
chmody
chowny
creaty
execy
lchowny
linky
lstaty
openy
opendiry
pathconfy
readlinky
removey
renamey
staty
truncatey
unlinky

mkdir,mkfifo,mknod和rmdir函数不能接受symbol link作为参数。
一个特例:当open函数以O_CREAT和O_EXCL标志被调用时,如果传入的路径名是一个symbol link,那么open会失败,且将错误码设为EEXIST。如果不这样做那么系统将会有安全隐患。

cat命令会follow symbol link。ls命令的-F选项会为symbol link文件加上一个@符号。

Creating and Reading Symbol Links

#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);
int symlink(const char *actualpath, int fd, const char *sympath);

创建symbol link时,不需要actualpath对应的文件一定存在。

读取symbol link信息:

#include <unistd.h>
ssize_t readlink(const char* restrict pathname, char *restrict buf, size_t bufsize);
ssize_t readlinkat(int fd, const char* restrict pathname, char *restrict buf, size_t bufsize);

File Times

FieldDescriptionExamplels(1)option
st_atimlast-access time of file dataread-u
st_mtimlast-modification time of file datawritedefault
st_ctimlast-change time of i-node statuschmod, chown-c

futimens, utimensat, and utimes functions

修改access time和modification time。

#include <sys/stat.h>

int futimeens(int fd, const struct timespec times[2]);

int utimensat(int fd, const char *path, const struct timespec times[2], int flag);

#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
#include <sys/stat.h>

int futimens(int fd, const struct timespec times[2]);

int utimensat(int fd, const char *path, const struct timespec times[2], int flag);

mkdir,mkdirat and rmdir functions

#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);

int mkdirat(int fd, const char *pathname, mode_t mode);

int rmdir(const char *pathname);

Reading Directories

目录文件本身只有内核才可写入。目录文件的写权限位和执行权限位只是决定了可以在该目录下创建文件和删除文件,但是这些权限位并没有给予写目录文件的权限。

#include <dirent.h>

DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
struct dirent *readdir(DIR *dp);
void rewinddir(DIR *dp);
int closedir(DIR *dp);
long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);

dirent至少包含两个域:

ino_t d_ino;		/* i-node number */
char d_name[];		/* null-terminated filename */

chdir、fchdir、getcwd函数

current working directory是一个进程的属性。改变当前进程的current working directory,不影响父进程的current working directory。

#include <unistd.h>

int chdir(const char *pathname);
int fchdir(int fd);
char *getcwd(char *buf, size_t size);

Device Special Files

关于st_dev和st_rdev两个域的作用:

  • 每个文件系统都有一个主设备号和一个从设备号,被编码在一个dev_t类型里
  • 可以用major和minor宏(<sys/types.h>)从dev_t变量里获取这两个设备号
  • st_dev域里包含了对应文件所处的文件系统的设备号和相应的i-node
  • 只有character special files和block special files有st_rdev。该值包含了对应设备的设备号。

Summary of File Access Permission Bits

ConstantDescriptionEffect on regular fileEffect on directory
S_ISUIDset-user-IDset effective user ID on execution(not used
S_ISGIDset-group-IDif group-execute set, then set
effective group ID on execution;
otherwise, enable mandatory
record locking(if supported)
set group ID of new files created in directory to group ID of directory
S_ISVTXsticky bitcontrol caching of file contents(if supported)restrict removal and renaming of files in drectory
S_IRUSRuser-readuser permission to read fileuser permission to read directory entries
S_IWUSRuser-writeuser permission to write fileuser permission to remove and create files in directory
S_IXUSRuser-executeuser permission to execute fileuser permission to search for given pathname in directory
S_IRGRPgroup-readgroup permission to read filegroup permission to read directory entries
S_IWGRPgroup-writegroup permission to write filegroup permission to remove and create files in directory
S_IXGRPgroup-executegroup permission to execute filegroup permission to search for given pathname in directory
S_IROTHother-readother permission to read fileother permission to read directory entries
S_IWOTHother-writeother permission to write fileother permission to remove and create files in directory
S_IXOTHother-executeother permission to execute fileother permission to serach for given pathname in directory