本章将描述文件系统的其他特性和文件的性质。
函数stat、fstat、fstatat和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,char *restrict pathname,struct stat *restrict buf,int flag);
一旦给出pathname,stat函数将返回与此命名文件相关的信息结构(存储在struct stat)
fstat函数获得已在描述符fd上打开文件的有关信息
lstat函数类似于stat,但是当命名文件时一个符号链接时,lstat返回该符号链接的有关信息,而不是该符号链接引用的文件的信息(符号链接有点像windows上的快捷方式)
stat结构基本形式如下:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
文件类型
1 普通文件 包含某种形式的数据(文本或者二进制数据)
2 目录文件 包含了其他文件的名字以及指向这些文件有关信息的指针
3 块特殊文件 这种类型的文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行
4 字符特殊文件 这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变,系统中的所有设备要么是字符特殊文件,要么是块特殊文件
5 FIFO 这种类型的文件用于进程间通信,有时也称为命名管道
6 套接字 这种类型的文件用于进程间的网络通信
7 符号链接 这种类型的文件指向另一个文件
文件类型信息包含在stat结构的st_mode成员中,可以用宏确定文件类型,下面程序将打印出文件信息
#include "apue.h" int main(int argc, char *argv[]) { int i; struct stat buf; char *ptr; for (i = 1; i < argc; i++) { printf("%s: ", argv[i]); if (lstat(argv[i], &buf) < 0) { err_ret("lstat error"); continue; } if (S_ISREG(buf.st_mode)) ptr = "regular"; else if (S_ISDIR(buf.st_mode)) ptr = "directory"; else if (S_ISCHR(buf.st_mode)) ptr = "character special"; else if (S_ISBLK(buf.st_mode)) ptr = "block special"; else if (S_ISFIFO(buf.st_mode)) ptr = "fifo"; else if (S_ISLNK(buf.st_mode)) ptr = "symbolic link"; else if (S_ISSOCK(buf.st_mode)) ptr = "socket"; else ptr = "** unknown mode **"; printf("%s ", ptr); } exit(0); }
设置用户ID和设置组ID
与一个进程相关联的ID有6个或更多,如下图所示
1 实际用户ID和实际组ID标识我们究竟是谁。这两个字段在登录时取自口令文件中的登录项。
2 有效用户ID、有效组ID以及附属组ID决定了我们的访问权限,下一节将对此进行说明
3 保存的设置用户ID和保存的设置组ID在执行一个程序时包含了有效用户ID和有效组ID的副本
文件访问权限
每个文件有下面的9个访问权限位,st_mode值也包含了对文件的访问权限位
umask函数
umask函数为进程设置文件模式创建屏蔽字,并返回之前的值
#include <sys/stat.h>
mode_t umask(mode_t cmast);
参数cmask是上节列出的9个常量中的按若干个按位“或”组成的
下面程序创建两个程序,演示了umask函数的作用
#include "apue.h" #include <fcntl.h> #define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH) int main(void) { umask(0); if (creat("foo", RWRWRW) < 0) err_sys("creat error for foo"); umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); if (creat("bar", RWRWRW) < 0) err_sys("creat error for bar"); exit(0); }
下面是程序运行情况
我们用shell的umask命令在运行程序前后打印文件模式创建屏蔽字,可见更改进程的文件模式创建屏蔽字并不影响父进程(常常是shell)的屏蔽字
用户可以设置umask值以控制他们所创建文件的默认权限。该值表示成八进制数,一位代表一种屏蔽的权限,如下图所示
函数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);
chmod函数在指定的文件上进行操作,而fchmod函数则对已打开的文件进行操作。
参数mode是下图所示常量的按位或
下面程序修改了上节两个文件的模式
#include "apue.h" int main(void) { struct stat statbuf; /* turn on set-group-ID and turn off group-execute */ if (stat("foo", &statbuf) < 0) err_sys("stat error for foo"); if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0) err_sys("chmod error for foo"); /* set absolute mode to "rw-r--r--" */ if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0) err_sys("chmod error for bar"); exit(0); }
下面是程序的运行情况
函数chown、fchown、fchownat和lchown
下面几个函数可用户更改文件的用户ID和组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 gruop);
文件系统
我们可以把一个磁盘分成一个或多个分区,每个分区可以包含一个文件系统,如下图所示。i节点是固定长度的记录项,它包含有关文件的大部分信息。
把i结点跟数据块分离出来,则可以看到下图所示的情况
图中两个目录项指向同一个i节点。每个i节点都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减少至0时,才可删除文件。
i节点包含了文件有关的所有信息:文件类型、文件的访问权限、文件长度和指向文件数据块的指针等。stat结构中大所属信息取自i节点。
函数link、linkat、unlink、unlinkat和remove
创建一个指向现有文件的链接的方法是使用link函数或linkat函数
#include <unistd.h> int link(const char *existringpath,const char *newpath); int linkat(int efd,const char *existingpath,int nfd,const char *newpath,int flag);
这两个函数创建一个新目录项newpath,它引用现有文件existringpath。只创建newpath最后一个分量,路径中的其他部分应当已经存在。
为了删除一个现有的目录项,可以调用unlink函数
#include <unistd.h> int unlink(const char *pathname); int unlink(int fd,const char *pathname,int flag);
这两个函数删除目录项,并将由pathname所引用文件的链接计数减1,只有当链接计数达到0时,该文件的内容才可被删除。
我们也可以用remove函数解除对一个文件或目录的链接。
#include <stdio.h> int remove(const char *pathname);
函数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);
符号链接
符号链接是对一个文件的间接指针,下图列出各个函数对符号链接的处理
创建和读取符号链接
下面函数创建一个指向actualpath的新目录项sympath。
#include <unistd.h> int symlink(const char *actualpath,const char *sympath); int symlinkat(const char *actualpath,int fd,const char *sympath);
因为open函数跟随符号链接,所以需要有一种方法打开该符号链接,并读取链接中的名字。下面函数提供了这种功能
#include <unistd.h> ssize_t readlink(const char *restrict pathname,char *restrict buf,size_t bufsize); ssize_t readkinkat(int fd,const char *restrict pathname,char *restrict buf,size_t bufsize);
两个函数组合了open、read和close的所有操作
文件的时间
对每个文件维护3个时间字段,它们的意义如下
下图列出我们已说明的各种函数对这3个时间的作用。
函数futimens、utimensat和utimes
一个文件的访问和修改时间可以用以下几个函数更改
#include <sys/stat.h> int futimens(int fd,const struct timespec time[2]); int utimensat(int fd,const char *path,const struct timespec times[2],int flag);
这两个函数的times数组的第一个元素包含访问时间、第二个元素包含修改时间。
timespec结构如下
struct timespec { time_t tv_sec; /* seconds */ long tv_nsec; /* nanoseconds */ };
如果times参数是一个空指针,则访问时间和修改时间两者都设置为当前时间。
utimes函数对路径名进行操作
#include <sys/time.h> int utimes(const char *pathname,const struct timeval times[2]);
timeval结构如下
struct timeval { time_t tv_sec; /* seconds */ long tv_usec; /* microseconds */ };
函数mkdir、mkdirat和rmdir
用mkdir和mkdirat函数创建目录,用rmdir函数删除目录
#include <sys/stat.h> int mkdir(const char *pathname,mode_t mode); int mkdirat(int fd,const char *pathname,mode_t mode);
用rmdir函数删除一个空目录。空目录是只包含.和..这两项的目录
#include <unistd.h> int rmdir(const char *pathname);
读目录
对某个目录具有访问权限的任一用户都可以读该目录,一个目录的写权限位和执行权限位决定了在该目录中能否创建文件以及删除文件。
下面所示的是对读目录的一些函数
#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
进程可以调用chdir或fchidr函数更改当前工作目录
#include <unistd.h> int chdir(const char *pathname); int fchdir(int fd);
跟umask函数一样,执行程序前后shell的当前工作目录并没有改变。
下面函数可以得到当前目录完整的绝对路径名
#include <unistd.h> char *getcwd(char *buf,size_t size);