1.文件信息结构体
struct stat{ mode_t st_mode; //file type and permissions ino_t st_ino; //i-node number (serial number) dev_t st_dev; //device number (file system) dev_t st_rdev; //device number for special files nlink_t st_nlink; //number of links uid_t st_uid; //user ID of owner gid_t st_gid; //group ID of owner off_t st_size; //size in bytes,for regular files struct timespec st_atime;//time of last access struct timespec st_mtime;//time of last modification struct timespec st_ctime;//time of last file status change blksize_t st_blksize; //best I/O block size blkcnt_t st_blocks; //number of disk blocks allocated };
2.文件类型
(1)普通文件。这是最常用的文件类型,这种文件包含了某种形式的数据。
(2)目录文件。这种文件包含了其他文件的名字以及指向与这些文件有关信息的指针。
(3)块特殊文件。这种类型的文件提供对设备带缓冲的访问,每次访问以固定长度为单位进行。
(4)字符特殊文件。这种类型的文件提供对设备不带缓冲的访问,每次访问长度可变。系统中的所有设备要么是字符特殊文件,要么是块特殊文件。
(5)FIFO。这种类型的文件用于进程间通信,有时也称为命名管道。
(6)套接字。这种类型的文件用于进程间的网络通信。套接字也可用于在一台宿主机上进程之间的非网络通信。
(7)符号链接。这种类型的文件指向另一个文件。
示例程序:判断路径对应文件的类型:
#include "apue.h" #include <iostream> using namespace std; int main(int argc,char *argv[]){ int i; struct stat buf; char *ptr; for (int i=1;i<argc;i++){ cout<<argv[i]<<" :"; if(lstat(argv[i],&buf)<0){ cout<<"latat error!"<<endl; } 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! * *"; cout<<ptr<<endl; } return 0; }
运行输出:
/etc/passwd :regular /etc :directory /dev/log :socket /var/lib :directory /dev/sr0 :block special /dev/tty :character special /dev/cdrom :symbolic link
3.文件访问权限:
每个文件有9个访问权限位,可将它们分成3类。
st_mode 屏蔽 | 含义 |
S_IRUSR | 用户读 |
S_IWUSR | 用户写 |
S_IXUSR | 用户执行 |
S_IRGRP | 组读 |
S_IWGRP | 组写 |
S_IXGRP | 组执行 |
S_IROTH | 其他读 |
S_IWOTH | 其他写 |
S_IXOTH | 其他执行 |
文件访问权限:
- 我们用名字打开任一类型的文件时,对该名字中包含的每一个目录,包括它可能隐含的当前工作目录都应该具有执行权限。
- 对于一个文件的读权限决定了我们是否能够打开现有文件进行读操作。
- 对于一个文件的写权限决定了我们是否能够打开现有文件进行写操作。
- 为了在一个目录中创建一个新文件,必须对该目录具有写权限和执行权限。
- 为了删除一个现有文件,必须对包含该文件的目录具有写权限和执行权限,对该文件本身不需要读、写权限。
进程每次打开、创建或者删除一个文件的时候,内核就进行文件访问权限测试,如下:
(1)若进程的有效用户ID为0(超级用户),则允许访问。
(2)若进程的有效用户ID等于文件的所有者ID,那么如果所有者适当的访问权限位被设置,则允许访问。访问权限与文件中该用户对应的权限设置有关。
(3)若进程的有效ID或者进程的附属组ID之一等于文件的组ID,那么如果组适当的访问权限位被设置,则允许访问。
(4)若其他用户适当的访问权限被设置,则允许访问。
4.新文件和目录的所有权
新文件的用户ID设置为进程的有效用户ID;
新文件的组ID可以设置为进程的有效组ID或者是它所在的目录的组ID。
5.函数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); //flag用于改变faccessat函数的行为,如果flag设置为AT_EACCESS,访问检查用的是调用进程的有效用户ID和有效组ID,而不是实际用户ID和实际组ID。 //两个函数的返回值:若成功,返回0,否则返回-1
示例程序:
#include <iostream> #include "apue.h" #include <fcntl.h> using namespace std; int main(int argc ,char *argv[] ) { if(argc !=2){ cout<<"usage: apue <pathname>"<<endl; return -1; } if(access(argv[1],R_OK)<0){ cout<<"access error for "<<argv[1]<<endl; } else cout<<"read access ok!"<<endl; if(open(argv[1],O_RDONLY)<0){ cout<<"open error for " <<argv[1]<<endl; } else cout<<"open reading ok!"<<endl; return 0; }
程序运行:
apue apue read access ok! open for reading ok! apue /etc/shadow read access error for /etc/shadow open error fro /etc/shadow
6.函数umask
至此,我们说明了与每个文件相关联的9个访问权限位,在此基础上我们可以说明与每个进程相关联的文件模式创建屏蔽字。
umask函数为进程设置文件模式创建屏蔽字,并返回之前的值。
#include <sys/stat.h> mode_t umask(mode_t cmask); //返回值:之前的文件模式创建屏蔽字
其中,参数cmask是由3中表格列出的9个常量(S_IRUSR , S_IWUSR)中的若干位按照或构成的。
示例程序:
#include "apue.h" #include <iostream> #include <fcntl.h> using namespace std; #define PWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)//禁止所有用户的执行权限 int main(){ umask(0); if(creat("foo",PWRWRW)<0){ cout<<"creat error for foo"<<endl; } umask(S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH); if(creat("bar",PWRWRW)<0) cout<<"creat error for bar"<<endl; return 0; }
测试生成的文件的权限:
ll foo ll bar
测试结果:
-rw-rw-rw- 1 zhouyang staff 0 3 25 23:34 foo -rw------- 1 zhouyang staff 0 3 25 23:34 bar
所以,为了使得所有的用户对文件都有访问权限,那么应该设置umask值为0。可见,最终文件的权限为PWRWRW设定的权限 - umask设定的权限。也就是说umask设定的是需要屏蔽的权限。
在bash中,umask的值也设定了创建文件的时候屏蔽的权限位,如下所示:
zhouyangdeMacBook-Pro:Debug zhouyang$ umask -S u=rwx,g=rx,o=rx zhouyangdeMacBook-Pro:Debug zhouyang$ umask 0002 zhouyangdeMacBook-Pro:Debug zhouyang$ umask -S u=rwx,g=rwx,o=rx
7.函数chmod,fchmod和fchmodat
chmod,fchmod和fchmodat函数使我们可以更改现有文件的访问权限。
#include <sys/stat.h> int chmod(const char *pathname); int fchmod(int fd,mode_t mode); int fchmodat(int fd,const char * pathname,mode_t mode,int flag); //成功返回0,出错返回-1.
为了改变一个文件的权限位,进程的有效用户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 | 其他执行 |
注意,我们增加了6项,其中9个是3中表格中的权限。我们另外增加了6个,分别是两个设置ID常量(S_ISUID,S_ISGID)、保存正文常量(S_ISVTX),以及3个组合常量。
程序示例:修改6中程序生成的文件的权限:
#include "apue.h" #include <iostream> #include <fcntl.h> using namespace std; int main(){ struct stat statbuf; if(stat("foo",&statbuf)<0){ cout<<"stat error for foo"<<endl; } //foo.mode_t = ( rw-rw-rw- & ~ S_IXGRP )|S_ISGID //turn on set-group-id and turn off group-execute //S_ISGID 执行时设置组ID if(chmod("foo",(statbuf.st_mode & ~S_IXGRP)|S_ISGID)<0){ cout<<"chmod error for foo "; } //set absolute mode to "rw-r--r--" if(chmod("bar",S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH)<0) cout<<"chmod error for bar"<<endl; return 0; }
最终文件foo和bar的权限分别为:
rw-rwSrw-
-rw-r--r--
8.粘着位
S_ISVTX位有一段有趣的历史。在UNIX尚未使用请求分页式技术的早期版本中,S_ISVTX位被称为粘着位。如果一个可执行程序文件的这一位置被设置了,那么当改程序第一次被执行的时候,在其终止的时候,程序正文的一个副本仍然板寸在交换区。这使得下次执行改程序时能够较快地将其载入内存。
9.函数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) //成功返回0,否则返回-1
10.文件长度
stat结构成员st_size 表示以字节为单位的文件的长度。此字段只对普通的文件、目录文件和符号链接有意义。
现今,大多数现代的UNIX系统提供字段st_blksize和st_blocks。其中,第一个是对文件I/O较为合适的块长度,第二个是所分配的实际的512字节块块数目。
11.文件截断
#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。
12.文件系统
文件系统结构图
(1)硬链接和软链接
在Linux中,多个文件名指向同一索引节点是存在的。一般这种连接就是硬链接。
软链接文件的实际内容包含了该符号链接所指向的文件的名字。(符号链接就是软链接)
(2)i结点
i结点包含了文件有关的所有信息:文件类型、文件访问权限位、文件长度和指向文件数据块的指针等。stat结构中的大多数信息来自结点。只有两项重要数据存放在目录项中:文件名和结点编号。
13.函数link、linkat、unlink、unlinkat和remove
创建硬链接:
#include <unistd.h> int link(const char * existpath,const char * newpath); int linkat(int efd,const char *existingpath,int nfd,const char *newpath,int flag);
//成功后返回0,否则返回-1
删除硬链接:
#include <unistd.h> int unlink(const char *pathname); int unlinkat(int fd,const char *pathname,int flag); //成功后返回0,否则返回-1
示例程序:
#include "apue.h" #include <iostream> #include <fcntl.h> using namespace std; int main(){ //是否打开文件出错 if(open("tempfile",O_RDWR)<0){ cout<<"open error!"<<endl; } //删除tempfile的硬链接 if(unlink("tempfile")<0) cout<<"unlink error!"<<endl; cout<<"file unlinked!"<<endl; //此时文件占有的空间仍然没有释放 sleep(15); cout<<"done!"<<endl; return 0; } //此时文件tempfile已经完全删除
14.重命名文件
#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
15.创建和读取符号链接
可以用symlink或者symlinkat函数创建一个符号链接:
#include <unistd.h> int symlink(const char * actualpath,const char *sympath); int symlinkat(const char *actualpath,int fd,const char *sympath);
使用下述函数可以打开符号链接本身:
#include <unistd.h> ssize_t readlink(const char *restrict pathname,char *restrict buf,size_t bufsize); ssize_t readlink(int fd,const char *restrict pathname,char *restrict buf,size_t bufsize)
16.文件的时间
对每一个文件维护以下3个时间字段,如下所示:
字段 | 说明 | 例子 | ls(1)选项 |
st_atim | 文件数据的最后访问时间 | read | -u |
st_mtim | 文件数据的最后修改时间 | write | 默认 |
st_ctim | i节点状态的最后更改时间 | chmod、chown | -c |
17.函数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)
18.读目录
#include <dirent.h> DIR *opendir(const char *pathname);//返回路径对应的DIR结构 DIR * fdopendir(int fd);//返回文件描述符对应的DIR结构 // 成功返回指针,出错返回NULL struct dirent *readdir(DIR *dp); //若成功,返回指针,若在目录尾或者出错,返回NULL void rewinddir(DIR * dp); void closedir(DIR*dp) ; //成功返回0,失败返回-1 long telldir(DIR *dp); void seekdir(DIR *dp,long loc); //返回与dp关联的目录中的当前位置
下面是一个示例程序,可以递归降序遍历文件的层次结构:
#include "apue.h" #include <iostream> #include <dirent.h> #include <limits.h> using namespace std; 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){ cout<<"usage: apue <starting-pathname>"; exit(-1); } //调用函数myftw ret = myftw(argv[1],myfunc); ntot=nreg+ndir+nblk+nchr+nfifo+nslink+nsock; if(ntot==0)//避免除0 ntot=1; //输出各种文件所占的比率 printf("regular files = %7ld,%5.2f %% ",nreg,nreg*100.0/ntot); printf("directories = %7ld,%5.2f %% ",ndir,ndir*100.0/ntot); printf("block special = %7ld,%5.2f %% ",nblk,nblk*100.0/ntot); printf("char special = %7ld,%5.2f %% ",nchr,nchr*100.0/ntot); printf("FIFOs = %7ld,%5.2f %% ",nfifo,nfifo*100.0/ntot); printf("symbolic links = %7ld,%5.2f %% ",nslink,nslink*100.0/ntot); printf("sockets = %7ld,%5.2f %% ",nsock,nsock*100.0/ntot); exit(ret); } /* * Descend through the hierarchy,starting at "pathname". * the caller's func() is called for every file. */ #define FTW_F 1 //file other than directory #define FTW_D 2 //directory #define FTW_DNR 3 //directory that can't be read #define FTW_NS 4 //file that we can't stat static char *fullpath; //contains full func() returns static size_t pathlen; static int myftw(char *pathname,Myfunc *func){ fullpath=path_alloc(&pathlen);//malloc PATH_MAX+1 bytes if(pathlen<= strlen(pathname)){ pathlen=strlen(pathname)*2; if((fullpath=(char *)realloc(fullpath,pathlen))==NULL){ cout<<"realloc error!"<<endl; } } strcpy(fullpath,pathname); return dopath(func); } /* * Descend through the hierarchy,starting at "fullpath". * if "fullpath" is anything other than a directory,we lstat() it, //不是目录 * call func(),and return.for a directory,we call ourself * recursively for each name in the directory */ static int dopath(Myfunc *func){//we return whatever func() returns struct stat statbuf; struct dirent *dirp; DIR *dp; int ret ,n; if(lstat(fullpath,&statbuf)<0){//stat error return (func(fullpath,&statbuf,FTW_NS)); } if(S_ISDIR(statbuf.st_mode)==0){//not a directory 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){//expand path buffer pathlen*=2; if((fullpath=(char *)realloc(fullpath,pathlen))==NULL){ cout<<"realloc error!"<<endl; } } 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);//append name after / if((ret=dopath(func))!=0)//recursive break; } fullpath[n-1] =0; if(closedir(dp)<0){ cout<<"can't close directory "<<fullpath<<endl; } 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: { cout<<"for S_IFDIR for "<<pathname<<endl; exit(-1); } } break; case FTW_D: ndir++; break; case FTW_DNR: cout<<"can't read directory "<<pathname<<endl; break; case FTW_NS: cout<<"stat error for "<<pathname<<endl; break; default: cout<<"unknown type "<<type <<" for pathname "<<pathname<<endl; exit(-1); } return 0; }
运行示例:
19.函数chdir,fchdir和getcwd
每个进程都有一个当前的工作目录,这个目录是搜索所有相对路径名的起点。当用户登录到UNIX系统的时候,其当前的工作目录通常是用户的home directory。
调用函数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
示例程序:
#include "apue.h" #include <iostream> using namespace std; int main(){ char *ptr; size_t size; if(chdir("/Users/zhouyang")<0){ cout<<"chdir failed!"<<endl; } ptr=path_alloc(&size); if(getcwd(ptr,size)==NULL){ cout<<"getcwd failed!"<<endl; } cout<<"cwd= "<<ptr<<endl; return 0; }
运行输出:
cwd= /Users/zhouyang