UNIX环境高级编程——文件和目录

本文详细介绍了UNIX文件系统的特性,包括stat系列函数在获取文件信息中的作用,文件的类型如普通文件、目录、特殊文件等,以及文件的访问权限、用户ID和组ID的设置。同时,讨论了文件所有权、访问权限位、文件截断、文件系统结构、链接和重命名功能,特别强调了硬链接和符号链接的区别。
摘要由CSDN通过智能技术生成

4.1 引言

本章将描述文件系统的其他特征和文件的性质。

4.2 函数stat、fstat、fstatat和lstat

4个stat函数:

#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);
										// 所有4个函数的返回值:若成功,返回0;若出错,返回-1
  • stat函数返回与pathname命名文件有关的信息结构;
  • fstat函数获得已在描述符fd上打开文件的有关信息;
  • lstat函数类似于stat函数,但在当命名文件是一个符号链接时,返回该符号链接的有关信息,而不是由该符号链接引用的文件的信息;
  • fstatat函数返回一个相当于当前打开目录fd参数指向)的路径名返回文件统计信息。flag参数控制着是否跟随着一个符号链接,当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号链接,而是返回符号链接本身的信息,否则,在默认情况下,返回符号链接所指向的实际文件的信息;
  • 参数buf是一个stat类型的指针,由函数填充其结构。

4.3 文件类型

UNIX系统的文件类型包括:

  • 普通文件:最常用的类型,包含了某种形式的数据;
  • 目录文件:包含了其他文件的名字以及指向与这些文件有关信息的指针;
  • 块特殊文件:提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行;
  • 字符特殊文件:提供对设备不带缓冲的访问,每次访问长度可变;
  • FIFO:用于进程间通信,亦称管道
  • 套接字:用于进程间的网络通信,也可用于在一台宿主机上进程之间的非网络通信;
  • 符号链接:指向另一个文件。

文件类型信息包含在stat结构的st_mode成员中,用下表中的宏确定,这些宏的参数为stat成员st_mode。

文件类型
S_ISREG()普通文件
S_ISDIR()目录文件
S_ISCHR()字符特殊文件
S_ISBLK()块特殊文件
S_ISFIFO()管道或FIFO
S_ISLNK()符号链接
S_ISSOCK()套接字文件

POSIX.1允许实现将进程间通信(IPC)对象说明为文件,下表中的宏可用来从stat结构中确定IPC对象的类型,这些宏的参数为stat结构指针。

对象的类型
S_TYPEISMQ()消息队列
S_TYPEISSEM()信号量
S_TYPEISSHM()共享存储对象

4.4 设置用户ID和设置组ID

与每个进程相关联的用户ID和组ID
实际用户ID 标识我们实际上是谁;
在登录时取自口令文件中的登录项;
在登录会话期间不改变,但超级用户可以修改。
实际组ID
有效用户ID 用于文件访问权限检查
有效组ID
附属组ID
保存的设置用户ID 由exec函数保存;
在执行程序时包含了有效用户ID和有效组ID的副本。
保存的设置组ID
  • 通常,有效用户ID等于实际用户ID,有效组ID等于实际组ID;
  • 每个文件有一个所有者和组所有者,所有者有stat结构的st_uid指定,组所有者则由st_gid指定;
  • 当执行一个程序文件时,进程的有效用户ID通常就是实际用户ID,有效组ID通常是实际组ID;
  • 可在文件模式字(st_mode)中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)”,在文件模式字中设置另一位,它将执行此文件的进程的有效组ID设置为文件的组所有者ID(st_gid),这两位分别被称为设置用户ID(set-user-ID)位和设置组ID(set-group-ID),可分别用常量S_ISUIDS_ISGID测试。

4.5 文件访问权限

st_mode值也包含了对文件的访问权限位,每个文件有9个访问权限位,可分3类:

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

4.6 新文件和目录的所有权

  • 新文件的用户ID设置为进程的有效用户ID;
  • 新文件的组ID可以是进程的有效组ID,也可以是它所在目录的组ID。

