第四章:文件和目录
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);
一旦给出pathname,stat函数就返回与此命名文件有关的信息结构。
fstat函数获得已在描述符fd上打开文件有关信息。
lstat函数类似于stat,但是当命名文件是一个符号链接是,lstat返回该符号衔接有关信息,而不是由
符号衔接引用的文件信息。
fstatat函数为一个相对于当前打开目录(由fd参数指定)的路径名返回文件统计信息。
flag参数控制着是否跟随着一个符号衔接。
当AT_SYMLINK_NOFOLLOW标志被设置时,fstatat不会跟随符号衔接,而是返回符号衔接本身的信息,
否则,默认情况下,返回的是符号衔接所指向的实际文件信息。
如果fd参数的值是AT_FDCWD,并且pathname是一个相对路径名,fstatat会计算相对于当前目录的
pathname参数。如果pathname是一个绝对路径名,fd参数就会被忽略。
第二个参数buf是一个指针,它指向一个我们必须提供的结构。函数用来填充由buf指向的结构。结构的
实际定义可能随具体实现有所不同,但其基本形式是:
struct stat{
mode_t st_mode;//文件类型和mode(权限)
ino_t st_ino;//索引节点号(序列号)
dev_t st_dev;//设备号(文件系统)
dev_t st_rdev;//特殊文件的设备编号
nlink_t st_nlink;//衔接数
uid_t st_uid;//使用者ID号
gid_t st_gid;//用户组ID
off_t st_size;//字节大小,用于常规文件。
struct timespec st_atime;//最后访问时间
struct timespec st_mtime;//最后修改时间
struct timespec st_ctime;//最后状态改变的时间。
blksize_t st_blksize;//最大的I/O的块大小
blkcnt_t st_blocks;//分配的磁盘块的数量
};
使用stat最多的地方就是ls -l命令,获取有关文件所以信息
文件类型:
stat结构中的st_mode成员:
S_ISREG() 普通文件
S_ISDIR() 目录文件
S_ISCHR() 字符特殊文件
S_ISBLK() 块特殊文件
S_ISFIFO() 管道或FIFO
S_ISLNK() 符号衔接
S_ISSOCK() 套接字
stat结构中取定IPC对象的类型:在<sys/stat.h>
S_TYPEISMQ() 消息队列
S_TYPEISSEM() 信号量
S_TYPEISSHM() 共享存储对象
示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>//用于stat结构
int main(int argc,char *argv[]){
int i;
struct stat buf;
char *ptr;
//需要给与程序额外的参数
for(i=1;i<argc;i++){
printf("%s: ",argv[i]);
//lstat函数获得已在描述符fd上打开文件有关信息
//它能返回符号衔接,而stat不能
if(lstat(argv[i],&buf)<0){
printf("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_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\n",ptr);
}
exit(0);
}
实际上st_mode成员判断文件为:
例如:S_ISDIR
#define S_ISDIR (mode) (((mode) & S_IFMT) == S_IFDIR)
在早期的版本中就是将st_mode与屏蔽字S_IFMT进行逻辑与运算,然后与名为S_IF***的常量比较
设置用户ID和设置组ID:
stat中的S_ISUID和S_ISGID
文件访问权限:
st_mode屏蔽 含义
S_IRUSR 用户读
S_IWUSR 写
S_IXUSR 执行
S_IRGRP 用户组读
S_IWGRP 写
S_IXGRP 执行
S_IROTH 其他读
S_IWOTH 写
S_IXOTH 执行
chmod用于修改这9个权限位。
u表示:用户(所有者)
g表示:组
o表示:其他
有效ID和实际ID:
1、实际用户ID和实际用户组ID:标识我是谁。
也就是登录用户的uid和gid,比如我的Linux以simon登录,
在Linux运行的所有的命令的实际用户ID都是simon的uid,
实际用户组ID都是simon的gid(可以用id命令查看)。
2、有效用户ID和有效用户组ID:进程用来决定我们对资源的访问权限。
一般情况下,有效用户ID等于实际用户ID,有效用户组ID等于实际用户组ID。
当设置-用户-ID(SUID)位设置,则有效用户ID等于文件的所有者的uid,
而不是实际用户ID;同样,如果设置了设置-用户组-ID(SGID)位,则有效
用户组ID等于文件所有者的gid,而不是实际用户组ID。
新文件和目录的所有权:
新文件的用户ID设置为进程的有效用户ID,而组ID如下:
1、新文件的组ID可以是进程的有效组ID
2、新文件的组ID可以是它所在目录的组ID
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
其中若测试文件是否存在,mode就为F_OK;否则按下列常量的按位或:
mode 说明
R_OK 测试读权限
W_OK 写
X_OK 执行
faccessat和access两种情况相同:
1、pathname参数为绝对路径
2、fd参数为AT_FDCWD而pathname参数为相对路径,否则faccessat计算相对于打开
目录(fd参数指向)的pathanme。
flag参数可以用于改变faccessat的行为,如果flag设置为AT_EACCESS,访问检查是调用进程的
有效用户ID和有效组ID,而不是实际用户ID和实际组ID。
access函数示例:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc,char *argv[]){
if(argc != 2)
printf("usage:a.out <pathname>");
if(access(argv[1],R_OK)<0)
printf("access error for %s \n",argv[1]);
else
printf("read access OK\n");
if(open(argv[1],O_RDONLY)<0)
printf("open error for %s \n",argv[1]);
else
printf("open for reading Ok\n");
exit(0);
}
umask函数:为进程设置文件模式创建屏蔽字,并返回之前的值.(这是少数几个没有出错返回函数中的一个)。
#include <sys/stat.h>
mode_t umask(mode_t cmask);
其中的cmask参数是9个常量(S_IRUSR、S_IWUSR)等,按位或构成的。
在文件模式创建屏蔽字中为1的位,在文件mode中的相应位一定被关闭。
下列中第一个,umask值为0,创建第二个时,umask禁止所以组和其他用户的访问权限:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)
int main(int argc,char *argv[]){
umask(0);
if(creat("foo",RWRWRW)<0)
printf("creat error for foo");
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if(creat("bar",RWRWRW)<0)
printf("creat error for bar");
exit(0);
}
测试:
umask
ls -l foo bar
umask
用户可以设置umask值以控制他们所创建文件的默认权限。该值表示成八进制数,一位代表一种要屏蔽的权限。
设置了相应位后,它所对应的权限就会被拒绝。
常用的几种umask值是002、022和027.
(002:第一位是所有者,第二位是所属组,第三位是其他用户)
(4:读 2:写 1:执行)
002阻止其他用户写入你的文件。
022阻止同组成员和其他用户写入你的文件。
027阻止同组成员写你的文件以及其他用户读、写或者执行你的文件
屏蔽位 含义
0400 用户读
0200 用户写
0100 用户执行
0040 组读
0020 组写
0010 组执行
0004 其他读
0002 其他写
0001 其他执行
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函数则对已打开的文件进行操作
fchmodat函数与chmod函数在下面两种情况下是相同的:
1、pathname参数为绝对路径
2、fd参数取值为AT_FDCWD,而pathname参数位相对路径,否则fchmodat计算相对于打开目录(由fd参数指向)
的pathname。
(当fd是AT_FDCWD的时候,表明是基于当前工作目录下的pathname)
flag参数可以用于改变fchmodat的行为,当设置了AT_SYMLINK_NOFOLLOW标志时,fchmodat并不会跟随符号
衔接。
为了改变一个文件的权限,进程的有效用户ID必须等于文件的所有者ID,或者是超级用户
参数mode是下列常量的按位或:
mode 说明
S_ISUID 执行时设置用户ID
S_ISGID 执行时设置组ID
S_ISVTX 保存正文(粘着位)
S_IRWXU 用户(所有者)读、写、执行
S_IRUSR 用户(所有者)读
S_IWUSR 用户(所有者)写
S_IXUSR 用户(所有者)执行
S_IRWXG 组读、写、执行
S_IRGRP 组读
S_IWGRP 组写
S_IXGRP 组执行
S_IRWXO 其他读、写、执行
S_IROTH 其他读
S_IWOTH 其他写
S_IXOTH 其他执行
演示umask函数:
ls -l foo bar
粘着位:(S_ISVTX)
如果一个可执行程序文件的这一位被设置了,那么当该程序第一次被执行,在终止时,程序正文部分的
一个副本仍被保存在交换区(程序的正文部分时机器指令)。这使得下次执行该程序时能较快地将其装载
入内存。
如果一个目录被设置了粘着位,只有对该目录具有写权限的用户并且满足下列条件之一,才能删除或重
命令该目录下的文件:
1、拥有此文件
2、拥有此目录
3、是超级用户
目录/tmp和/var/tmp是设置粘着位的典型候选者---任何用户都可在这两个目录中创建文件。任一用户
对这两个目录的权限通常是读、写和执行。但是不应能删除或重命名属于其他人的文件,为此在这两个目录
的文件模式中都设置了粘着位。
chown、fchown、fchownat和lchown:更改用户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。
除了引用的文件是符号衔接之外,这4个函数的操作类似。在符号衔接情况下,lchown和fchownat
(设置了AT_SYMLINK_NOFOLLOW标志)更改符号衔接本身的所有者,而不是该符号衔接所指向的文件的所有者。
fchown函数改变fd参数指向的打开文件的所有者,既然它在一个已打开的文件上操作,就不能用于改变符号衔接
的所有者。
fchownat函数与chown或者lchown函数在下面有两种情况下是相同的:
1、pathname参数为绝对路径
2、fd参数取值为AT_FDCWD而pathname参数为相对路径
在这两种情况下,
如果flag参数设置了AT_SYMLINK_NOFOLLOW标志,fchownat与lchown行为相同,
如果flag参数清除了AT_SYMLINK_NOFOLLOW标志,则fchownat与chown行为相同。
如果fd参数设置为打开目录的文件描述符,并且pathname参数是一个相对路径名,fchownat函数计算
相对于打开目录的pathname。
文件长度:
stat结构成员st_size表示已字节为单位的文件长度。此字段只对普通文件、目录文件和符号衔接有意义。
文件中的空洞:
空洞是由所设置的偏移量超过文件尾端,并写入某些数据后造成的。
文件截断:
有时候我们需要在文件尾端截去一些数据已缩短文件。将一个文件的长度截断为0是一个特例,在打开文件时
使用O_TRUNC标志可以做到这一点。为了截断文件爱你可以调用函数truncate和ftruncate。
#include <unistd.h>
int truncate(const char *pathname,off_t length);
int ftruncate(int fd,off_t length);
这两个函数将一个现有文件长度截断为length。如果该文件以前的长度大与length,则超过length以外的
数据就不再能访问。如果以前的长度小于length,文件长度增加,在以前的文件尾端和新的文件尾端只见的数据
将读作0(也就是可能在文件中创建了一个空洞)。
文件系统:
主要的信息:
1、每个i节点中都有一个衔接计数,其值是指向该i节点的目录项数。只有当衔接计数减少到0时,才可删除该文件
(也就是可以释放该文件占用的数据块)。这就是为什么“解除一个文件的衔接”操作并不总是意味着“释放该文件
占用的磁盘块”的原因。在stat结构中,衔接计数包含在st_nlink成员中,其基本系统数据类型时nlink_t.这种
衔接类型是硬衔接。LINK_MAX指定了一个文件衔接数的最大值。
2、另外一种衔接类型称为符号衔接。符号衔接文件的实际内容(在数据块中)包含了该符号衔接所指向的文件的名字
例如:
rwxrwxrwx 1 root 7 sep 25 07:14 lib->usr/lib
该i节点中的文件类型是S_IFLNK,于是系统知道这是一个符号衔接。
3、i节点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度、指向文件数据块的指针等。
stat结构中的大多数信息都取自i节点。只有两项重要数据存放在目录项中:文件名和i节点编号。其他的
数据项(如文件名长度和目录记录长度)。i节点编号的数据类型是ino_t.
4、因为目录项中的i节点编号指向同一文件系统中的相应i节点,一个目录项不能指向另一个文件系统的
i节点。这就是为什么ln命令(构造一个指向一个现有文件的新目录项)不能跨越文件系统的原因。
5、当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并为移动,只需构造一个指向现有
i节点的新目录项,并删除老的目录项。衔接计数不会改变。例如,为将文件/usr/lib/foo重命名为/usr/foo
如果目录/usr/lib和/usr在同一文件系统中,则文件foo的内容无需移动。这就是mv命令的通常操作方式。
注意在父目录中的每一个子目录都使该父目录的衔接计数增加1
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);
两个函数:若为0,成功,否则为-1,失败
这两个函数创建一个新目录项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 unlinkat(int fd,const char *pathname,int flag);
两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数删除目录项,并将由pathname所引用文件的衔接计数减1。如果对该文件还有其他衔接,则仍可通
过其他衔接访问该文件的数据,如果出错,则部队该文件做任何更改。
我们在前面已经提及,为了解除对文件的衔接,必须对包含该目录项的目录具有写和执行权限。如果对该目录
设置了粘着位,则对该目录必须具有写权限,并且具备下面3个条件之一:
1、拥有该文件
2、拥有该目录
3、具有超级用户权限
只有当衔接计数达到0时,该文件的内容才可被删除。另一个条件也会阻止删除文件的内容---只要有进程打开了该
文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程个数;如果这个计数达到0,内容再去
检查其衔接计数;如果计数也是0,那么就删除该文件的内容。
如果pathname参数是相对路径名,那么unlinkat函数计算相对于由fd文件描述符参数代表的目录的路径名。如果
fd参数设置了AT_FDCWD,那么通过相对于调用进程的当前工作目录来计算路径名。如果pathname参数是绝对路径名
那么fd参数被忽略。
flag参数给出了一种方法,是调用进程可以改变unlinkat函数的默认行为。当AT_REMOVEDIR标志被设置时,
unlinkat函数可以类似于rmdir一样删除目录。如果这个标志被清除,unlinkat与unlink执行同样的操作。
我们可以使用remove函数解除对一个文件或目录的衔接。
对于文件,remove的功能与unlink相同。
对于目录,remove的功能与rmdir相同。
#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);
两个函数的返回值:若成功,返回0;若出错,返回-1。
根据oldname指的文件、目录还是符号链接,由几种情况:
1、如果oldname指的是一个文件而不是目录,那么为该文件或者符号衔接重命名。在这种情况下,如果newname
已存在,则它不能引用一个目录。如果nawname已存在,而且不是一个目录,则先将该目录项删除然后将oldname
重命名为newname。对包含oldname的目录以及包含newname的目录,调用进程必须具有写权限,因为将更改这
两个目录。
2、如果oldname指的是一个目录,那么为该目录重命名。如果newname已存在,则它必须引用一个目录,而且该
目录应当是空目录(空目录指的是该目录中只有.和..项)。如果newname存在(并且是一个空目录),则先将其
删除,然后将oldname重命名为newname。另外,当为一个目录重命名时,newname不能包含oldname作为其
路径前缀。例如,不能将/usr/foo重命名为/usr/foo/testdir,因为旧名字(/usr/foo)是新名字的路径前缀
因而不能将其删除。
3、如若oldname或者newname引用符号衔接,则处理的是符号衔接本身,而不是它所引用的文件
4、不能对.和..重命名。就不说,不能让.和..出现在oldname和newname最后的部分。
5、作为一个特例,如果oldname和newname引用同一个文件,则函数不做任何更改返回成功。
renameat函数:
如果oldname参数指定了一个相对路径,就相对于oldfd参数引用的目录来计算oldname。
类似的,如果newname指定了相对路径,就相对于newfd引用的目录来计算newname。
newfd和oldfd参数都能设置成AT_FDCWD,此时相对于当前目录来计算相应的路径名。
符号衔接:
它是对一个文件的间接的指针。
它与硬衔接不同,硬衔接直接指向文件的i节点。引入符号衔接的原因是为了避开硬衔接的一些限制。
1、硬衔接通常要求衔接和文件位于同一文件系统中
2、只有超级用户才能创建指向目录的硬衔接(在底层文件系统支持的情况下)。
对符号衔接以及它指向何种对象并无任何文件系统限制,任何用户都可以创建指向目录的符号衔接。
符号衔接一般用于将一个文件或整个目录结构移到系统中的另外一个位置。
例如:
mkdir foo
touch foo/a
ln -s ../foo foo/testdir
ls -l foo
另外一个例子:
ln -s /no/such/file myfile 并不要求/no/such/file存在
ls myfile
cat myfile
ls -l myfile
创建和读取符号衔接:
可以使用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的新目录项sympath。在创建此符号衔接时,并不要求
actualpath已经存在,并且actualpath和sympath并不需要为与同一文件系统中。
symlinkat函数和symlink函数类似,但sympath参数根据相对于打开文件描述符引用的目录(由fd参数指定)进行
计算。如果sympath参数指定的是绝对路径或者fd参数设置了AT_FDCWD值,那么symlinkat就等同于symlink函数
因为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字节终止。
当pathname参数指定的是绝对路径名或者fd参数的值为AT_FDCWD,readlinkat函数的行为和readlink相同。
但是,如果fd参数是一个打开目录的有效文件描述符并且pathname参数是相对路径名,则readlinkat计算相对于
由fd代表的打开目录的路径名。
文件的时间:
对每个文件维护3个时间字段,他们的意义:
字段 说明 例子 ls(l)选项
st_atim 文件数据的最后访问时间 read -u
st_mtim 文件数据的最后修改时间 write 默认
st_ctim i节点状态的最后更改时间 chmod、chown -c
注意,修改时间(st_mtim)和状态更改时间(st_ctim)之间的区别。
修改时间是文件内容最后一次被修改的时间。
状态更改时间是该文件的i节点最后一次被修改的时间。
影响到i节点的操作,如更改文件的访问权限、更改用户ID、更改衔接数等,但它们并没有更改文件的实际内容。
系统并不维护对一个i节点的最后一次访问时间,所以access和stat函数并不更改这3个时间中的任一个。
系统管理员常常使用访问时间来删除在一定时间范围内没有被访问过的文件。典型的例子就是删除在过去一周内没有
访问过的名为a.——ut或core的文件。find()命令常被用来进行这种类型的操作。
修改时间和状态更改时间可被用来归档那些内容已经被修改或i节点已经被更改的文件。
ls命令按这3个时间值中的一个排序进行显示的。系统默认(用-l或-t选项调用时)是按文件的修改时间的先后排序
显示。-u选项使ls命令按访问时间排序,-c选项则使其状态更改时间排序。
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 *pathname,const struct timespec times[2],int flag);
返回值:若成功,返回0,若出错,返回-1。
struct timespec{
__time_t tv_sec; /*seconds 秒*/
long int tv_nsec; /*nanoseconds 纳秒*/
}
这两个函数的times数组参数的第一个元素包含访问时间,第二个包含的是修改时间。
这两个时间值是日历时间。
时间戳可以按下列4种方式之一进行指定:
1、如果times参数是一个空指针,则访问时间和修改时间两者都设置为当前时间
2、如果times参是是指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值
为UTIME_NOW,相应的时间戳就设置为当前时间,忽略相应的tv_sec字段。
3、如果times参数指向两个timespec结构的数组,任一数组元素的tv_nsec字段的值
为UTIME_OMIT,相应的时间戳保持不变,忽略相应的tv_sec字段。
4、如果times参数指向两个timespec结构的数组,且tv_nsec字段的值不是UTIME_NOW
和UTIME_OMIT,在这种情况下,相应的时间戳设置为相应的tv_sec和tv_nsec字段的值
执行这些函数所要求的优先权取决于times参数的值。
1、如果times是一个空指针,或者任一tv_nsec字段设为UTIME_NOW,则进程的有效用户ID
必须等于该文件的所有者ID;进程对该文件必须具有写权限,或者进程是一个超级用户进程。
2、如果times是一个空指针,并且任一tv_nsec字段不是UTIME_NOW也不是UTIME_OMIT,
则进程的有效用户ID必须等于该文件的所有者ID,或者进程必须是一个超级用户进程。对文件
具有写权限是不够的。
3、如果times是非空指针,并且两个tv_nsec字段的值都为UTIME_OMIT,就不执行任何的权限
检查。
futimens函数需要打开文件来更改它的时间,utimensat函数提供了一种使用文件名更改文件时间的方法。
pathname参数是相对于fd参数进行计算的,fd要么是打开目录的文件描述符,要么设置为特殊值AT_FDCWD
(强制通过相对于调用进程的当前目录计算pathname)。如果pathname指定了绝对路径,那么fd参数被忽略。
utimesat的flag参数可用于进一步修改默认行为。如果设置了AT_SYMLINK_NOFOLLOW标志,则符号衔接
本身的时间就会被修改(如果路径名指向符号衔接)。默认的行为是跟随符号衔接,并把文件的时间改成符号
衔接的时间。
futimens和utimensat函数都包含在POSIX.1中,第三个参数utimes包含在Single UNIX Specification
中:
#include <sys/time.h>
int utimes(const char *pathname,const struct timeval times[2]);
函数的返回值:若成功,返回0,若失败,返回-1
utimes函数对路径名进行操作。times参数是指向包含两个时间戳(访问时间和修改时间)
元素的数组的指针,两个时间戳是用秒和微妙表示的。
struct timeval
{
__time_t tv_sec; /* 秒。 */
__suseconds_t tv_usec; /* 微秒。 */
};
注意,我们不能对状态更改时间st_ctim(i节点最近被修改的时间)指定一个值,因为调用utimes
函数时,此字段会被自动更新。
下列:
程序使用带O_TRUNC选项的open函数将文件长度截断为0,当并不更改其访问时间和修改时间。
为了做到这一点,首先用stat函数得到这些时间,然后截断文件,最后再用futimens函数重置
这两个时间。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
int main(int argc,char *argv[])
{
int i,fd;
struct stat statbuf;
struct timespec times[2];
for(i=1;i<argc;i++){
if(stat(argv[i],&statbuf)<0){
printf("%s: stat error",argv[i]);
continue;
}
if((fd=open(argv[i],O_RDWR | O_TRUNC))<0){
printf("%s: open error",argv[i]);
}
times[0]=statbuf.st_atim;
times[1]=statbuf.st_mtim;
if(futimens(fd,times)<0){
printf("%s: futimens error",argv[i]);
}
close(fd);
}
exit(0);
}
执行:
touch times
ls -l times 查看长度和最后修改时间
ls -lu times 查看最后访问时间
date 当前时间
./a times 执行
ls -l times 检查结果
ls -lu times 检查最后访问时间
ls -lc times 检查状态更改时间
如果正确:最后修改时间和最后访问时间未变。但是状态更改时间则更改为程序运行时的时间。
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。
这两个函数创建一个新的空目录。其中.和..目录项是自动创建的。所指定的文件访问权限mode由进程的
文件模式创建屏蔽字修改。
常见的错误是指定与文件相同的mode(指定读、写权限)。但是,对目录目录通常至少要设置一个执行权限为,以
允许访问该目录中的文件名。
在早期版本中,进程要调用mknod函数创建一个新目录,但是只有超级用户进程才能使用mknod函数。为了避免
这一点,创建目录的命令mkdir必须由跟用户拥有,而且对它设置了设置用ID位。要通过一个进程创建一个目录
必须用system函数调用mkdir命令。
mkdirat函数与mkdir函数类似。当fd参数具有特殊值AT_FDCWD或者pathname参数指定了绝对路径名时,
mkdirat与mkdir完全一样。否则,fd参数是一个打开目录,相对路径名根据此打开目录进行计算。
用rmdir函数可以删除一个空目录。空目录是只包含.和..这两项的目录。
#include <unistd.h>
int rmdir(const char *pathname);
如果调用此函数使目录的衔接计数成为0,并且也没有其他进程打开此目录,则释放由此目录占用的空间。
如果在衔接数达到0时,有一个或多个进程打开此目录,则在此函数返回前删除最后一个衔接及.和..项。
另外,在此目录中不能再创建新文件。但是在最后一个进程关闭它之前并不释放此目录。
即使一些进程打开该目录,它们在此目录下也不能执行其他操作。这样处理的原因时,为了使rmdir函数
成功执行,该目录必须时空的。
读目录:
对某个目录具有访问权限的任一用户都可以读该目录,但是,为了防止文件系统产生混乱,只有内核才能
写目录。一个目录的写权限位和执行权限位决定了在该目录中能否创建新文件以及删除文件,它们并不
代表能够写目录本身。
在早期的文件系统中:每个目录项时16个字节,其中14个字节时文件名,2个字节时i节点编号。
#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);
fdopendir函数最早出现在SUSv4中,它提供了一种方法,可以把打开文件描述符转换为目录处理函数需要的DIR结构。
telldir和seekdir函数不是基本POSIX.1标准的组成部分。特门是Single UNIX Specification中的XSI扩展,
所以可以期望所以符合UNIX系统的实现都会提供这两个函数。
定义在头文件<dirent.h>中的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) 文件名,最长256字符 */
}
相关函数
opendir(),readdir(),closedir();
编写一个遍历文件层次结构的程序,其目的是得到各种类型的文件计数。
Solaris提供了一个遍历此层次结构的函数ftw(3),对于每一个文件它都调用一个用户定义的函数。
ftw函数的问题是:对于每一个文件,它都调用stat函数,这就是使程序跟随符号衔接。
例如,如果从根目录(root)开始,并且有一个名为/lib的符号衔接,它指向/ust/lib,则所有在目录
/usr/lib中的文件都会被计数两次。为了纠正就一点,Solaris提供了另一个函数nftw(3),它具有一个
停止跟随符号衔接的选项。
#include <unistd.h>
#include <dirent.h>
#include <limits.h>
#include <sys/stat.h>
#include <stdlib.h>
#include <stdio.h>
char *path_alloc(size_t size)
{
if (size == 0)
return (char *)malloc(PATH_MAX);
else
return (char *)malloc(size);
}
typedef int Myfunc(const char *,const struct stat *,int);
static Myfunc myfunc;
static int myftw(char *,Myfunc *);
static int dopath(Myfunc *);
static long nreg,ndir,nblk,nchr,nfifo,nslink,nsock,ntot;
int main(int argc,char *argv[])
{
int ret;
if(argc!=2)
printf("usage: ftw <starting-pathname>");
ret=myftw(argv[1],myfunc);
ntot=nreg+ndir+nblk+nchr+nfifo+nslink+nsock;
if(ntot==0)
ntot=1;
printf("regular files= %7ld, %5.2f %%\n",nreg,nreg*100.0/ntot);
printf("directories = %7ld, %5.2f %%\n",ndir,ndir*100.0/ntot);
printf("block special = %7ld, %5.2f %%\n",nblk,nblk*100.0/ntot);
printf("char special = %7ld, %5.2f %%\n",nchr,nchr*100.0/ntot);
printf("FIFOS = %7ld, %5.2f %%\n",nfifo,nfifo*100.0/ntot);
printf("symboloc links = %7ld, %5.2f %%\n",nslink,nslink*100.0/ntot);
printf("sockets = %7ld, %5.2f %%\n",nsock,nsock*100.0/ntot);
exit(ret);
}
#define FTW_F 1
#define FTW_D 2
#define FTW_DNR 3
#define FTW_NS 4
static char *fullpath;
static size_t pathlen;
static int myftw(char *pathname,Myfunc *func)
{
fullpath=path_alloc(&pathlen);
if(pathlen<=strlen(pathname))
{
pathlen=strlen(pathname)*2;
if((fullpath=realloc(fullpath,pathlen))==NULL)
printf("realloc failed");
}
strcpy(fullpath,pathname);
return(dopath(func));
}
static int dopath(Myfunc *func)
{
struct stat statbuf;
struct dirent *dirp;
DIR *dp;
int ret,n;
if(lstat(fullpath,&statbuf)<0)
return(func(fullpath,&statbuf,FTW_NS));
if(S_ISDIR(statbuf.st_mode)==0)
return(func(fullpath,&statbuf,FTW_F));
if((ret=func(fullpath,&statbuf,FTW_D))!=0)
return(ret);
n=strlen(fullpath);
if(n+NAME_MAX+2>pathlen){
pathlen*=2;
if((fullpath=realloc(fullpath,pathlen))==NULL)
printf("realloc failed");
}
fullpath[n++]='/';
fullpath[n]=0;
if((dp=opendir(fullpath))==NULL)
return(func(fullpath,&statbuf,FTW_DNR));
while((dirp=readdir(dp))!=NULL){
if(strcmp(dirp->d_name,".")==0 || strcmp(dirp->d_name,"..")==0)
continue;
strcpy(&fullpath[n],dirp->d_name);
if((ret=dopath(func))!=0)
break;
}
fullpath[n-1]=0;
if(closedir(dp)<0)
printf("can't close directory %s",fullpath);
return(ret);
}
static int myfunc(const char *pathname,const struct stat *statptr,int type)
{
switch(type){
case FTW_F:
switch(statptr->st_mode & S_IFMT){
case S_IFREG: nreg++;break;
case S_IFBLK: nblk++;break;
case S_IFCHR: nchr++;break;
case S_IFIFO: nfifo++;break;
case S_IFLNK: nslink++;break;
case S_IFSOCK: nsock++;break;
case S_IFDIR: printf("for S_IFDIR for %s",pathname);
}
break;
case FTW_D:
ndir++;
break;
case FTW_DNR:
printf("can't read directory %s",pathname);
break;
case FTW_NS:
printf("stat error for %s",pathname);
default:
printf("unknown type %d for pathname %s",type,pathname);
}
return(0);
}
chdir、fchdir和getcwd函数:
每个进程都有一个当前工作目录,此目录是搜索所有相对路径名的起点(不以斜线开始的路径名为相对路径名)。
当用户登陆到UNIX系统的时候,其当前工作目录通常是口令文件(/etc/passwd)中该用户登陆项的第6个字段
----用户的起始目录(home directory)。当前工作目录是进程的一个属性,起始目录则是登陆名的一个属性。
进程调用chdir或fchdir函数可以更改当前工作目录:
#include <unistd.h>
int chdir(const char* pathname);
int fchdir(int fd);
在这两个函数中,分别用pathname或打开文件描述符来指定新的当前工作目录。
因为当前工作目录是进程的一个属性,所以它只影响调用chdir的进程本身,而不影响其他进程。这就意味者程序并
不会产生我们可能希望得到的结果。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
if(chdir("/tmp")<0)
printf("chdir failed");
printf("chdir to /tmp successed\n");
exit(0);
}
执行:
pwd
./chdir
pwd
从上可以看出,执行./chdir命令的shell当前工作目录并没有改变,这是shell执行程序工作方式的一个副作用。
每个程序运行在独立的进程中,shell的当前工作目录并不会随着程序调用chdir而改变。由此可见,为了该改变
shell进程自己的工作目录,shell应当直接调用chdir函数,为此,cd命令内建在shell中。
因为内核必须维护当前工作目录的信息,所以我们应能获取其当前值。遗憾的是,内核为每个进程只保存指向该目录
v节点的指针等目录本身的信息,并不保存该目录的完整路径名。
获得当前目录完整的绝对路径名:
#include <unistd.h>
char *getcwd(char *buf,size_t size);
必须向此函数传递两个参数,一个是缓冲区地址buf,另一个是缓冲区的长度size。
这缓冲区必须有足够的长度以容纳绝对路径名再加上一个终止null字节,否则返回出错。
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#define PATH_MAX 100
char *path_alloc(size_t size)
{
if(size==0)
return (char*)malloc(PATH_MAX);
else
return (char*)malloc(size);
}
int main()
{
char *ptr;
size_t size;
if(chdir("/usr/spool/uucppublic")<0)
printf("chdir failed");
ptr=path_alloc(size);
if(getcwd(ptr,size)==NULL)
printf("getcwd failed");
printf("cwd=%s\n",ptr);
exit(0);
}
一般要返回到出发点是,先使用getcwd函数保存其值,再完成处理后,就可以将保存的原工作路径名作为调用参数传
送给chdir。
而fchdir,它是使用open打开当前工作目录,然后保存其返回的文件描述符。当希望回到原工作目录时,只要简单地
将该文件描述符传送给fchdir。
设备特殊文件:
st_dev和st_rdev这两个字段经常引起混淆。我们编写ttyname函数时,需要使用这两个字段。
1、每个文件系统所在的储存设备都由其主、次设备号表示。设备号所用的数据类型时基本系统数据类型dev_t。主设备号
标识设备驱动程序,由是编码为与其通信的外设板;此设备号标识特定的子设备。一个磁盘驱动器经常包含若干个文件系统。
在同一磁盘驱动器上的各文件系统通常具有相同的主设备号,但是次设备号却不同。
2、我们通常使用两个宏:major和minor来访问主、次设备号,大多数实现都定义这两个宏。这就意味着我们无需关心这
两个数是如何存放在dev_t对象中。
3、系统中与每个文件名关联的st_dev值是文件系统的设备号,该文件系统包含了这一文件名以及与其对应的i节点。
4、只有字符特殊文件和块特殊文件才有st_rdev值。此值包含实际设备的设备号。
程序为每个命令行参数打印设备号,另外,若此参数引用的是字符特殊文件或者块特殊文件,则还打印该特殊文件的
st_rdev值。
例子:
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>//用于使用stat结构
#ifdef SOLARIS //solaris在<sys/mkdev.h>中定义了它们的函数原型。
#include <sys/mkdev.h>
#endif
int main(int argc,char *argv[])
{
int i;
struct stat buf;
for(i=1;i<argc;i++){
printf("%s:",argv[i]);
if(stat(argv[i],&buf)<0)
{
printf("stat error");
continue;
}
//其中的major是访问主设备号,minor是访问次设备号
printf("dev=%d/%d",major(buf.st_dev),minor(buf.st_dev));//文件系统设备号
//如果是字符特殊文件或者块设备文件,它们由st_rdev值
//st_rdev;//特殊文件的设备编号
if(S_ISCHR(buf.st_mode) || S_ISBLK(buf.st_mode))
{
printf(" (%s) rdev=%d/%d",(S_ISCHR
(buf.st_mode))?"character":"block",major(buf.st_rdev),minor(buf.st_rdev));
}
printf("\n");
}
exit(0);
}
执行:
./dev / /home
文件访问权限位小结:
S_IRWXU=S_IRUSR | S_IRWUSR | S_IRXUSR
S_IRWXG=S_IRGRP | S_IRWGRP | S_IRXGRP
S_IRWXO=S_IROTH | S_IRWOTH | S_IRXOTH
常量 说明 对普通文件的影响 对目录文件的影响
S_ISUID 设置用户ID 执行设置有效用户ID 无
S_ISGID 设置组ID 若组位设置,则执行时 将在目录中创建的新的
设置有效组ID,否则使 文件组ID设置为目录组ID
强制性所起作用(若支持)
S_ISVTX 粘着位 在交换缓存程序正文(若支持) 限制目录中删除和重命名文件
S_IRUSR 用户读 用户可读文件 用户可读目录
S_IWUSR 用户写 用户可写文件 用户可删除和创建文件
S_IXUSR 用户执行 用户可执行文件 用户可在目录中搜索给定路径名
S_IRGRP 组读 组可读文件 组可读目录
S_IWGRP 组写 组可写文件 组可删除和创建文件
S_IXGRP 组执行 组可执行文件 组可搜索给定路径名
S_IROTH 其他读 其他读文件 其他可读目录
S_IWOTH 其他写 其他写文件 其他可删除和创建文件
S_IXOTH 其他执行 其他执行文件 其他可在目录中搜索给丁路径名
文件和目录全解
最新推荐文章于 2023-04-05 21:15:07 发布