Skip to content
This repository has been archived by the owner on Feb 1, 2022. It is now read-only.

Latest commit

 

History

History
950 lines (753 loc) · 31.3 KB

notes.md

File metadata and controls

950 lines (753 loc) · 31.3 KB

第04章 文件和目录

章节目录 函数表 类型表


目录

函数stat、fstat、fstatat、lstat

文件类型

设置用户ID和设置组ID

文件访问权限

新文件和目录的所有权

函数access和faccessat

函数umask

函数chmod、fchmod和fchmodat

粘着位

函数chown、fchown、fchownat和lchown

文件长度

文件截断

文件系统

函数link、linkatunlink、unlinkat和remove

函数rename和renameat

符号链接

创建和读取符号链接

文件的时间

函数futimens、utimensat和utimes

函数mkdir、mkdirat和rmdir

读目录

函数chdir、fchdir和getcwd

设备特殊文件


函数stat、fstat、fstatat、lstat

int stat(const char *pathname, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *pathname, struct stat *buf);
int fstatat(int fd, const char *pathname, struct stat *buf, int flag);

头文件sys/stat.h
功能获取文件的信息结构返回值若成功返回0出错返回-1说明stat()获取文件pathname的信息结构如果文件是符号链接lstat()将返回符号链接本身的信息而不是指向的文件fstat()返回在描述符fd上打开的文件的信息结构fstatat() 返回相对于当前打开目录(fd)的路径名的信息结构flag参数AT_SYMLINK_NOFOLLOW被置位返回符号链接本身的信息而不是指向的文件。
    (1)pathname是绝对路径忽略fd参数。
    (2)pathname是相对路径开始地址是 fd 指向的目录。
    (3)pathname是相对路径fd 参数等于 AT_FDCWD开始地址是 当前工作目录

示例代码:lstat()

回顶部


文件类型

下表列出了UNIX系统的文件类型。文件类型存储在stat结构体中的st_mode成员中,可用下表中的宏S_ISxxx()来测试文件类型。将st_mode与宏S_IFMT位与,然后和宏S_IFxxx比较,也可以得到文件类型。即:

#define S_ISDIR(mode)    (((mode) & S_IFMT) == S_IFDIR)
文件类型说明
普通文件 regular file 最常用的文件类型,包含了某种形式的数据。 S_ISREG() S_IFREG
目录文件 directory file 包含了其他文件的名字以及指向这些文件有关信息的指针。 S_ISDIR() S_IFDIR
块特殊文件 block special file 提供对设备带缓冲的访问,每次访问以固定长度进行。 S_ISBLK() S_IFBLK
字符特殊文件 character special file 提供对设备不带缓冲的访问,每次访问长度可变。 S_ISCHR() S_IFCHR
FIFO 用于进程间通信,有时也称为命名管道。 S_ISFIFO() S_IFFIFO
套接字 socket 用于进程间的网络通信。 S_ISSOCK() S_IFSOCK
符号链接 symbolic link 指向另一个文件。 S_ISLNK() S_IFLNK
示例代码struct stat sta = {0};
    /* init stat from file */
    if (S_ISREG(sta.st_mode))
        printf("regular file\n");

回顶部


设置用户ID (SUID) 和设置组ID (SGID)

进程相关的ID。通常,有效用户ID等于实际用户ID;有效组ID等于实际组ID。

ID说明
实际用户ID 实际上是谁,在登录时取自口令文件中的登录项
实际组ID
有效用户ID 用于文件访问检查
有效组ID
附属组ID
保存的设置用户ID 有效用户ID的副本
保存的设置组ID 有效组ID的副本

文件相关的ID

文件所有者:stat 结构中的 st_uid
文件所属组:stat 结构中的 st_gid

当执行一个文件时,(进程的)有效用户ID等于(登录用户的)实际用户ID。也就是说,与文件没有关系。如果文件的设置用户ID(SUID)置位,那么,(进程的)有效用户ID等于文件的所有者。

有效组ID也是类似的。

