函数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 *restrict buf, int flag);
/*若成功,返回0,若出错,返回-1*/
restrict是c99标准引入的,它只可以用于限定和约束指针,并表明指针是访问一个数据对象的唯一且初始的方式。即它告诉编译器,所有修改该指针所指向内存中内容的操作都必须通过该指针来修改,而不能通过其它途径(其它变量或指针)来修改;这样做的好处是,能帮助编译器进行更好的优化代码,生成更有效率的汇编代码.如 int *restrict ptr, ptr 指向的内存单元只能被 ptr 访问到,任何同样指向这个内存单元的其他指针都是未定义的,直白点就是无效指针。restrict 的出现是因为 C 语言本身固有的缺陷,C 程序员应当主动地规避这个缺陷,而编译器也会很配合地优化你的代码。
数据结构struct stat的定义随具体的实现有所不同,在GUN C LIB中,结构体的定义为:
struct stat
{
__dev_t st_dev; /* Device. */
#if __WORDSIZE == 64 || !defined __USE_FILE_OFFSET64
unsigned short int __pad1;
__ino_t st_ino; /* File serial number. */
#else
__ino64_t st_ino; /* File serial number. */
#endif
__mode_t st_mode; /* File mode. */
__nlink_t st_nlink; /* Link count. */
__uid_t st_uid; /* User ID of the file's owner. */
__gid_t st_gid; /* Group ID of the file's group.*/
__dev_t st_rdev; /* Device number, if device. */
unsigned short int __pad2;
#ifndef __USE_FILE_OFFSET64
__off_t st_size; /* Size of file, in bytes. */
#else
__off64_t st_size; /* Size of file, in bytes. */
#endif
__blksize_t st_blksize; /* Optimal block size for I/O. */
#ifndef __USE_FILE_OFFSET64
__blkcnt_t st_blocks; /* Number 512-byte blocks allocated. */
#else
__blkcnt64_t st_blocks; /* Number 512-byte blocks allocated. */
#endif
#ifdef __USE_XOPEN2K8
/* Nanosecond resolution timestamps are stored in a format
equivalent to 'struct timespec'. This is the type used
whenever possible but the Unix namespace rules do not allow the
identifier 'timespec' to appear in the <sys/stat.h> header.
Therefore we have to handle the use of this header in strictly
standard-compliant sources special. */
struct timespec st_atim; /* Time of last access. */
struct timespec st_mtim; /* Time of last modification. */
struct timespec st_ctim; /* Time of last status change. */
# define st_atime st_atim.tv_sec /* Backward compatibility. */
# define st_mtime st_mtim.tv_sec
# define st_ctime st_ctim.tv_sec
#else
__time_t st_atime; /* Time of last access. */
unsigned long int st_atimensec; /* Nscecs of last access. */
__time_t st_mtime; /* Time of last modification. */
unsigned long int st_mtimensec; /* Nsecs of last modification. */
__time_t st_ctime; /* Time of last status change. */
unsigned long int st_ctimensec; /* Nsecs of last status change. */
#endif
unsigned long int __glibc_reserved4;
unsigned long int __glibc_reserved5;
};
函数stat根据pathname返回与此命名文件有关的信息结构。
函数fstat获取已在描述符fd上打开文件的有关信息结构。
函数lstat类似于stat,但是当命名的文件是一个符号链接时,lstat返回该符号链接的有关信息,而不是符号链接引用的文件信息。
函数fstatat为一个相对于当前打开目录(fd参数指向)的路径名返回文件的信息结构,flag参数控制着是否跟随一个符号链接。如果fd参数是AT_FDCWD,并且pathname是相对路径,则fstatat会计算相对于当前目录的pathname参数,如果pathname是绝对路径,则fd参数将会被忽略。
文件类型
在UNIX系统中的文件类型主要包括:
- 普通文件;
- 目录文件;
- 块特殊文件,提供对设备(如磁盘)带缓冲的访问;
- 字符特殊文件,提供对设备不带缓冲的访问;
- FIFO,用于进程间通信;
- 套接字socket,用于进程间的网络通信;
- 符号链接。
可使用在linux中的/usr/include/sys/stat.h中定义的宏判断文件类型,这些宏的参数是stat结构体中的st_mode成员。
/* Test macros for file types. */
#define __S_ISTYPE(mode, mask) (((mode) & __S_IFMT) == (mask))
#define S_ISDIR(mode) __S_ISTYPE((mode), __S_IFDIR)
#define S_ISCHR(mode) __S_ISTYPE((mode), __S_IFCHR)
#define S_ISBLK(mode) __S_ISTYPE((mode), __S_IFBLK)
#define S_ISREG(mode) __S_ISTYPE((mode), __S_IFREG)
#ifdef __S_IFIFO
# define S_ISFIFO(mode) __S_ISTYPE((mode), __S_IFIFO)
#endif
#ifdef __S_IFLNK
# define S_ISLNK(mode) __S_ISTYPE((mode), __S_IFLNK)
#endif
#if defined __USE_BSD && !defined __S_IFLNK
# define S_ISLNK(mode) 0
#endif
#if (defined __USE_BSD || defined __USE_UNIX98 || defined __USE_XOPEN2K) \
&& defined __S_IFSOCK
# define S_ISSOCK(mode) __S_ISTYPE((mode), __S_IFSOCK)
#elif defined __USE_XOPEN2K
# define S_ISSOCK(mode) 0
#endif
与进程关联的ID
与进程关联的ID主要包括:
- 实际用户ID、实际组ID,这两个字段取自口令文件中的登录项;
- 有效用户ID、有效组ID、附属组ID,该字段决定了文件的访问权限;
- 保存的设置用户ID、保存的设置组ID,在执行程序时包含了有效用户ID与有效组ID的副本。
在文件模式字(st_mode)中设置了特殊标志,其含义为“当执行此文件时,将进程的有效用户ID设置为文件所有者的用户ID(st_uid)”,同时在文件模式字(st_mode)中设置了另一个特殊标志,其含义为“当执行此文件时,将进程的有效组ID设置为文件所有者的组ID(st_gid)”
在文件模式字(st_mode)中,这两个特殊的标志称为“设置用户ID(set-user-ID)”与“设置组ID(set-group-ID)”。
文件访问权限
所有的文件类型都有访问权限。每个文件都有9个访问权限。在st_mode值中包含对文件的访问权限位。
/* Protection bits. */
#define S_ISUID __S_ISUID /* Set user ID on execution. */
#define S_ISGID __S_ISGID /* Set group ID on execution. */
#if defined __USE_BSD || defined __USE_MISC || defined __USE_XOPEN
/* Save swapped text after use (sticky bit). This is pretty well obsolete. */
# define S_ISVTX __S_ISVTX
#endif
#define S_IRUSR __S_IREAD /* Read by owner. */
#define S_IWUSR __S_IWRITE /* Write by owner. */
#define S_IXUSR __S_IEXEC /* Execute by owner. */
/* Read, write, and execute by owner. */
#define S_IRWXU (__S_IREAD|__S_IWRITE|__S_IEXEC)
#define S_IRGRP (S_IRUSR >> 3) /* Read by group. */
#define S_IWGRP (S_IWUSR >> 3) /* Write by group. */
#define S_IXGRP (S_IXUSR >> 3) /* Execute by group. */
/* Read, write, and execute by group. */
#define S_IRWXG (S_IRWXU >> 3)
#define S_IROTH (S_IRGRP >> 3) /* Read by others. */
#define S_IWOTH (S_IWGRP >> 3) /* Write by others. */
#define S_IXOTH (S_IXGRP >> 3) /* Execute by others. */
/* Read, write, and execute by others. */
#define S_IRWXO (S_IRWXG >> 3)
相关的说明为:
- 目录的执行权限位常被称为搜索位。例如打开文件/usr/include/stat.h,则需要对目录/、/usr、/usr/include具有执行权限;
- 对目录的读权限与执行权限的意义是不相同的,目录的读权限允许获取目录中所有文件名的列表,目录的执行权限允许可通过该目录,即搜索该目录,寻找一个特定的文件名;
- 为了在目录中新建文件,则需要对该目录具有写权限与执行权限;
- 为了删除目录中的文件,则需要对该目录具有写权限与执行权限,而对删除的文件本身不需要读写权限;
进程相关的ID:有效用户ID、有效组ID、附属组ID
文件相关的ID:文件所有者ID(st_uid、st_gid)
进程在访问文件时,内核进行文件访问权限测试:
- 若进程的有效用户ID为0,即是root,则允许访问文件;
- 若进程的有效用户ID等于文件的所有者ID,那么如果所有者适当的访问权限位被设置,则允许访问,否则拒绝访问;
- 若进程的有效组ID与附属组ID之一等于文件的组ID,那么如果组适当的访问权限位被设置,则允许访问,否则拒绝访问;
- 若其他用户适当的访问权限位被设置,则允许访问,否则拒绝访问。
新文件和新目录的所有权
新文件或者新目录的用户ID设置为进程的有效用户ID。
新文件或者新目录的组ID可设置为:
- 新文件或者新目录的组ID可以是进程的有效组ID;
- 新文件或者新目录的组ID可以是其所在目录的组ID;
- 对于Linux,取决于所在目录的设置组ID位是否被设置,如果被设置,则新文件或者新目录对于Linux,取决于所在目录的设置组ID位是否被设置,如果被设置,则新文件或者新目录的组ID设置为目录的组ID,否则为进程的有效组ID。
函数access和faccessat
函数access与faccessat是按照进程的实际用户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*/
当一个进程使用设置用户ID或者设置组ID作为另一个用户或者组(比如root)运行时,可能会需要验证其实际用户ID能否访问一个给定的文件,通过函数access或者faccessat即可验证。
函数umask
函数umask为进程设置文件模式创建屏蔽字。函数原型为:
#include <sys/stat.h>
mode_t umask(mode_t cmask);
/*返回之前的文件模式创建屏蔽字*/
参数cmask是由S_IRUSR、S_IWUSR、S_IXUSR、S_IRGRP等9个常量的“或”构成。
在文件模式创建屏蔽字中为1的位,其文件mode中相应的位一定被关闭。例如若cmaks为 S_IROTH | S_IWOTH,则调用umask后,创建的文件,其他读权限与其他写权限一定是关闭的。
函数chmod、fchmod和fchmodat
函数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);
/*若成功,返回0,若出错,返回-1*/
chmod函数在指定的文件上更改访问权限。
fchmod函数对已经打开的文件更改访问权限。
对于fchmodat函数:
- 若pathname为绝对路径,则fchmodat函数与chmod函数相同;
- 若fd为AT_FDCWD,而pathname为相对路径,则fchmodat函数与chmod函数相同;
- fchmodat函数计算相对于打开目录(fd参数指向)的pathname。
- flag用于改变fchmodat函数的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,不会跟随符号链接。
函数chown、fchown、fchownat和lchown
chown系列函数用于更改文件的用户ID与组ID,函数原型为:
#include <unistd.h>
int chown(const char *pathname, uid_t owner, git_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);
/*若成功,返回0,若出错,返回-1*/
若参数owner或者group为-1,则对应的ID不变。
在符号链接的情况下,lchown与fchownat(设置了AT_SYMLINK_NOFOLLOW标志)更改符合链接本身的所有者ID,而不改变符合链接指向的文件的所有者。
文件长度
在stat结构中成员st_size表示以字节为单位的文件的长度。此字段对普通文件、目录文件与符号链接有意义。
在stat结构中成员st_blksize表示字节块的大小,一般为1024字节块或者512字节块,在Linux中,若设置了环境变量POSIXLY_CORRECT,则du命令报告的是1024字节块的块数,否则是512字节块的块数。
在stat结构中成员st_blocks是所分配的st_blksize大小的字节块的块数。
文件截断
文件截断可调用的函数原型为:
#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,即可能在文件中创建了一个空洞。
文件系统
磁盘,分区与文件系统的示意图见下:
数据块与i节点的详细示意图见下:
i节点包含了文件有关的所有信息,包括文件类型,文件访问权限位,文件长度,文件数据块指针等。stat结构中大多数信息来自i节点。
在目录项中存放文件名与i节点编号,i节点编号的数据类型是ino_t。
因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的i节点。
当在不更换文件系统的情况下,为一个文件重命名时,该文件的数据块并未移动,只是构造了一个指向现有i节点的新目录项,并删除旧的目录项。
函数link、linkat
函数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已经存在,则函数返回出错。
对于linkat函数,现有文件是通过efd与existingpath参数指定,新的路径名是通过nfd与newpath参数指定。
当现有文件是符号链接时,flag参数控制linkat函数是创建指向现有符号链接的链接,还是创建指向现有符号链接所指向的文件的链接。
函数unlink、unlinkat
函数unlink与unlinkat是删除一个现有的目录项,函数原型为:
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
/*若成功,返回0,若失败,返回-1*/
函数删除目录项,并将由pathname所引用的文件的链接计数减去1。
只有当文件的链接数减为0时,该文件的内容才能被删除。
只要有进程打开了文件,则其内容也不会被删除。
函数remove
函数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 renameat(int oldfd, const char *oldname, int newfd, const char *newname);
/*若成功,返回0,若出错,返回-1*/
符合链接
符合链接是对一个文件的间接指针。
与硬链接有所区别,硬链接直接指向文件的i节点。
硬链接要求链接与文件位于同一个文件系统中,软链接不需要。
只有root才能创建指向目录的硬链接,而任何用户均可以创建指向目录的符号链接。
当使用以文件名字为引用参数的函数时,需要注意了解该函数是否处理符合链接,函数是否跟随符合链接到达其所链接的文件。
创建和读取符号链接
可以使用函数symlink与symlinkat创建一个符号链接,函数原型为:
#include <unistd.h>
int symlink(const char *actualpath, const char *sympath);
int symlinkat(const char *actualpath, int fd, const char *sympath);
/*若成功,返回0,若出错,返回-1*/
在创建符号链接时,并不要求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);
/*若成功,返回读取的字节数,若出错,返回-1*/
函数组合了open、read与close的功能,在buf中返回的符号链接的内容不以null结尾。
文件的时间
对于每个文件或者目录,维护3个时间字段:
- st_atim,文件数据的最后访问时间;
- st_mtim,文件数据的最后修改时间;
- st_ctim,文件i节点状态的最后更改时间。
注意最后修改时间与状态最后更改时间的区别。
函数futimens、utimensat与utimes
一个文件的访问时间与修改时间可通过函数进行更改,函数的原型为:
#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);
/*若成功,返回0,若出错,返回-1*/
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);
/*若成功,返回0,若出错,返回-1*/
函数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);
/*若成功,返回0,若出错,返回-1*/
#include <unistd.h>
int rmdir(const char *pathname);
/*若成功,返回0,若出错,返回-1*/
读目录
对于某个目录具有访问权限的任一个用户都可以读目录,但是只有内核才能写目录。
一个目录的写权限位与执行权限位决定了在该目录能否创建新文件和删除文件,并不表示能否写目录本身。
#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);
数据结构DIR类似于FILE,是一个内部结构,对于DIR数据结构的定义,在Linux下为:
struct __dirstream
{
void *__fd;
char *__data;
int __entry_data;
char *__ptr;
int __entry_ptr;
size_t __allocation;
size_t __size;
__libc_lock_define (, __lock)
};
typedef struct __dirstream DIR;
dirent结构体存储的关于文件的信息很少,所以dirent同样也是起着一个索引的作用,数据结构dirent的定义如下:
struct dirent
{
long d_ino; /* inode number 索引节点号 */
off_t d_off; /* offset to this dirent 在目录文件中的偏移 */
unsigned short d_reclen; /* length of this d_name 文件名长 */
unsigned char d_type; /* the type of d_name 文件类型 */
char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最长255字符 */
}
其中d_type表示档案类型,是枚举值,定义如下:
enum
{
DT_UNKNOWN = 0, //未知类型
# define DT_UNKNOWN DT_UNKNOWN
DT_FIFO = 1, //管道
# define DT_FIFO DT_FIFO
DT_CHR = 2, //字符设备
# define DT_CHR DT_CHR
DT_DIR = 4, //目录
# define DT_DIR DT_DIR
DT_BLK = 6, //块设备
# define DT_BLK DT_BLK
DT_REG = 8, //常规文件
# define DT_REG DT_REG
DT_LNK = 10, //符号链接
# define DT_LNK DT_LNK
DT_SOCK = 12, //套接字
# define DT_SOCK DT_SOCK
DT_WHT = 14 //链接
# define DT_WHT DT_WHT
};
另外,常见的stat结构体常规定义如下:
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; //以字节为单位的文件容量
time_t st_atime; //最后一次访问该文件的时间
time_t st_mtime; //最后一次修改该文件的时间
time_t st_ctime; //最后一次改变该文件状态的时间
blksize_t st_blksize; //包含该文件的磁盘块的大小
blkcnt_t st_blocks; //该文件所占的磁盘块
};
想要获取某目录下(比如a目下)b文件的详细信息:
- 首先使用opendir函数打开目录a,返回指向目录a的DIR结构体c;
- 接着调用readdir( c)函数读取目录a下所有文件(包括目录),返回指向目录a下所有文件的dirent结构体d;
- 遍历d,调用stat(d->name,stat *e)来获取每个文件的详细信息,存储在stat结构体e中。
函数chdir、fchdir和getcwd
进程调用函数chdir或者fchdir可以更改当前工作目录:
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
/*若成功,返回0,若出错,返回-1*/
函数getcwd获取当前工作目录的完整的绝对路径名:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
/*若成功,返回buf,若出错,返回NULL*/
函数的入参分别是缓冲区地址buf,以及缓冲区的长度size(以字节为单位)。该缓冲区需要足够的长度以容纳绝对路径再加上一个null字节。