4.7 函数access和faccessat

accessfaccessat函数是按实际用户ID实际组ID进行访问权限测试:

#include <unistd.h>

int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 如果测试文件是否已经存在,mode就为F_OK,否则是下表常量的按位或
mode说明
R_OK测试读权限
W_OK测试写权限
X_OK测试执行权限
  • 如果flag设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。

  • faccessat函数计算相对于打开目录(由fd参数指向)的pathnamefaccessat函数与access函数在下面两种情况下是相同的:
    (1)pathname参数为绝对路径;
    (2)fd参数取值为AT_FDCWDpathname参数为相对路径时。

4.8 函数umask

umask函数为进程设置文件模式创建屏蔽字,并返回之前的值:

#include <sys/stat.h>

mode_t umask(mode_t cmask);
						// 返回值:之前的文件创建屏蔽字
  • cmask参数是4.5节中9个常量的若干个按位“”构成的;
  • 文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭。

4.9 函数chmod、fchmod和fchmodat

chmodfchmodfchmodat这3个函数可以更改现有文件的访问权限:

#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);
										// 3个函数返回值:若成功,返回0;若出错,返回-1
  • chmod函数在指定的文件上进行操作;
  • fchmod函数对已打开的文件进行操作;
  • fchmodat计算相对于打开目录(由fd参数指向)的pathnameflag参数用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号链接。

参数mode是下表中所有常量的按位或:

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

4.10 粘着位

S_ISVTX被称为粘着位:如果一个可执行程序文件的这一位被设置了,那么当该程序第一次被执行,在其终止时,程序正文部分的一个副本仍被保存在交换区(程序的正文部分是机器指令);交换区被作为一个连续文件来处理,这使得下次执行该程序时能较快地将其载入内存。

4.11 函数chown、fchown、fchownat和lchown

下面几个chown函数可用于改变文件的用户ID和组ID,如果两个参数ownergroup中的任意一个是**-1**,则对应的ID不变:

#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);
										// 4个函数的返回值:若成功,返回0;若出错,返回-1
  • 在符号链接情况下,lchownfchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符号链接本身的所有者,而不是该符号链接所指向的文件的所有者;
  • fchown函数改变fd参数指向的打开文件的所有者,因为它在一个已打开的文件上操作,所以不能用于改变符号链接的所有者;
  • fchownat函数与chown或者lchown函数在下面两种情况下是相同的:
    (1)pathname参数为绝对路径;
    (2)fd参数取值为AT_FDCWDpathname参数为相对路径;
    在这两种情况下,如果flag参数中设置了AT_SYMLINK_NOFOLLOW标志,fchownatlchown行为相同,如果flag参数中清除了AT_SYMLINK_NOFOLLOW标志,则fchownatchown行为相同。如果fd参数设置为打开目录的文件描述符,并且pathname参数是一个相对路径名,fchownat函数计算相对于打开目录的pathname

4.12 文件长度

stat结构成员st_size表示以字节为单位的文件的长度,此字段只对普通文件、目录文件和符号链接有意义:

  • 对于普通文件,其文件长度可以是0,在开始读这种文件时,将得到文件结束(end-of-file)指示;
  • 对于目录,文件长度通常是一个数(如16或512)的整倍数;
  • 对于符号链接,其文件长度是文件名中的实际字节数。

4.13 文件截断

为了截断文件可以调用函数truncateftruncate

#include <unistd.h>

int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 这两个函数将一个现有文件长度截断为length
  • 如果该文件以前的长度大于length,则超过length以外的数据就不再能访问;
  • 如果以前的长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就是可能在文件中创建了一个空洞)。

4.14 文件系统

可以把一个磁盘分成一个或多个分区,每个分区可以包含一个文件系统,i节点是固定长度的记录项,它包含有关文件的大部分信息:
磁盘、分区和文件