SUID 和 SGID 存储在 stat 结构体中的 st_mode 成员中。可用宏 S_ISUID() S_ISGID() 测试。

回顶部


文件访问权限

st_mode包含了对文件的访问权限位,可用以下宏来测试。

st_mode屏蔽位含义 st_mode屏蔽位含义 st_mode屏蔽位含义
S_IRUSR用户读 S_IWUSR用户写 S_IXUSR用户执行
S_IRGRP组读 S_IWGRP组写 S_IXGRP组执行
S_IROTH其他读 S_IWOTH其他写 S_IXOTH其他执行

文件访问权限的规则:

  1. 打开任一类型的文件,对该名字中包含的每一个目录,以及当前工作目录,都要有执行权限。

目录的读权限:获取该目录中所有文件名的列表
目录的执行权限:可通过该目录访问一个文件
如果PATH变量指定了一个不具有执行权限的目录,shell不会在此目录下寻找可执行文件

  1. 文件的读权限:打开文件进行读操作。

与open 函数的 O_RDONLY 和 O_RDWR 标志有关。

  1. 文件的写权限:打开文件进行写操作。

与open函数的 O_WRONLY 和 O_RDWR标志有关。

  1. open 函数指定 O_TRUNC 标志时,必须具有写权限。
  2. 在目录中创建一个文件,对所在目录必须有写权限和执行权限
  3. 删除一个文件,对所在目录必须有写权限和执行权限,对文件本身不需要读写权限。
  4. exec执行某个文件,必须有执行权限,且是一个普通文件。

文件权限的测试:
进程打开、创建、删除一个文件时,内核会进行权限的测试。 测试过程涉及到文件的所有者(st_uid)、文件的所属组(st_gid), 以及进程的有效用户ID有效组ID

  1. 如果进程的有效用户ID是root,允许访问(忽略了文件的权限位)
  2. 如果进程的有效用户ID和文件的st_uid相同,如果所有者适当的访问权限位打开,则允许访问。否则拒绝访问。
  3. 如果进程的有效组ID和文件的st_gid相同,如果所属组适当的访问权限位打开,允许访问,否则拒绝。
  4. 若其他用户的适当权限位被设置,允许访问,否则拒绝。

注:如果(2)测试失败,则(3)和(4)不会测试;其他类似。
适当的访问权限是指:若进程为读而打开该文件,则用户读位应为1。其他类似。

回顶部


新文件和目录的所有权

新文件(目录)的所有者是进程的有效用户ID。
新文件(目录)的组ID可以是下列之一: \

  1. 进程的有效组ID
  2. 所在目录的组ID(可以做到向下传递权限)

对于Linux来说,如果目录的设置组ID位(SGID)被设置,则是(2);否则(1)。

回顶部


函数 access 和 faccessat

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

头文件unistd.h
功能以进程的实际用户ID和实际组ID来测试访问权限open()函数使用有效用户ID和有效组ID来测试返回值成功(有权限)返回0出错(没有权限)返回-1形参说明pathname测试文件的文件名
    mode测试的权限详细见下表flag更改 faccessat()的行为如果取值为 AT_EACCESS访问检查用进程的有效用户ID和有效组IDfd:
        (1)pathname 为绝对路径忽略此参数。
        (2)pathname 为相对路径fd 指出起始目录。
        (3)pathname 为相对路径fd 取值为 AT_FDCWD起始目录为当前工作目录

形参mode指明需要测试的权限,按以下常量位或:

mode说明头文件
R_OK测试读权限unistd.h
W_OK测试写权限
X_OK测试执行权限
F_OK测试文件是否存在

示例代码:test_access.c

回顶部


函数 umask

mode_t umask(mode_t cmask);

头文件sys/stat.h
功能为进程设置文件模式创建屏蔽字并返回之前的值返回值之前的文件模式创建屏蔽字说明cmask S_IRUSER S_IWUSR 等常量按位或构成umask 中为1的位新建文件对应的权限一定被关闭

在shell中,可用umask命令查看或设置文件模式创建屏蔽字。

