|
apue第四章 文件和目录 |
|
|
apue第四章文件和目录
函数stat,fstat,fstatat,lstat
#include
intstat(constcharrestrictpathname,structstatrestrictbuf);
intfstat(intfd,structstatbuf);
intlstat(constcharrestrictpathname,structstatrestrictbuf);
intlstat(intfd,constcharrestrictpathname,structstatrestrictbuf,intflag);
/所有4个函数的返回值:若成功,返回0;若出错,返回-1/
stat:获得路径所指向的文件信息
lstat:stat类似,除了当路径指向为符号链接时,lstat返回链接本身信息,而stat返回对应的文件信息
fstat:获取已经打开的文件描述符的文件信息
fstatat:为一个相对于当前打开目录(由fd参数指向)的路径名返回文件统计信息。当AT_FDCWD传入fd参数,并且路径参数为相对路径时,不会跟随符号链接,而是返回符号链接本身的信息。如果路径为绝对路径,则fd参数被忽略。
sturctstat{
mode_tst_mode;/filetype&mode(permissions)/
ino_tst_ino;/i-nodenumber(serialnumber)/
dev_tst_dev;/devicenumber(filesystem)/
dev_tst_rdev;/devicenumberforspecialfiles/
nlink_tst_nlink;/numberoflinks/
uid_tst_uid;/userIDoflinks/
gid_tst_gid;/groupIDofowner/
off_tst_size;/sizeinbytes,forregularfiles/
structtimespecst_atime;/timeoflastaccess/
structtimespecst_mtime;/timeoflastmodification/
structtimespecst_ctime;/timeoflastfilestatuschange/
blksize_tst_blksize;/bestI/Oblocksize/
blkcnt_tst_blocks;/numberofdiskblocksallocated/
}
文件类型
普通文件(regularfile):包含了某种形式的数据;
目录文件(directoryfile):包含了其他文件的名字以及指向与这些文件有关信息的指针;
块特殊文件(blockspecialfile):该类型文件提供对设备(如磁盘)带缓冲的访问,每次访问以固定长度为单位进行;
字符特殊文件(characterspecialfile):该文件提供对设备不带缓冲的访问,每次访问长度不变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
FIFO:该文件用于进程通信,也称为命名管道(namedpipe);
套接字(socket):该文件用于进程间的网络通信,也可以用在一台宿主机上进程间的非网络通信。
符号链接(symboliclink):该文件指向另一个文件。
确定文件类型
宏 文件类型
S_ISREG() 普通文件
S_ISDIR() 目录文件
S_ISCHR() 字符特殊文件
S_ISBLK() 块特殊文件
S_ISFIFO() 管道或FIFO
S_ISLNK() 符号链接
S_ISSOCK() 套接字
例子:取命令行参数然后针对每一个命令行打印其文件类型
#include
#include
#include
#include
intmain(intargc,charargv)
{
inti;
charptr;
structstatbuf;
for(i=1;i {
printf("%s:",argv[i]);
if(lstat(argv[i],&buf)<0)
{
printf("lstaterror!\n");
continue;
}
if(S_ISREG(buf.st_mode))
ptr="regular";
elseif(S_ISDIR(buf.st_mode))
ptr="directory";
elseif(S_ISCHR(buf.st_mode))
ptr="characterspecial";
elseif(S_ISBLK(buf.st_mode))
ptr="blockspecial";
elseif(S_ISFIFO(buf.st_mode))
ptr="fifo";
elseif(S_ISLNK(buf.st_mode))
ptr="symboliclink";
elseif(S_ISSOCK(buf.st_mode))
ptr="socket";
printf("%s\n",ptr);
}
exit(0);
}
yanke@yanke-pc:~/yanke/Code/apue/ch4$./a.out/etc/passwd/etc//dev/log\
>/dev/tty/var/lib/oprofile/opd_pipe/dev/sr0/dev/cdrom
/etc/passwd:regular
/etc/:directory
/dev/log:socket
/dev/tty:characterspecial
/var/lib/oprofile/opd_pipe:lstaterror!
/dev/sr0:lstaterror!
/dev/cdrom:lstaterror!
实际用户ID和实际组ID这两个字段在登陆时候确定,一般不会改变。比如我以yanke登录,那么实际用户id即yanke的uid,实际组id为yanke的gid.
有效用户ID和有效用户组ID是进程用来决定我们对资源的访问权限。一般情况下,与实际用户id和实际组id相同,但是当设置了suid(设置用户id)和sgid(设置组id),则有效用户ID等于文件的所有者的uid,有效用户组ID等于文件所有者的gid。
除了rwx权限以外,还有s权限,这就是设置用户ID和设置组ID,它能让进程有效用户ID和有效组ID等于程序拥有者的uid和gid。
文件访问权限
st_mode成员包含了文件的访问权限位。
用户rwx、组rwx和其他rwx,可以使用chmod命令改变。
文件访问如下规则:
打开任意类型文件,需要对保护该名字的每一个目录都有执行权限
对于一个文件的读权限决定了我们能不能打开文件进行读操作。
对于一个文件的写权限决定了我们能不能打开文件进行写操作。
为了在一个目录中创建新文件,必须对包含该文件的目录具有写权限和执行权限
新文件和目录的所有权
新文件的用户ID设置为进程的有效用户ID
关于组ID:可以是进程的有效组ID,也可以是它所在目录的组ID
访问权限测试函数access和faccessat
函数access和faccessat是按照实际用户ID和实际组ID来进行访问权限设置的。faccessat函数更加强大,既可以测试实际用户ID,也可以测试有效用户ID,除非是少数情况,大部分都是使用faccessat函数.
#include
intaccess(constcharpath,intamode);
intfaccessat(intfd,constcharpath,intmode,intflag);
1
2
3
flag设置为AT_EACCESS,则检测有效用户ID和有效组ID。
access函数的用法:
//useaccessfunction
#include
#include
#include
#include
intmain(intargc,charargv)
{
if(argc!=2)
{
printf("usage:a.out\n");
exit(1);
}
if(access(argv[1],R_OK)<0)
{
printf("accesserrorfor%s\n",argv[1]);
}
else
{
printf("readaccessOK\n");
}
if(open(argv[1],O_RDONLY)<0)
{
printf("openerrorfor%s\n",argv[1]);
}
else
{
printf("openforreadingOK\n");
}
exit(0);
}
函数umask
为进程设置屏蔽字,返回之前的值。
#include
mode_tumask(mode_tcmask);
1
2
使用umask
#include
#include
#include
#include
#defineRWRWRW(S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
intmain()
{
umask(0);
if(creat("foo",RWRWRW)<0)
{
printf("creatfooerror!\n");
exit(1);
}
umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
if(creat("bar",RWRWRW)<0)
{
printf("creatbarerror!\n");
exit(1);
}
exit(0);
}
yanke@vm:~/Code/apue/ch4$umask
0002
yanke@vm:~/Code/apue/ch4$llbarfoo
-rw-------1yankeyanke08月3118:39bar
-rw-rw-rw-1yankeyanke08月3118:39foo
shell启动时,它会读取启动文件的设置从而在启动时自行设置为默认的掩码字,在运行过程中,用户可以使用umask命令手动更改掩码字。shell打开一个进程,新的进程则会继承shell的umask值,在使用Unix的时候,经常需要使用的mkdir、touch之类的创建文件的命令,这些命令在运行的时候就是继承shell的umask。
因此:如果想要保证自己能完全指定文件的权限,那么必须在运行时使用umask函数修改为0,否则非0得umask值可能会关闭我们需要的权限位置,当然,如果进程不需要关心文件的权限问题,那么完全可以指定rwxrwxrwx或者rwrwrw的权限,然后umask会自动根据默认值将其修改。
文件权限修改函数chmod,fchmod,fchmodat
#include
intchmod(constcharpath,mode_tmode);
intfchmod(inwww.shanxiwang.nettfildes,mode_tmode);
intchmodat(intfd,constcharpath,mode_tmode,intflag);
1
2
3
4
函数返回值:成功返回0,出错返回-1.
flag参数为AT_SYMLINK_NOFOLLOW时,fchmodat不会跟随符号链接。当flag为0的时候,和其他两个函数等价。
全部的权限位如下:
#defineS_ISUID__S_ISUID/SetuserIDonexecution./
#defineS_ISGID__S_ISGID/SetgroupIDonexecution./
#defineS_ISVTX__S_ISVTX
#defineS_IRUSR__S_IREAD/Readbyowner./
#defineS_IWUSR__S_IWRITE/Writebyowner./
#defineS_IXUSR__S_IEXEC/Executebyowner./
#defineS_IRWXU(__S_IREAD|__S_IWRITE|__S_IEXEC)
#defineS_IRGRP(S_IRUSR>>3)/Readbygroup./
#defineS_IWGRP(S_IWUSR>>3)/Writebygroup./
#defineS_IXGRP(S_IXUSR>>3)/Executebygroup./
#defineS_IRWXG(S_IRWXU>>3)
#defineS_IROTH(S_IRGRP>>3)/Readbyothers./
#defineS_IWOTH(S_IWGRP>>3)/Writebyothers./
#defineS_IXOTH(S_IXGRP>>3)/Executebyothers./
#defineS_IRWXO(S_IRWXG>>3)
#define__S_ISUID04000/SetuserIDonexecution./
#define__S_ISGID02000/SetgroupIDonexecution./
#define__S_ISVTX01000/Saveswappedtextafteruse(sticky)./
#define__S_IREAD0400/Readbyowner./
#define__S_IWRITE0200/Writebyowner./
#define__S_IEXEC0100/Executebyowner./
使用chmod
//testchmod
#include
#include
#include
#include
intmain()
{
structstatstatbuf;
//
if(stat("foo",&statbuf)<0)
{
printf("staterrorforfoo\n");
exit(1);
}
if(chmod("foo",(statbuf.st_mode&~S_IXGRP)|S_ISGID)<0)
{
printf("chmoderrorforfoo\n");
exit(1);
}
if(chmod("bar",S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)<0)
{
printf("chmoderrorforbar\n");
exit(-1);
}
exit(0);
}
yanke@vm:~/Code/apue/ch4$llfoobar
-rw-r--r--1yankeyanke08月3118:39bar
-rw-rwSrw-1yankeyanke08月3118:39foo
粘着位
粘着位,又称保存文本位,其实就是一个权限控制属性。通常情况下,粘着位即可以用在普通文件上,也可以用在目录文件上。当用在普通文件上时,粘着位可以把某个程序文件的SUID置位。并且它的文本映像将永久保存在交换区里。如此的话,当Unix操作系统程序获得了CPU使用权时,就可以快速的装载到内存中。故粘着位可以提高系统程序的运行效率。
如有些版本的Unix系统,就把vi等常用的程序文件的粘着位设置为1,就明显提高了这些应用程序的运行效率。不过现在很少用到这个特性。因为现在磁盘读写速度已经达到极致;而且内存的价格也便宜。
也就是说,现在磁盘速度与内存往往已经不是系统的瓶颈资源,故为普通文件设置粘着位已经没有特殊的必要了。为此笔者现在在系统设置中,基本上不会为普通文件设置粘着位。
更改用户ID和组ID
chown、fchown、fchownat和lchown用于更用户ID和组ID。如果两个参数owner或group中的任意一个是-1,则对应的ID不变。
#include
intchown(constcharpathname,uid_towner,git_tgroup);
intfchown(intfd,uid_towner,gid_tgroup);
intfchownat(intfd,constcharpathname,uid_towner,gid_tgroup,intflag);
intlchown(constcharpathname,uid_towner,gid_tgroup);
//返回值:若辰宫,返回0;若出错,返回-1
文件长度
stat结构成员st_size表示以字节为单位的单位长度。只对普通文件、目录文件和符号连接有效。
对于普通文件,文件长度可以为0
对于目录文件,文件长度是一个数(16,512)的整数倍
对于链接文件,文件长度为文件名中的实际字节数
文件空洞
空洞是由所设置的偏移量超过文件端尾,并写入了某些数据后造成的。
文件截断
如果该文件以前的长度大于length,则超过length以外的数据就不能再访问。如果以前的长度小于length,文件长度将增加,在以前的文件尾端和新的文件尾端之间的数据将读作0。
include
inttruncate(constcharpathname,off_tlength);
intftruncate(intfd,off_tlength);
//返回值:若成功,返回0;若失败,返回-1
文件系统
每个i节点中都有一个链接计数,其值是指向该i节点的目录项数。只有当链接计数减至0时,才可删除该文件(也就是可以释放该文件占用的数据块,这里要注意的一点是虚拟文件系统中并没有关于数据块的概念,只有具体的文件系统中才有相关概念)。这也就是为什么“解除对一个文件的链接”操作并不总是意味着“释放该文件占用的磁盘块”的原因。这也是为什么删除一个目录项的函数被称之为unlink而不是delete的原因。在stat结构中,链接计数包含在st_nlink成员中。这种链接为硬链接。
另外一种链接类型称为符号链接。符号链接文件的实际内容(在数据块中)包含了该符号链接所指向的文件的名字。该i节点中的文件类型是S_IFLINK,于是系统知道这是一个符号链接。
i节点包含了文件有关的所有信息。stat结构中的大多数信息都取自i节点。只有两项重要数据存放在目录项中:文件名和i节点编号。此处的目录项其实也是一种数据块,但它代表的是目录中的一项,因此被称为目录项。目录项中存储的数据是文件名与i节点编号,i节点编号就是文件名所引用的文件的i节点编号,通过这一编号可首先查找到具体的i节点,再通过i节点访问文件中的数据。
一个文件系统中的i节点不能指向另一个文件系统中的i节点,
当在不更换文件系统的情况下为一个文件重命名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的新目录项,并删除老的目录项。链接计数并不会发生改变。
对于任何一个叶子目录(不包含任何其他目录的目录)的链接计数总是2,数值2来自于命令该目录的目录项以及在该目录中的.项。注意,在父目录中的每一个子目录都是该父目录的链接计数增加1。
函数link、linkat、unlink、unlinkat和remove
创建一个指向现有文件的链接
#include
intlink(constcharexistingpath,constcharnewpath);
intlinkat(intefd,constcharexistingpath,intnfd,constcharnewpath,intflag);
//两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数创建一个新目录项newpath,它引用现有文件existingpath。如果newpath已存在,则返回出错。
对于linkat函数,现有文件是通过efd和existingpath指定,新的路径名是通过nfd和newpath指定的。
删除一个现有目录项
#include
intunlink(constcharpathname);
intunlinkat(intfd,constcharpathname,intflag);
//两个函数的返回值:若成功,返回0;若出错,返回-1
这两个函数删除目录项,并将由pathname所引用的链接数减1。
解除文件链接,必须具有写权限,并且必须要有三个条件之一:1.拥有该文件;2.拥有该目录;3.具有超级用户权限。
只有连接为0时才会删除文件,但是只要有进程打开了文件,这个文件也不会删除。
使用remove删除
对于文件和unlink相同
对于目录,与rmdir相同
#include
intremove(constcharpathname);
//返回值:若成功,返回0;若失败,返回-1
函数rename和renameat
#include
intrename(constcharoldname,constcharnewname);
intrenameat(intoldfd,constcharoldname,intnewfd,constcharnewname);
如果oldname指的是一个文件而不是目录,那么为该文件或符号连接重命名。
如果oldname指的是一个空目录,那么为该目录重命名。
如果oldname或newname引用符号连接,则处理的是符号链接本身,而不是它引用的文件。
不能对.和..重命名。
作为一个特例,如果oldname和newname引用同意文件,则函数不做任何更改而成功返回。
符号链接
符号链接,即通常说的软链接,是对一个文件的间接指针。硬链接是直接指向索引块,而软链接实际上是一个文件。
硬链接的限制:
-硬链接通常要求链接和文件位于同一文件系统中。
-只有超级用户才能创建指向目录的硬链接。
对符号链接以及他指向何种对象并无任何文件系统限制。任何用户都可以创建指向目录的符号链接。符号链接一般用于将一个文件或这个目录结构移动到系统中的另一个位置。
创建和读取符号连接
创建目录,其中.和..目录项是自动创建的。目录项通常至少要设置一个执行权限位,以允许访问该目录中的文件名。
#include
intsymlink(constcharactualpath,constcharsympath);
intsymlinat(constcharacutalpath,intfd,constcharsymptah);
//两个函数的返回值:若成功,返回0;若出错,返回-1
读取符号链接
#include
ssize_treadlink(constcharactualpath,constcharsymptah);
sszie_treadlinkat(intfd,constcharrestrictpathname,charrestrictbuf,size_tbufsize);
//两个函数的返回值:若成功,返回读取的字节数;若出错,返回-1
文件时间
由于文件i节点和文件的实际内容是分开的,修改i节点就会修改st_ctime,修改文件内容就会修改st_mtime
目录也是一种文件,也有三个时间
任何修改目录内容的操作也必定会导致目录的inode块被修改,即mtime修改同时ctime修改
任何创建子目录的行为都会导致父目录被更改,这点很好理解,因为子目录必定会导致父目录索引数目增加
目录下项目数目的增加都会导致目录被更改
重命名一个文件实际就是修改了inode块,必定会导致目录修改
函数futimens、utimensat和utimes
修改文件访问时间
#include
intfutimens(intfd,conststructtimespectimes[2]);
intutimensat(intfd,constcharpath,conststructtimespectimes[2],intflag);
//两个函数返回值:若成功,返回0;若出错,返回-1
1
2
3
4
5
times数组参数第一个元素包含访问时间,第二个元素包含修改时间
times参数为空,两个时间设置为当前时间
如果非空,两个时间分别被设置为第一个元素和第二个元素
任何情况下,changetime都被设置为当前时间
使用futimens
#include
#include
#include
#include
#include
#include
intmain(intargc,charargv)
{
inti,fd;
structstatstatbuf;
structtimespectimes[2];
for(i=1;i {
//getcurrenttimes
if(stat(argv[0],&statbuf)<0)
{
printf("%s:staterror\n",argv[i]);
continue;
}
if((fd=open(argv[i],O_RDWR|O_TRUNC))<0)
{
printf("%s:openerror\n",argv[i]);
continue;
}
times[0]=statbuf.st_atim;
times[1]=statbuf.st_mtim;
if(futimens(fd,times)<0)
{
printf("%s:futimeserror\n",argv[i]);
}
close(fd);
}
exit(0);
}
yanke@vm:~/Code/apue/ch4$touchchangemodtimes
yanke@vm:~/Code/apue/ch4$llchangemodtimes
-rw-rw-r--1yankeyanke08月3121:55changemod
-rw-rw-r--1yankeyanke08月3121:55times
yanke@vm:~/Code/apue/ch4$ls-luchangemodtimes
-rw-rw-r--1yankeyanke08月3121:55changemod
-rw-rw-r--1yankeyanke08月3121:55times
yanke@vm:~/Code/apue/ch4$ls-lcchangemodtimes
-rw-rw-r--1yankeyanke08月3121:55changemod
-rw-rw-r--1yankeyanke08月3121:55times
yanke@vm:~/Code/apue/ch4$./a.outchangemodtimes
yanke@vm:~/Code/apue/ch4$ls-luchangemodtimes
-rw-rw-r--1yankeyanke08月3121:55changemod
-rw-rw-r--1yankeyanke08月3121:55times
yanke@vm:~/Code/apue/ch4$ls-lcchangemodtimes
-rw-rw-r--1yankeyanke08月3121:57changemod
-rw-rw-r--1yankeyanke08月3121:57times
函数mkdir、mkdirat和rmdir
创建新目录
include
intmkdir(constcharpathname,mode_tmode);
intmkdir(intfd,constcharpathname,mode_tmode);
//两个函数返回值:若成功,返回0;若出错,返回-1
用rmdir函数可以创建一个空目录
#include
intrmdir(constcharpathname);
//返回值:若成功,返回0;若失败,返回-1
读目录
#include
DIRopendir(constcharpathname);
DIRfdopendir(intfd);
/两个函数返回值:若成功,返回指针;若出错,返回NULL/
structdirentreaddir(DIRdp);
/返回值:若成功,返回指针;若在目录尾或出错,返回NULL/
voidrewinddir(DIRdp);
intcloseddir(DIRdp);
/返回值:若成功,返回0;若失败,返回-1/
longtelldir(DIRdp);
/返回值:与dp关联的相关位置/
voidseekdir(DIRdp,longloc);
DIR结构体的定义如下。DIR结构体类似于FILE,是一个内部结构。
struct__dirstream
{
void__fd;
char__data;
int__entry_data;
char__ptr;
int__entry_ptr;
size_t__allocation;
size_t__size;
__libc_lock_define(,__lock)
};
typedefstruct__dirstreamDIR;
dirent结构体,首先我们要弄清楚目录文件(directoryfile)的概念:这种文件包含了其他文件的名字以及指向与这些文件有关的信息的指针。
定义如下:
structdirent
{
longd_ino;/inodenumber索引节点号/
off_td_off;/offsettothisdirent在目录文件中的偏移/
unsignedshortd_reclen;/lengthofthisd_name文件名长/
unsignedchard_type;/thetypeofd_name文件类型/
chard_name[NAME_MAX+1];/filename(null-terminated)文件名,最长255字符/
}
通过readdir函数读取到的文件名存储在结构体dirent的d_name成员中,而函数
intstat(constcharfile_name,structstatbuf);
的作用就是获取文件名为d_name的文件的详细信息,存储在stat结构体中。以下为stat结构体的定义:
structstat{
mode_tst_mode;//文件访问权限
ino_tst_ino;//索引节点号
dev_tst_dev;//文件使用的设备号
dev_tst_rdev;//设备文件的设备号
nlink_tst_nlink;//文件的硬连接数
uid_tst_uid;//所有者用户识别号
gid_tst_gid;//组识别号
off_tst_size;//以字节为单位的文件容量
time_tst_atime;//最后一次访问该文件的时间
time_tst_mtime;//最后一次修改该文件的时间
time_tst_ctime;//最后一次改变该文件状态的时间
blksize_tst_blksize;//包含该文件的磁盘块的大小
blkcnt_tst_blocks;//该文件所占的磁盘块
};
想要获取某目录下(比如a目下)b文件的详细信息,我们应该怎样做?
-使用opendir函数打开目录a,返回指向目录a的DIR结构体c。
-调用readdir(c)函数读取目录a下所有文件(包括目录),返回指向目录a下所有文件的dirent结构体d。
-遍历d,调用stat(d->name,state)来获取每个文件的详细信息,存储在stat结构体e中。
总体就是这样一种逐步细化的过程,在这一过程中,三种结构体扮演着不同的角色。
工作目录函数族chdir、fchdir和getcwd
每个进程都有一个当前工作目录,调用chdir和fchdir可以更改当前工作目录
#include
intchdir(constcharpathname);
intfchdir(intfd);
实例
#include
#include
#include
intmain()
{
if(chdir("/tmp")<0)
{
printf("chdirfailed\n");
exit(1);
}
printf("chdirto/tmpsucceeded\n");
exit(0);
}
yanke@vm:~/Code/apue/ch4$pwd
/home/yanke/Code/apue/ch4
yanke@vm:~/Code/apue/ch4$./a.out
chdirto/tmpsucceeded
yanke@vm:~/Code/apue/ch4$pwd
/home/yanke/Code/apue/ch4
逐层上移,直到遇到根,得到当前工作目录完整的绝对路径。必须像此函数传递两个函数,一个是缓冲区地址buf(要有足够的长度容纳绝对路径再加上一个终止null字节),另一个是缓冲区的长度size(以字节为单位)。
chargetcwd(charbuf,size_tsize);
1
|
|
|
|
|
|
|
|
|
|
|