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 *resrict pathname, struct stat *restrict buf, int flag);
所有4个函数的返回值:若成功,返回0;若出错,返回-1
struct stat
{
mode_t st_mode;
ino_t st_ino;
dev_t st_dev;
dev_t st_rdev;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
struct timespec st_atime;
struct timespec st_mtime;
struct timespec st_ctime;
blksize_t st_blksize;
blkcnt_t st_blocks;
};
两者的差别在于,文件指向一个符号链接时,stat返回符号链接所链接文件信息,lstat返回文件自身信息。
文件类型包括如下几种。
普通文件
包含了某种形式的数据。至于数据是文本还是二进制,对UNIX内核而言无区别。
对普通文件内容的解释由处理该文件的应用程序进行。
目录文件
包含了其他文件的名字,及指向与这些文件有关信息的指针。
只有内核可以直接写目录文件。
块特殊文件
提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行。
字符特殊文件
提供对设备不带缓冲的访问,每次访问长度可变。
系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
FIFO
用于进程间通信,有时也称为命名管道。
套接字
用于进程间的网络通信。
套接字也可用于在一台宿主机上进程之间的非网络通信。
符号链接
指向另一个文件。
文件类型信息包含在 stat 结构的 st_mode 成员中。
这些宏的参数都是 stat 结构中的 st_mode 成员。
宏 | 文件类型 |
---|---|
S_ISREG() | 普通文件 |
S_ISDIR() | 目录 |
S_ISCHR() | 字符特殊文件 |
S_ISBLK() | 块特殊文件 |
S_ISFIFO() | 管道或FIFO |
S_ISSOCK() | 套接字 |
S_ISLINK() | 符号链接 |
POSIX.1允许实现将进程间通信(IPC)对象(如消息队列和信号量等)说明为文件。
下面的宏可用来从 stat 结构中确定 IPC 对象的类型。
宏 | 对象的类型 |
---|---|
S_TYPEISMQ() | 消息队列 |
S_TYPEISSEM() | 信号量 |
S_TYPEISSHM() | 共享存储对象 |
对文件可读/可写/可执行理解
对于普通文件而言:
1.读权限允许用户标识读取该文件;
2.写权限允许用户修改该文件;
3.执行权限允许用户标识执行该文件:
a. 对于一个不可执行的文件来说,拥有执行权限是没有任何意义的;
b. 如果文件是一个程序或者某种类型的脚本时,那么它就是可执行的 。
对于目录而言:
1.读权限允许用户标识读取目录中的文件名,只能列举目录中的文件名,不能进入该目录,相应也不能查看目录下各文件的大小;
2.写权限允许用户标识修改目录(创建、移动、复制、删除);
3.执行权限允许用户搜索该目录:
- 对进程
每个文件有一个所有者和组所有者,所有者由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)位。
st_mode 值也包含了对文件的访问权限位。
所有文件类型(目录、字符特别文件等)都有访问权限。
不同的使用方式汇总:
内核进行的测试具体如下。
新文件的用户 ID 设置为进程的有效用户ID。
关于组ID,POSIX.1允许实现选择下列之一作为新文件的组ID。
依据进程的实际ID/实际组ID执行对文件执行权限测试
#include
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1
其中,如果测试文件是否已经存在,mode 就为 F_OK;否则,如下图:
// 进程用open创建文件时,文件的模式为 参数mode & ~(cmask)
#include
mode_t umask(mode_t cmask);
返回值:之前的文件模式创建屏蔽字
在进程创建一个新文件或新目录时,就一定会使用文件模式创建屏蔽字。
在文件模式创建屏蔽字中为1的位,在文件 mode 中的相应位一定被关闭。
进程更改现有文件的访问权限。
进程为超级进程或进程的有效ID等于文件的所有者ID,进程才有权限对文件执行此函数。
#include
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 函数在下列条件下自动清除两个权限位。
如果对一个目录设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重命名该目录下的文件:
目录 /tmp 和 /var/tmp 是设置粘着位的典型候选者——任何用户都可以在这两个目录中创建文件。
任一用户(用户、组合其他)对这两个目录的权限通常都是读、写和执行。
更改已经存在文件的ID和组ID。
#include
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
fchown 函数改变 fd 参数指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能用于改变符号链接的所有者。
fchownat 函数与 chown 或者 lchown 函数在下面两种情况下是相同的:一种是 pathname 参数为绝对路径,另一种是 fd 参数取值为 AT_FDCWD 而 pathname 参数为相对路径。
stat 结构成员 st_size表示以字节为单位的文件长度。
此字段只对普通文件,目录文件,符号链接有意义。
大多数现代的UNIX系统提供字段 st_blksize 和
st_blocks。
文件中的空洞
空洞是由所设置的偏移量超过文件尾端,并写入了某些数据后造成的。
文件截断
修改一个已经存在文件可访问长度。
#include
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数将一个现有文件长度截断为 length 。
如果该文件以前的长度大于 length,则超过 length 以外的数据就不能再访问。
如果以前的长度小于 length ,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0(也就可能在文件中创建了一个空洞)。
一种叫做符号链接的文件,在该文件i节点指向数据块中存储了一个文件路径(为其所链接的文件)。
对应文件类型常量为S_IFLNK。
i节点含有文件有关的所有信息:文件类型、访问权限、文件长度和指向文件数据块的指针等。
目录块每一项,i节点编号(必须为同一文件系统下的)。
当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有 i 节点的新目录项,并删除老的目录项。链接计数不会改变。
创建一个指向现有文件的链接的方法是使用 link 函数或 linkat 函数。
#include
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 中的最后一个分量,路径中的其他部分应当已经存在。
对于 linkat 函数,现有文件是通过 efd 和 existingpath 参数指定的,新的路径名是通过 nfd 和 newpath 参数指定的。
为了删除一个现有的目录项,可以调用 unlink 函数。
#include
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数删除目录项,并将由 pathname 所引用文件的链接计数减1。
如果该文件还有其他链接,则让可通过其他链接访问该文件的数据。如果出错,则不对该文件做任何更改。
即使一个i节点的引用计数变为0,但如果此i节点此时被文件表项所指向(也即处于打开状态),此i节点也不会被删除。
当此i节点无文件表现指向,引用计数又是0时,系统会释放此i节点。
#include
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
两个函数的返回值:若成功,返回0;若出错,返回-1
用link创建的是硬连接,底层文件仍然只有一个i节点。
符号链接的产生一个独立的文件,此文件的内容是其所链接文件的路径名。
一般的文件I/O函数,传入符号链接文件时,会自动替换为其所链接文件的路径再进行后续处理。
#include
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
两个函数的返回值:若成功,返回0;若出错,返回-1
函数创建了一个指向 actualpath 的新目录项 sympath 。
在创建此符号链接时,并不要求 actualpath 已经存在。并且, actualpath 和 sympath 并不需要位于同一文件系统中。
读出符号链接文件自身内容,并读出该链接的名字。
#include
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, size_t bufsize);
两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1
人为设置文件的最后访问,修改时间。
#include
int utimes(const char *pathname, const struct timeval times[2]);
函数返回值:若成功,返回0;若出错,返回-1
utimes 函数对路径名进行操作。
times 参数是指向包含两个时间戳(访问时间和修改时间)元素的数组的指针,两个时间戳是用秒和微秒表示的。
struct timeval
{
time_t tv_sec;
long tv_usec;
};
#include
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int fd, const char *pathname, mode_t mode);
两个函数返回值:若成功,返回0;若出错,返回-1
这两个函数创建一个新的空目录。
其中, . 和 … 目录项是自动创建的。
所指定的文件访问权限 mode 由进程的文件模式创建屏蔽字修改。
常见的错误是指定与文件相同的 mode (只指定读、写权限)。但是,对于目录通常至少要设置一个指向权限位,以运行访问该目录中的文件名。
用 rmdir 函数可以删除一个空目录。
空目录是只包含 . 和 … 这两项的目录。
#include
int rmdir(const char* pathname);
返回值:若成功,返回0;若出错,返回-1
对某个目录具有访问权限的任一用户都可以读该目录,但是,为了放在文件系统产生混乱,只有内核才能写目录。
目录文件结构依赖特定实现,需借助系统API由系统与其交互。
#include
DIR *opendir(const char *pathname);
DIR *fdopendir(int fd);
两个函数返回值:若成功,返回指针;若出错,返回 NULL
struct dirent* readdir(DIR* dp);
返回值:若成功,返回指针;若在目录尾或出错,返回 NULL
int closedir(DIR* dp);
返回值:若成功,返回0;若出错,返回-1
设置和获取进程当前目录
#include
int chdir(const char *pathname);
int fchdir(int fd);
两个函数的返回值:若成功,返回0;若出错,返回-1
在这两个函数中,分别用 pathname 或打开文件描述符来指定新的当前工作目录。
函数从当前工作目录(.)开始,用 … 找到其上一级目录,然后读其目录项,直到该目录项中的 i 节点编号与工作目录 i 节点编号相同,这样地就找到了其对应的文件名。
#include
char *getcwd(char *buf, size_t size);
返回值:若成功,返回buf;若出错,返回 NULL
必须向此函数传递两个参数,一个是缓冲区地址 buf ,另一个是缓冲区的长度 size (以字节为单位)。
该缓冲区必须由足够的长度以容纳绝对路径名再加上一个终止 null 字节,否则返回出错。
st_dev 和 st_rdev 这两个字段经常引起混淆。
编写 ttyname 函数时,需要使用这两个字段。
有关规则很简单:
学习参考资料:
《UNIX 环境高级编程》第3版