示例代码:test_umask.c

回顶部


函数 chmod、fchmod、fchmodat

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

头文件sys/stat.h
功能更改现有文件的访问权限返回值成功返回0出错返回-1形参说明pathname文件名mode新的访问权限下图所示常量的按位或flag取值为 AT_SYMLINK_NOFOLLOW时不会跟随符号链接区别fchmod() 对已打开的文件进行操作fchmodat():
           (1) pathname 为绝对路径忽略fd参数。
           (2) pathname 为相对路径fd 指出起始目录。
           (3) pathname 为相对路径fd 取值为 AT_FDCWD起始目录为当前工作目录

为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者IDroot

执行时设置组ID保存正文(粘着位)用户读用户写用户执行
mode说明
S_ISUID执行时设置用户ID
S_ISGID
S_ISVTX
S_IRWXU用户读、写和执行
S_IRUSR
S_IWUSR
S_IXUSR
S_IRWXG组读、写和执行
S_IRGRP组读
S_IWGRP组写
S_IXGRP组执行
S_IRWXO其他读、写和执行
S_IROTH其他读
S_IWOTH其他写
S_IXOTH其他执行

chmod()将在下列条件自动清除两个权限位:

  1. Solaris等系统,试图设置普通文件的S_ISVTX权限位,而没有root权限,那么S_ISVTX将会自动关闭。
  2. 如果新文件的组ID不等于进程的有效组ID或者进程的附属组ID中的一个,而且进程没有root权限,那么S_ISGID会被自动关闭。

新创建文件的组ID可能是父目录的组ID。

回顶部


粘着位

S_ISVTX,粘着位(sticky bit),也称为保存正文位(saved-text bit)。对于设置了粘着位的可执行文件或目录,有以下效果:

  • 可执行文件

程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区。这使得下次执行该程序时能够较快的将其载入内存。

  • 目录

只有对该目录有写权限,并且满足下列条件之一,才能删除或重命名该目录下的文件:

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

典型应用:/tmp 目录。任何一个用户都可以在此目录创建文件,但是不能删除或重命名属于其他人的文件。

回顶部


函数 chown、fchown、fchownat 和 lchown

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

头文件unistd.h
功能更改文件的用户ID和组ID若owner/group对应的取值为-1表示不更改形参说明pathname文件名owner用户ID若为-1表示不更改group组ID若为-1表示不更改区别fchown()在一个已打开的文件上操作所以不能改变符号链接的uid/gidlchown()更改符号链接本身的uid/gidfchownat():
        (1) pathname 是绝对路径忽略参数fd。
        (2) pathname 是相对路径fd参数指出起始目录。
        (3) pathname 是相对路径fd参数是AT_FDCWD起始目录是当前工作目录flag若设置了AT_SYMLINK_NOFOLLOW位表示更改符号链接本身的uid/gid

若_POSIX_CHOWN_RESTRICTED对指定的文件生效,则:

  1. 只有超级用户进程能更改该文件的用户ID
  2. 普通进程可以更改属于自己的文件的gid,但只能更改到进程的有效组ID或附属组ID。

非超级用户进程调用成功返回时,文件的设置用户ID位(SUID)设置组ID位(SGID)将被清除。

回顶部


文件长度

  • stat 结构成员 st_size 表示以字节为单位的文件长度。
  • st_size 只对普通文件、目录文件和符号链接有意义。
  • 普通文件:长度可以为0,在开始读取时,将得到文件结束标志(EOF)。
  • 目录文件:通常是一个数(16或512)的整数倍。
  • 符号链接:在文件名中的实际字节数。
  • 部分系统对管道也定义了文件长度,表示可从管道中读取的字节数。
  • st_blksize:对文件IO较合适的块长度。
  • st_blocks:所分配的实际512字节块块数。

并不是所有系统都是512B,此值是不可移植的。

  • 文件中的空洞:设置的偏移量超过文件尾端,并写入某些数据后照成的。

