文件和目录
函数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, const char *restrict pathname, struct stat *buf, int flag);
给定一个pathname,stat函数返回一个与此命名文件相关的信息结构,fstat函数获得已在描述符fd上打开的文件有关信息。lstat函数类似于stat,但是当命名的文件是一个符号连接时,lstat返回该符号连接的有关信息,而不是由该符号链接引用的文件信息。
第二个参数是一个指针,指向一个我们应提供的结构。基本形式是:
struct stat{
mode_t st_mode;
ino_t std_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;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
long st_blksize;
long st_blocks;
};
文件类型
UNIX大多数文件是普通文件或目录,也有另外一些文件类型
-
普通文件(regular file)。
-
目录文件(directory file)。
-
字符特殊文件(character special file)。用于系统中某些类型的设备。
-
块特殊文件(block special file)。典型的用于磁盘设备
-
FIFO。这种文件用于进程间通信,将其称为命名管道。
-
套接口(socket)。这种文件用于网路通信
-
符号连接(symbolic link)。这种文件指向另一个文件。
在<sys/stat.h>中的文件类型宏
宏 | 文件类型 |
---|---|
S_ISREG() | 普通文件 |
S_ISDIR() | 目录文件 |
S_ISCHR() | 字符特殊文件 |
S_ISBLK() | 块特殊文件 |
S_ISFIFO() | 管道或FIFO |
S_ISLNK() | 符号连接 |
S_ISSOCK() | 套接字 |
文件存取许可权
st_mode的值也包含了对文件的存取许可权位。每个文件有9个存取许可权位,可分成三类见下表图4-6
st_mode屏蔽 | 意义 |
---|---|
S_IRUSR | 用户-读 |
S_IWUSR | 用户-写 |
S_IXUSR | 用户-执行 |
S_IRGRP | 组-读 |
S_IWGRP | 组-写 |
S_IXGRP | 组-执行 |
S_IROTH | 其他-读 |
S_IWOTH | 其他-写 |
S_IXOTH | 其他-执行 |
access函数
access函数按实际用户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 | 说明 |
---|---|
R_OK | 测试读权限 |
W_OK | 测试写权限 |
X_OK | 测试执行权限 |
F_OK | 测试文件是否存在 |
access函数的mode标志,取自<unistd.h>。 faccessat函数与access函数在下面两种情况下是相同的;一种是pathname参数为绝对路径,另一种是fd参数值为AT_FDCWD而pathname参数为绝对路径。否则,faccessat计算相对于打开目录(由fd参数指向的)pathname。
umask函数
umask函数为进程设置文件模式创建屏蔽字,并返回之前的值。
#include <sys/stat.h>
mode_t umask(mode_t cmask);
返回值:之前文件模式创建屏蔽字
其中,参数cmask是由图4-6中列出的9个常量(S_IRUSR、S_IWUSR等)中的若干个按位“或”构成的。
在进程中创建一个新文件或目录时,就一定会使用文件模式创建屏蔽字。
用户可以设置umask值以控制他们所创建文件的默认权限。该值表示成八进制数,一位代表一种要屏蔽的权限,这显示于图4-10。设置了相应位后,他所对应的权限就会被拒绝。常见的几种就是002、022和027。
屏蔽位 | 表示含义 |
---|---|
0400 | 阻止用户读 |
0200 | 阻止用户写 |
0100 | 阻止用户执行 |
0040 | 阻止组读 |
0020 | 阻止组写 |
0010 | 阻止组执行 |
0004 | 阻止其他读 |
0002 | 阻止其他写 |
0001 | 阻止其他执行 |
函数chmod、fchmod和fchmodat
chmod、fchmod和fchmodat这3个函数使我们可以更改现有文件的访问权限
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(inf fd, mode_t mode);
int fchmodat(int fd, const char *pathname, mode_t mode, int flag);
3个函数返回值:若成功,返回0;若出错,返回-1
chmod 函数在指定文件上操作,而fchmod函数则对已经打开的文件进行操作。fchmodat函数与chmod函数在两种情况下相同。。。
为了改变一个文件的权限位,进程的有效用户ID必须等于文件的所有者ID,或者进程必须具有超级用户权限。
参数mode是图4-11中所示常量的按位或。注意图4-11中,有9个取自4-6的文件访问权限位,没有列出。
mode | 说明 |
---|---|
S_ISUID | 执行时设置用户ID |
S_ISGID | 执行时设置组ID |
S_ISVTX | 保存正文(粘着位) |
S_IRWXU | 用户(所有者)读、写和执行 |
S_IRWXG | 组读、写和执行 |
S_IRWXO | 其他读、写和执行 |
函数chown、fchown、fchownat和lchown
下面几个chown函数可用于更改文件的用户ID和组ID。如果两个参数owner或group中的任意一个是-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
lchown和fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符好链接本身的所有者,而不是该符号链接所指向的文件的所有者。
文件截断
有时我们需要在文件尾端截去一些数据以缩短文件。将一个文件的 长度截断为0是一个特例,在打开文件时使用O_TRUNC标志可做到这一点。为了截断文件可调用函数truncate和ftruncate。
#include <unistd.h>
int truncate(const char *pathname, off_t len);
int ftruncate(int fd, off_t len);
两个函数的返回值:如成功,返回0;失败,返回-1
这两个函数将一个现有文件长度截断为len。如果该文件以前长度大于len,则超过len以外的数据不可以访问。若小于len,那么文件长度将增加,在以前文件的尾端和新的文件的尾端之间的数据将读作0。
函数link、linkat、unlink、unlinkat和remove
任何一个文件可以有多个目录项指向其i节点。创建一个指向现有文件的链接的方法是使用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);
这两个函数创建一个新的目录项newpath,它引用现有文件existingpath。如果newpath已存在,则返回出错。只创建newpath中的最后一个分量,路径中的其他部分应当已经存在。
对于linkat函数,现有文件通过efd和existingpath参数指定的,新的路径名是通过nfd和newpath参数指定的。默认情况下,如果两个路径名中任何一个是相对路径,那么它需要通过相对于对于文件描述符进行计算。如果两个文件描述符中的任一个设置为AT_FDCWD,那么相对应的路径名就通过相对于当前目录进行计算。如果任一路径是绝对路径,相应的文件描述符参数就会被忽略。
当现有文件是符号链接时,由flag参数来控制linkat函数是创建指向现有符号连接的链接,还是创建指向现有符好链接所指向文件的链接,flag参数设置AT_SYMLINK_FOLLOW。
为了删除一个现有的目录项,可调用unlink函数
#include <unistd.h>
int unlink(const char *pathname);
int unlink(int fd, const char *pathname, int flag);
若成功,返回0,否则返回-1
这两个函数删除目录项,并将由pathname所引用的文件的链接计数减1。为了解除对文件的链接,必须对包含该目录项的目录具有写和执行权限。
只有当链接计数达到0时,该文件的内容才可能被删除。另一个条件也会阻止删除文件的内容——只要由进程打开了该文件,其内容也不能删除。
flag参数给出了一个方法,使调用进程可以改变unlinkat函数的默认行为。当AT_REMOVEDIR标志被设置时,unlinkat函数可以类似于rmdir一样删除目录。如果这个标志清除,unlinkat和unlink执行同样的操作。
unlink的这种特性经常被程序用来确保即使时在程序崩溃时,它所创建的临时文件也不会遗留下来。进程用open或create创建一个文件,然后立即调用unlink,因为该文件仍旧时打开的,所以不会将其内容删除。只有当进程关闭该文件或终止时,该文件的内容才被删除。
也可以用remove函数解除对一个文件或目录的链接。对于文件,remove的功能与unlink相同。对于目录,remove的功能相同。
#include <stdio.h>
int remove(const char *pathname); //成功返回0,失败返回-1
函数rename和renameat
文件或目录可以用rename函数或renameat函数重命名。
#include <stdio.h>
int rename(const char* oldname, const char* newname);
int rename(int oldfd, const char* oldname, int newfd, const char* newname);
根据oldname是指文件、目录还是符号链接,有几种情况需要加以说明。我们也必须说明如果newname已存在时将发生什么。
- 如果oldname指的是一个文件,那么为该文件或符号链接重命名。在这种情况下,如果newname已经存在,则它不能引用一个目录。如果newname已存在,而且不是一个目录,则先将该目录项删除后将oldname重命名为newname。对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这两个目录。
- 如若oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该目录是空目录(空目录指的是该目录中只有. 和 …项)。如果newname存在(而且是一个空目录),则先将其删除,然后重命名。另外,当为一个目录重命名时,newname不能包含oldname目录作为路径前缀。
- 如若oldname或newname引用符号链接he,则处理的时符号链接本身。
- 不能对.和…重命名。更确切的说,.和…都不能出现在oldname和newname的最后部分。
- 作为一个特例,如果oldname和newname引用同一文件,则函数不做任何更改并返回。
符号链接
符号链接是对一个文件的间接指针,它与硬链接不同,硬链接直接指向文件的i节点。引用符号链接是为了避开硬链接的一些限制。
- 硬链接通常要求链接和文件位于同一文件系统中
- 只有超级用户才能创建指向目录的硬链接
对符号链接以及它指向何种对象并无任何文件系统的限制,任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或整个目录结构移到系统的另一个位置。
创建和读取符号链接
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
函数创建了一个指向actualpath的新目录项sympath。在创建此符号连接时,并不要求actualpath已经存在。并且,actualpath和sympath并不需要位于同一文件系统中。
因为open函数跟随符号链接,所以有一种方法打开链接本身,并读取该链接的名字。readlink和readlinkat函数提供了这种功能。
#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);
两个函数组合了open、read和close的所有操作。如果函数成功,则读入buf的字节数。在buf中返回的符号链接内容不以null字节终止。
函数futimens、utimensat和utimes
一个文件的访问和修改时间可以用以下几个函数修改。futimens、utimensat函数可以指定纳秒级精度的时间戳。用到的数据结构是与stat函数族相同的timespec结构。
#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);
这两个函数的times参数的第一个元素包含访问时间,第二个元素包含修改时间。这两个时间值都是日历时间,这是自特定时间(1970年1月1日00:00:00)以来所经过的秒数。不足秒的部分用纳秒表示。
时间戳可以按下列4种方式之一进行指定。
- 如果times参数是一个空指针,则访问时间和修改时间两者都设置为当前时间。
- 如果times参数指向两个两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_NOW,相应的时间戳就设置为当前时间,忽略相应的tv_sec字段。
- 如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值为UTIME_OMIT,相应的时间戳保持不变,忽略相应的tv_sec字段。
- 如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值既不是UTIME_OMIT,也不是UTIME_OMIT,在这种情况下,相应的时间戳设置为相应的tv_sec和tv_nsec字段的值。
执行这些函数所要求的优先权取决于times参数的值。
-
如果times是一个空指针,或者任一tv_nsec字段设为UTIME_NOW,则进程的有效用户ID必须等于该文件所有者的ID;进程对该文件必须具有写权限,或者进程是一个超级用户进程。
-
如果times是非空指针,并且任一tv_nsec字段的值既不是UTIME_NOW或UTIME_OMIT,则进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程。对文件只具有写权限是不够的。
-
如果times是非空指针,并且两个tv_nsec字段的值都为UTIME_OMIT,就不执行任何权限检查。
第三个函数utimes包含在Single UNIX Specification的XSI扩展项种。
#include <sys/time.h> int utimes(const char *pathname, const struct timeval times[2]);
utimes函数对路径名进行操作。times指向包含两个时间戳元素的数组的指针,两个时间戳使用秒和微妙表示的。
struct timeval {
time_t tv_sec;
long tv_usec;
};
注意,我们不能对状态更改时间st_ctim(i节点最近被修改的时间)指定一个值,因为调用utimes函数时,此字段自动更新。
函数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);
这两个函数创建一个新的空目录。其中,.和…目录项是自动创建的。所指定的文件访问权限mode由进程的文件模式创建屏蔽字修改。
常见的错误是指定与文件相同的mode(只指定读、写权限)。但是,对于目录通常至少要设置一个执行权限位,以允许访问该目录的文件名。
#include <unistd.h> int rmdir(const char *pathname);
用rmdir函数可以删除一个空目录。如果调用此函数使目录的链接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。
读目录
对某个目录具有访问权限的任何用户都可以读该目录,但为了防止文件系统混乱,只有内核才能写目录。一个目录的写权限和执行权限决定了在该目录中能否创建新的目录以及删除文件,它们并不表示能否写目录本身。
#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); long telldir(DIR *dp); 返回值:与dp关联的目录的当前位置 void seekdir(DIR *dp, long loc);
fdopendir函数最早出现在SUSv4中,他提供了一种方法,可以把打开文件描述符转换成目录处理函数所需要的DIR结构。
telldir和seekdir函数不是基本POSIX.1标准的部分,是SUS的XSI扩展。
ls命令的基本实现部分使用了两个函数 opendir和readdir。
函数chdir、fchdir和getcwd
每个进程都有一个当前工作目录,此目录是搜索所有相对路径的起点。当用户登录到UNIX系统时,其当前工作目录通常是口令文件(/etc/passwd)中该登录项的第6个字段——用户的起始目录(home directory)。当前工作目录是进程的一个属性,起始目录则是登录名的一个属性。
进程调用chdir或fchdir函数可以更改当前工作目录。
#include <unistd.h> int chdir(const char *pathname); int fchdir(int fd);
我们需要一个函数,它从当前工作目录(.)开始,用…找到其上一级目录,然后读其目录项,直到该目录项的i节点与工作目录的i节点编号相同,这样就找到其对应的文件名。按照这种方法,逐层上移,直到根目录,这样就得到当前目录的完整的绝对路径。getcwd提供这样的功能。
#include <unistd.h> char *getcwd(char *buf, size_t size)
必须由此函数传递两个参数,一个是缓冲地址buf,另一个是缓冲区的长度size。该缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止null字节,否则返回出错。