在这里插入图片描述

  • 任何一个文件可以有多个目录项指向其i节点,每个i节点中都有一个链接计数,其值是指向该i节点的目录项数,只有当链接计数减少至0时,才可以删除该文件(也就是可以释放该文件占用的数据块),在stat结构中,链接计数包含在st_nlink成员中,其基本系统数据类型是nlink_t,这种链接类型称为硬链接
  • 另外一种链接类型称为符号链接,符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字;
  • i节点包含了文件有关的所有信息:文件类型文件访问权限位文件长度指向文件数据块的指针等,stat结构中的大多数信息都取自i节点,只有文件名和i节点编号存放在目录项中;
  • 一个目录项不能指向另一个文件系统的i节点。

4.15 函数link、linkat、unlink、ublinkat和remove

创建一个指向现有文件的硬链接的方法是使用link函数或linkat函数:

#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);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 这两个函数创建一个新目录项newpath,它引用现有文件existingpath,如果newpath已经存在,则返回出错;
  • 只创建newpath中的最后一个分量,路径中的其他部分应当已经存在,例如,如果newpath是/home/data/abc,则必须要求/home/data目录已经存在,这点和mkdir函数要求一样;
  • 对于linkat函数,现有文件是通过efdexistingpath参数指定的,新的路径名是通过nfdnewpath参数指定的。默认情况下,如果两个路径名中的任一个是相对路径,那么它需要通过相对于对应的文件描述符进行计算;如果两个文件描述符中的任一个设置为AT_FDCWD,那么相应的路径名(如果它是相对路径)就通过相对于当前目录计算;如果任一路径名是绝对路径,相应的文件描述符参数就会被忽略;
  • 当现有文件是符号链接时,如果在flag参数中设置了AT_SYMLINK_FOLLOW标志,就创建指向符号链接目标的链接,如果这个标志被清除,则创建一个指向符号链接本身的链接。

为了删除一个现有的目录项,可以调用unlink函数:

#include <unistd.h>

int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 这两个函数删除目录项,并将由pathname所引用文件的链接计数减1;如果对该文件还有其他链接,则仍可通过其他链接访问该文件的数据;只有当链接计数达到0时,该文件内容才可被删除;
  • 如果pathname参数是相对路径,那么计算相对于fd文件描述符代表的目录的路径名;如果fd参数设置为AT_FDCWD,那么通过相对于调用进程的当前工作目录来计算路径名;如果pathname参数是绝对路径名,那么fd参数被忽略;
  • 如果flag参数设置为AT_REMOVEDIRunlinkat函数类似于rmdir一样删除目录,如果这个标志被删除,unlinkatunlink执行同样操作;
  • 如果pathname是符号链接,那么unlink删除该符号链接,而不是删除该链接所引用的文件。

可以使用remove函数解除对一个文件或目录的链接:

#include <stdio.h>

int remove(const char *pathname);
								// 返回值:若成功,返回0;若出错,返回-1
  • 对于文件,remove的功能与unlink相同;
  • 对于目录,remove的功能与rmdir相同。

4.16 函数rename和renameat

文件或目录可以用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);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 如果oldname指定了相对路径,就相对于oldfd参数引用的目录来计算oldnamenewname类似;
  • oldfdnewfd参数(或两者)都能设置成AT_FDCWD,此时相对于当前目录来计算相应的路径名。

4.17 符号链接

符号链接是对一个文件的间接指针,它与硬链接不同,硬链接直接指向文件的i节点,引入符号链接的原因是为了避开硬链接的一些限制:

  • 硬链接通常要求链接和文件位于同一文件系统;
  • 只有超级用户才能创建指向目录的硬链接。

对符号链接以及它指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号链接;符号链接一般用于将一个文件或整个目录结构移到系统中的另一个位置。

4.18 创建和读取符号链接

可以用symlinksymlinkat函数创建一个符号链接:

#include <unistd.h>

int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 在创建此符号链接时,并不要求actualpath已经存在;
  • actualpathsympath并不需要位于同一文件系统中;
  • symlinkat函数的sympath参数根据相对于打开文件描述符引用的目录(由fd参数指定)进行计算;如果sympath参数指定的是绝对路径或者fd参数设置了AT_FDCWD值,那么symlinkat就等同于symlink函数。