查看文件大小的命令

  • ls -l 以字节为单位
  • du -s 以块为单位,一般情况下一块是512B
  • wc -c 计算文件中的字符数

回顶部


文件截断

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

头文件unistd.h
功能将文件的长度截断为length如果length=0可以在打开文件时指定 O_TRUNC 选项形参说明pathnamefdlength说明length < old_length 文件缩小超过 length 的部分将不可访问
length > old_length 文件增加old_length  length 之间的数据读为0很有可能创建了一个空洞

回顶部


文件系统

磁盘、分区和文件系统

  • 磁盘
  • 磁盘可以有多个分区,每个分区有不同的文件系统。
  • 在一个分区中,有多个柱面。每个柱面,包含i节点数组、数据块、目录块。
  • 数据块
  • 文件存储数据的地方
  • 一个文件可以有多个数据块
  • 一个数据块只能被一个文件所拥有
  • 目录块
  • 存储目录文件数据的地方
  • 目录项构成,目录项包含i节点编号和文件名
  • i节点编号可以理解为指向某个i节点,此i节点即为此目录下的文件。
  • 指向的i节点与目录块必须在同一个分区。
  • 有两个特殊的目录项:...
  • i节点
  • 包含了文件的大部分信息:文件类型、文件访问权限位、文件长度、指向文件数据块的指针。
  • 链接计数(st_nlink):指向此i节点的目录项。减为0,才会删除该文件。

由以上内容可以推出: 任何一个叶目录(不含任何文件)的链接计数总是2, (1)命名该目录的目录项 (2)该目录中的 . 目录

UFS文件系统

回顶部


函数link、linkat、unlink、unlinkat和remove

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

头文件unistd.h
功能创建一个指向现有文件的链接创建一个新目录项newpath引用现有文件existingpathexistingpath所引用文件的链接计数加1如果newpath已经存在则返回出错返回值成功返回0出错返回-1形参说明existingpath现有目录项newpath新目录项efd:
        (1) existingpath为绝对路径忽略efd。
        (2) existingpath为相对路径efd指明起始目录。
        (3) existingpath为相对路径efd为AT_FDCWD起始目录为当前工作目录nfd与efd类似flag:(假设 existingpath 是个符号链接且指向toppath)
        设置了 AT_SYMLINK_FOLLOW 标志
            newpath => toppath
            existingpath -> toppath
        没有设置 AT_SYMLINK_FOLLOW 标志
            newpath => existingpath -> toppath
        => 硬链接两个文件的inode相同
        -> 软链接两个文件的inode不同
限制:
(1) 无法跨文件系统
(2) 只有root可以创建指向目录的硬链接
int unlink(const char* pathname);
int unlinkat(int fd, const char* pathname, int flag);

头文件unistd.h
功能删除目录项并将由pathname所引用文件的链接计数减1如果出错不对该文件做任何更改返回值成功返回0出错返回-1形参说明pathname如果是符号链接则删除符号链接本身而不是指向的文件fd
    flagAT_REMOVEDIR 标志被设置时unlinkat() 可以类似于rmdir()一样删除目录否则和unlink()执行同样的操作权限要求对包含该目录项的目录具有写和执行权限如果目录设置了粘着位(S_ISVTX),还要具备下列条件之一拥有该文件或拥有该目录或具有超级用户权限

删除一个文件(inode)的条件:

  • 打开该文件的进程个数为0
  • 指向该文件(inode)的目录项为0,即链接计数为0
int remove(const char *pathname);

头文件stdio.h
功能对于文件remove() 的功能与 unlink() 相同
     对于目录remove() 的功能与 redir() 相同
返回值成功返回0出错返回-1说明此函数由 ISO C 说明绝大多数非UNIX系统不支持文件链接

回顶部


函数 rename 和 renameat

int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);

头文件stdio.h
功能对文件或目录重命名ISO C对文件定义了rename() 函数POSIX.1扩展了此标准使其包含了目录和符号链接形参说明oldnamenewnameoldfdnewfd权限要求对包含oldname及包含newname的目录具有写和执行权限
  • oldname是文件或符号链接

如果 newname 存在,必须不是一个目录。先将 newname 删除,然后将oldname重命名为 newname。

  • oldname 是目录

如果 newname存在,必须是一个空目录。先将 newname 删除,然后将oldname重命名为 newname。
newname 不能包含 oldname 作为路径前缀。不能将 /usr/foo 重命名为 /usr/foo/testdir。

  • 如果 oldname 或 newname 引用符号链接,则处理的是符号链接本身,而不是引用的文件。
  • 不能对 . 和 .. 重命名。
  • 如果 oldname 和 newname 相同,不做任何更改,直接成功返回。

回顶部


符号链接

  • 符号链接是对一个文件的间接指针.
  • 符号链接文件本身,只是存储了目标文件的路径。
  • 使用以名字引用文件的函数时,应当了解该函数是否跟随符号链接到达它所链接的文件。

注意:可以创建一个符号链接,指向一个不存在的文件。如果使用跟随符号链接的函数访问此符号链接,将会出错。

  • 使用符号链接可能会在文件系统中引用循环。大多数查找路径名的函数在这种情况发生时都将出错返回,errno等于ELOOP。

跟随符号链接:access() chdir() chmod() chown() creat() exec() link() open() opendir() pathconf() stat() truncate()

不跟随符号链接:lchown() lstat() readlink() remove() rename() unlink()

回顶部


创建和读取符号链接

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

头文件unistd.h
功能创建一个指向actualpath的新目录项sympath返回值成功返回0出错返回-1形参说明actualpathsympathfd:
    (1) sympath是绝对路径忽略此参数。
    (2) sympath是相对路径fd 指出起始目录。
    (3) sympath是相对路径fd 等于 AT_FDCWD起始目录是当前工作目录说明在创建此符号链接时并不要求actualpath已经存在
ssize_t readlink(const char *pathname, char *buf, size_t bufsize)
ssize_t readlinkat(int fd, const char *pathname, char *buf, size_t bufsize)

头文件unistd.h
功能打开符号链接本身并读该链接中的名字返回值若成功返回读取的字节数若出错返回-1形参说明pathnamebuf返回读取到的名字不以null字节终止bufsizebuf的长度fd

回顶部


文件的时间

每个文件维护3个时间字段,际精度依赖于文件系统的实现。

字段说明例子ls选项
st_atime 文件数据的最后访问时间 read -u
st_mtime 文件数据的最后修改时间 write 默认
st_ctime i节点状态的最后更改时间 chmod() chown() -c
  • 注意 st_mtime 和 st_ctime 的区别。

i节点和文件数据是分开存放的。

  • 系统并不维护对i节点的最后一次访问时间,

access() 和 stat() 不更改以上任何一个时间。

  • 当修改一个文件(目录)的时候,还有可能会影响其父目录的时间。

回顶部


函数futimens、utimensat和utimes

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

头文件sys/stat.h
功能更改一个文件的访问和修改时间返回值成功返回0出错返回-1形参说明times[0]:返回时间
    times[1]:修改时间
    (1) times为空指针st_atim和st_mtim都设置为当前时间。
    (2) times非空指针tv_nsec=UTIME_NOW相应的时间戳设置为当前时间。
    (3) tv_nsec 的值为 UTIME_OMIT相应的时间戳不变。
    (4) tv_nsec 既不是 UTIME_NOW也不是 UTIME_OMIT相应的时间戳设置为相应的 tv_sec tv_nsec权限要求如果要更改文件的时间则进程的有效ID要等于文件的所有者ID需要有写权限或者进程具有root权限

两个函数的区别:

  • futimens()需要打开文件来更改,utimensat()使用文件名更改。

utimensat()函数的fd参数:略。

  • futimens()无法更改符号链接本身的时间,utimensat()可以通过flag参数来控制。

flag如果设置了AT_SYMLINK_NOFOLLOW标志,则符号链接本身的时间会被修改。默认的行为是跟随符号链接,并把文件的时间改成符号链接的时间。