可以用readlinkreadlinkat函数来打开符号链接本身:

#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);
										// 两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1
  • 如果函数成功执行,则返回读入buf的字节数,在buf中返回的符号链接的内容不以null字节终止;
  • pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWDreadlinkat函数的行为与readlink相同;
  • 如果fd参数是一个打开目录的有效文件描述符并且pathname参数是相对路径名,则readlinkat计算相对于由fd代表的打开目录的路径名。

4.19 文件的时间

每个文件维护3个时间字段,保存在stat结构中:

  • st_atime:文件数据的最后访问时间;
  • st_mtime:文件数据的最后修改时间;
  • st_ctime:i节点状态的最后更改时间。

4.20 函数futimens、utimensat和utimes

一个文件的访问时间和修改时间可以用futimensutimensat函数修改,它们使用纳秒级精度的时间戳:

#include <sys/stat.h>

int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *pathname, const struct timespec times[2], int flag);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 这两个函数的times数组参数的第一个元素包含访问时间,第二个元素包含修改时间
  • futimens函数需要打开文件来更改它的时间;
  • utimensat函数的pathname参数是相对于fd参数进行计算的,fd要么是打开目录的文件描述符,要么设置为特殊值AT_FDCWD;如果pathname指定了绝对路径,那么fd参数被忽略。
/* POSIX.1b structure for a time value.  This is like a `struct timeval' but
   has nanoseconds instead of microseconds.  */
struct timespec
{
  __time_t tv_sec;		/* Seconds.  */
  __syscall_slong_t tv_nsec;	/* Nanoseconds.  */
};
#include <sys/time.h>

int utimes(const char *pathname, const struct timeval times[2]);
										// 函数返回值:若成功,返回0;若出错,返回-1
  • utimes函数对路径名进行操作;
  • times参数是指向包含两个时间戳(访问时间修改时间)元素的数组的指针,两个时间戳是用秒和微妙表示的。
/* A time value that is accurate to the nearest
   microsecond but also has a range of years.  */
struct timeval
{
  __time_t tv_sec;		/* Seconds.  */
  __suseconds_t tv_usec;	/* Microseconds.  */
};

4.21 函数mkdir、mkdirat和rmdir

mkdirmkdirat函数创建目录:

#include <sys/stat.h>

int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
										// 两个函数返回值:若成功,返回0;若出错,返回-1
  • 这两个函数创建一个新目录,其中**.…**目录项是自动创建的;
  • 所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改,对于目录通常至少要设置一个执行权限位,以允许访问该目录中的文件名;
  • mkdirat函数的fd参数具有特殊值AT_FDCWD或者pathname参数指定了绝对路径名时,mkdiratmkdir完全一样,否则,当fd参数是一个打开目录,相对路径名根据此打开目录来计算。

rmdir函数删除目录:

#include <unistd.h>

int rmdir(const char *pathname);
								// 返回值:若成功,返回0;若出错,返回-1

4.22 读目录

对某个目录具有访问权限的任一用户都可以读该目录,但为了防止文件系统产生混乱,只有内核才能写目录。

#include <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);
								// 返回值:与dp关联的目录中的当前位置
void seekdir(DIR *dp, long loc);

4.23 函数chdir、fchdir和getcwd

进程使用chdirfchdir函数可以更改当前工作目录:

#include <unistd.h>

int chdir(const char *pathname);
int fchdir(int fd);
								// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 在这两个函数中,分别用pathname或打开文件描述符来指定新的当前工作目录;
  • 当前工作目录是进程的一个属性,只影响调用chdir的进程本身,而不影响其他进程。

可以用getcwd函数获取当前工作目录完整的绝对路径名:

#include <unistd.h>

char *getcwd(char *buf, size_t size);
										// 返回值:若成功,返回buf;若出错,返回NULL

4.24 设备特殊文件

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

4.25 文件访问权限位小结

在这里插入图片描述

4.26 实例代码

chapter4

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

MinBadGuy

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值