int utimes(const char *pathname, const struct timeval times[2]);

头文件sys/time.h
功能更改一个文件的访问和修改时间返回值成功返回0出错返回-1形参说明pathnametimes两个时间戳用秒和微秒表示struct timeval {
        time_t tv_sec;   /* second */
        long   tv_usec;  /* microseconds */
    }

注意:不能对状态更改时间t_ctim指定一个值,调用这些函数时,st_ctim会自动更新。

回顶部


函数mkdir、mkdirat和rmdir

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

头文件sys/stat.h
返回值成功返回0出错返回-1功能创建一个新的空目录,.  .. 目录项是自动创建的形参说明pathnamemode文件访问权限由文件模式创建屏蔽字(umask)修改对目录通常至少要设置一个执行权限位以访问该目录中的文件名fd新目录的UID和GIDUID进程的有效用户IDGID进程的有效组ID或父目录的组ID
int rmdir(const char *name);

头文件unistd.h
返回值成功返回0出错返回-1功能删除一个空目录只包含 .  .. 的目录形参说明pathname说明此函数使目录的链接计数成为0如果没有其他进程打开此目录则释放此目录占用的空间否则删除最后一个链接删除目录项...,在此目录中不能新建文件

回顶部


读目录

  • 对某个目录具有访问权限的任一用户都可以读该目录。
  • 只有内核能够写目录。
  • 对目录的写、执行权限,只是代表能在此目录中新建、删除文件,不代表写目录本身。

由于各个平台,目录的格式各不相同,为了简化读目录的过程,UNIX包含了一套与目录有关的例程,是 POSIX.1的一部分。

头文件dirent.h

打开目录从文件名或文件描述符):
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
    返回值若成功返回指针出错返回NULL读取一个目录项struct dirent *readdir(DIR *dp);
    返回值成功返回指针在目录尾或出错返回NULL复位目录项偏移量void rewinddir(DIR *dp);

关闭打开的目录int closedir(DIR *dp);
    返回值成功返回0出错返回-1获取目录偏移量:
long telldir(DIR *dp);
    返回值void seekdir(DIR *dp, long loc);

struct dirent结构体至少包含以下两个成员:

struct dirent {
    ino_t d_ino;       /* i-node number */
    char  d_name[]     /* null-terminated filename */
}
d_name的大小没有指定但是必须保证能包含NAME_MAX个字节

回顶部


函数chdir、fchdir和getcwd

  • 当前工作目录:进程的一个属性,是所有相对路径名的起点
  • 用户的起始目录:登录名的一个属性,/etc/passwd文件的第6个字段
int chdir(const char *pathname);
int fchdir(int fd);

头文件unistd.h
返回值成功返回0出错返回-1功能更改当前的工作目录chdir()跟随符号链接注意当前工作目录是进程的属性chdir/fchdir 只会影响当前的进程所以 cd 命令内建在 shell 
char *getcwd(char *buf, size_t size);

头文件unistd.h
返回值若成功返回buf出错返回NULL功能获取当前的工作目录完整的绝对路径名形参说明buf返回路径名sizebuf的长度注意buf必须有足够大的长度来容纳绝对路径名再加上一个NULL字符否则返回NULL

回到当前工作目录的方法:

  • getcwd 获取绝对路径,随后利用chdir跳转回来
  • open当前目录,随后利用 fchdir 跳转回来。

可以尝试看一下pwd的源码

示例代码:test_getcwd.c

回顶部


设备特殊文件

  • 文件系统所在的存储设备都由主、次设备号表示,设备号使用数据类型 dev_t 表示。主设备号标识设备驱动程序,次设备号标识特定的子设备。
  • 通常可以使用 major 访问主设备号,minor 访问次设备号。
  • 系统中与每个文件名关联的 st_dev 是文件系统的设备号,该文件系统包含了文件名和i节点。
  • 只有字符特殊设备和块特殊设备才有 st_rdev,此值包含实际设备的设备号。

回顶部


章节目录 函数表 类型表