遍历目录下的文件时要用lstat而不能用statnode
#include <sys/stat.h> int stat(const char *path, struct stat *buf); int fstat(int fd, struct stat *buf); int lstat(const char *path, struct stat *buf);//丢弃跟随属性,只针对符号连接自己,不针对符号连接对应的文件 int fstatat(int fd,const char *path, struct stat *buf,int flag); //当flag被设置为AT_SYMLINK_NOFOLLOW时,fstatat不跟随符号连接,只返回符号连接自己的信息 //fd为AT_FDCWD时,fstatat会计算针对当前目录的path参数 //以上函数成功返回0失败返回-1 struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
//文本信息包含在st_mode中,如下宏肯定st_mode成员类型 S_ISREG(m) is it a regular file (普通文件) S_ISDIR(m) directory (目录文件) S_ISCHR(m) character device (字符特殊文件) S_ISBLK(m) block device (块特殊文件) S_ISFIFO(m) FIFO (named pipe) (管道或 FIFO) S_ISLNK(m) symbolic link (符号连接)(Not in POSIX.1-1996.) S_ISSOCK(m) socket (套接字)(Not in POSIX.1-1996.)
也可从stat结构体中肯定IPC对象类型,他们的参数并不是st_mode,而是指向stat结构体指针ios
S_TYPEISMQ()//消息队列 S_TYPEISSEM()//信号量 S_TYPEISSHM()//共享存储对象
与一个进程关联的ID有6个或更多,以下图所示:git
实际用户IDgithub 实际组ID数组 |
咱们实际是谁 |
有效用户ID网络 有效组IDapp 附加组IDsocket |
用于文件访问权限检索 |
保存的设置用户IDide 保存的设置组ID函数 |
由exec函数保存 |
一般,有效用户ID等于实际用户ID,有效组ID等于实际组ID。
每一个文件都有一个全部者和组全部者,全部者由stat结构中的st_uid成员表示,组全部者则由st_gid成员表示——只针对可执行文件有效
当执行一个程序文件时,进程的有效用户ID一般就是实际用户ID,有效组ID一般是实际组ID。可是能够在文件模式字中设置一个特殊标志,其含义是“当执行此文件时,将进程的有效用户ID设置为文件全部者的用户ID(st_uid)”。与此相似,在文件模式字中能够设置另外一位,它使得将执行此文件的进程的有效组ID设置为文件的组全部者(st_gid)。在文件模式字中的这两位被称为设置用户ID(set_user-ID)位和设置组ID(set-group-ID)位。
例如,若文件全部者是超级用户,并且设置了该文件的设置用户ID位,而后当该程序由一个进程执行时,则该进程具备超级用户特权。无论执行此文件的进程的实际用户ID是什么,都进行这样的处理。例如,UNIX程序password容许任一用户改变其口令,该程序是一个设置用户ID程序。由于该程序应能将用户的新口令写入口令文件中,而只有超级用户才具备对该文件的写权限,因此须要使用设置用户ID特征。由于运行设置用户ID程序的进程一般获得额外的权限,因此编写这样程序时要特别谨慎。
再返回到stat函数,设置用户ID位及设置组ID位都包含在st_mode值中。这两位可用常量S_ISUID和S_ISGID测试。
stat 结构的st_mode 值中包含了针对文件的访问权限位。全部文件类型都具备访问权限。每一个文件有 9 个访问权限位
st_mode 屏蔽 | 意义 |
S_IRUSR | 用户 -读 |
S_IWUSR | 用户 -写 |
S_IXUSR | 用户 -执行 |
S_IRGRP | 组 -读 |
S_IWGRP | 组 -写 |
S_IXGRP | 组 -执行 |
S_IROTH | 其余 -读 |
S_IWOTH | 其余 -写 |
S_IXOTH | 其余 -执行 |
进程每次打开、建立或删除一个文件时,内核就进行文件访问权限测试。这种测试可能涉及文件的全部者(st_uid 和st_gid)、进程的有效 ID(有效用户 ID 和有效组 ID)以及进程的附加组 ID。内核进行的测试按下面步骤依次进行:
顺序的执行以上四步,若是进程拥有此文件(2步)按用户访问权限位批准或拒绝该进程对文件的访问——不看组访问权限,若是进程不拥有此文件,单进程属于某个适当的组,按组访问权限位批准或拒绝该进程对文件的访问权限——不看其余用户的访问权限。
新文件的用户 ID 设置为进程的有效用户 ID。关于组 ID,POSIX.1 容许实现选择下列之一做为新文件的组 ID。
对于 Linux 2.4.22,新文件的组 ID 取决于它所在目录的设置组 ID 为是否被设置。若是该目录的这一位被设置,则新文件的组 ID 设置为目录的组 ID;不然,将新文件的组 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 /* Values for the second argument to access. These may be OR'd together. */ #define R_OK 4 /* Test for read permission. */ #define W_OK 2 /* Test for write permission. */ #define X_OK 1 /* Test for execute permission. */ #define F_OK 0 /* Test for existence. */
注意:
open打开文件时,内核以进程有效用户ID和有效组ID为基础测试文件访问权限位。次函数按实际用户ID和实际组ID进行文件访问权限位测试
#include <sys/stat.h> int chmod(const char *path, mode_t mode); int fchmod(int fd, mode_t mode); int fchmodat(int fd,const char *path,mode_t mode,int flag); //以上函数成功返回0失败返回-1
The new file permissions are specified in mode, which is a bit mask created by ORing together zero or more of the following: S_ISUID (04000) set-user-ID (set process effective user ID on execve(2)) S_ISGID (02000) set-group-ID (set process effective group ID on execve(2); mandatory locking, as described in fcntl(2); take a new file's group from parent directory, as described in chown(2) and mkdir(2)) S_ISVTX (01000) sticky bit (restricted deletion flag, as described in unlink(2)) S_IRUSR (00400) read by owner S_IWUSR (00200) write by owner S_IXUSR (00100) execute/search by owner ("search" applies for directories, and means that entries within the directory can beaccessed) S_IRGRP (00040) read by group S_IWGRP (00020) write by group S_IXGRP (00010) execute/search by group S_IROTH (00004) read by others S_IWOTH (00002) write by others S_IXOTH (00001) execute/search by others
注意:
对于文件:
在之前旧的系统当中,若是一个程序文件一旦设置了粘着位,那么当该程序停止的时候他的全部指令段将被保存到系统的交换分区当中,再次运行时能够更快的调入系统.不过如今的操做系统已经再也不使用这种功能了.但这并不表示这功能已经彻底被废弃
对于目录:
当一个目录设置为粘着位时,它将发挥特殊的做用,即当一个目录被设置为"粘着位"(用chmod a+t),则该目录下的文件只能由
也就是说,即使该目录是任何人均可以写,但也只有文件的属主才能够删除文件
stat结构成员st_size表示以字节为单位的文件长度,此字段只对普通文件、目录文件和符号连接有意义。对于普通文件,其文件长度能够是0,在读这种文件时,将获得文件结束(end-of-file)指示。对于目录,文件长度一般是一个数(例如16或者512)的倍数。对于符号连接,文件长度是文件名中实际字节数。
空洞是由所设置的偏移量超过文件尾端,并写了某些数据后形成的。对于没有写过的字节位置,read函数读到的字节是0.若是使用实用程序(例如cat(1))复制这种文件,那么全部这些空洞都会被填满,其中全部实际数据字节皆填写为0.
咱们能够把一个磁盘分红一个或多个分区。每一个分区能够包含一个文件系统
每一个文件系统各自对他们的i节点进行编号,所以目录项中的i节点编号数指向同一文件系统中的相应i节点,不能使一个目录项指向另外一个文件系统的i节点。
当在不更换文件系统状况下为一个文件改名时,该文件的实际内容并未移动,只需构造一个指向现有i节点的心目录项,并解除与旧目录项的连接。这就是mv(1)命令的一般操做方式
任何一个文件能够有多个目录项指向其i节点,建立一个指向现有文件的连接——link,也就是建立硬连接。
#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中的最后一个份量,路径中的其余部分应当已存在。
建立新目录项以及增长连接计数应当是个原子操做,大多数实现要求这两个路径名在同一个文件系统中。若是实现支持建立执行一个目录的硬连接,那么也是仅限于超级用户才能够这么作
为了删除一个现有的目录项,能够调用unlink函数
#include <unistd.h> int unlink(const char *pathname); int unlinkat(int fd,const char *pathname,int flag); //返回值:若成功返回0,若出错返回-1
此函数删除目录项,并将由pathname所引用文件的连接计数减1。若是还有指向该文件的其余连接,则仍然能够经过其余连接访问该文件的数据。若是出错,怎不对该文件作任何更改。
为了解除对文件的连接,必须对包含该目录项目录具备写和执行权限。若是对该目录设置了粘着位,则对该目录必须具备写权限,而且具有下面三个条件之一:
只有连接计数达到0时,该文件的内容才能够被删除。只要有进程打开了该文件,其内容也不能删除。关闭一个文件时,内核首先检查打开该文件的进程数。若是该数达到0,而后内核检查其链接数,若是这个数也是0,那么就删除该文件的内容。
unlink的这种性质常常被程序用来确保及时是在该程序奔溃时,他所建立的临时文件也不会遗留下来。进程用open或create建立一个文件,而后当即调用unlink。由于该文件仍旧是打开的,因此不会将其内容删除。只有当进程关闭该文件或终止时(在这种状况下,内核会关闭该进程打开的所有文件),该文件的内容才会被删除。
若是pathname是符号连接,那么unlink删除该符号连接,而不会删除由该连接所引发的文件。给出符号连接名状况下,没有一个函数能删除该连接所引用的文件。
符号连接是指向一个文件的间接指针,它与硬连接有所不一样,硬连接直接指向文件的i节点。引入符号连接的缘由是为了避开硬连接的一些限制:
对符号连接以及他指向各类对象并没有任何文件系统限制,任何用户均可以建立指向目录的符号连接。符号连接通常用于将一个文件或整个目录结构移到系统中的另外一个位置。
当使用以名字引用文件的函数时,应当了解该函数是否处理符号连接。也就是该函数是否跟随符号连接到达他所链接的文件。若是该函数具备处理符号连接的功能,则其路径名参数引用由符号连接所指向的文件。不然,路径名参数将引用连接自己,而不是该连接指向的文件。
下表列出了本章所说明的各个函数是否处理符号连接,表中没有列出mkdrir、mkinfo、mknod、rmdir这些函数,其缘由是,当路径名是符号连接时,他们都出错返回。以文件描述符做为参数的一些函数(如fstat、fchmod等)也未在该表中列出,其缘由是,对于符号连接的处理是由返回文件描述符的函数(一般是open)进行的。chown是否跟随符号连接取决于实现。
引入符号连接可能在文件系统中引入循环。大多数查找路径名的函数在这种状况发生时都将返回值为ELOOP的errno
这里建立了一个目录foo,它包含了一个名为a的文件以及一个指向foo的符号连接。在下图显示了这种这种结果,以圆表示目录,正方形表示一个文件。若是咱们编写一段程序,使用Solaris的标准函数ftw(3)以降序遍历文件结构,打印每一个遇到的路径名:其结果输出是:
这样一个循环式很容易消除的。由于unlink并不跟随符号连接,因此能够unlink文件foo/testdir。可是若是建立了一个构成循环的硬连接,那么就很难消除它。这就是为何link函数不容许构造指向目录的硬连接的缘由(除非进程具备超级用户特权)。
当open打开文件时,若是传递给open函数的路径名指定了一个符号连接,那么open跟随此连接到达你所指定的文件。若此符号连接所指向的文件并不存在,则open返回出错,表示他不能打开该文件。
建立符号连接
#include <unistd.h> int symlink(const char *actualpath, const char *sympath); int symlinkat(const char *actualpath,int fd,const char *sympath); //返回值:若成功怎返回0,若出错则返回-1;
#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的字节数。在buf中返回的符号连接的内容不以null字符终止。
注意修改时间(st_mtime)和更改状态时间呢(st_ctime)之间的区别。修改时间是文件内容最后一次被修改的时间。更改时间状态是该文件的i节点最后一次被修改的时间。有不少操做,他们影响到i节点,但没有更改文件的实际内容:文件的存取许可权、用户ID、链接数等等。由于i节点中的全部信息都是与文件的实际内容分开存放的,因此,除了文件夹数据修改时间之外,还须要更改状态时间。
注意,系统并不保存对一个i节点的最后一次存取时间,因此access和stat函数并不更改这三个时间中的任意一个。
系统管理员经常使用存取时间来删除在必定时间范围内没有存取过的文件。典型的例子是删除在过去一周内没有存取过的名为a.out或core的文件。find(1)命令常被用来进行这种操做。
修改时间和更改时间状态可被用来归档其内容已经被修改或者其i节点已经被更改的那些文件。
ls命令按这三个时间值中的一个进行排序显示。按系统默认,他按文件的修改时间的前后排序显示。-u选择项使其用存取时间排序,-c选择项则使其用刚改状态时间排序。
每一个进程都有一个当前工做目录,此目录是搜索全部相对路径名的起点(不以斜线开始的路径名为相对路径名)。当前工做目录是进程的一个属性,起始目录则是登陆名的一个属性。
对某个目录具备访问权限的任一用户均可以读该目录,可是为了防止文件系统产生混乱,只有内核才能够写目录。一个目录的写权限位和执行权限位决定了该目录中可否建立新文件以及删除文件,他们并不表示可否写目录自己。
目录的实际格式依赖于UNIX系统实现和文件系统的设计。不少实现阻止应用程序使用read函数读取目录的内容,由此进一步将应用程序与目录格式中相关的细节隔离。
#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);
定义在头文件<dirent.h>中的dirent结构与实现相关。实现对此结构所作的定义至少包含下列两个成员:
ino_t d_ino; /*i节点编号*/ char d_name[]; /*以null结束的文件名*/
注意,d_name项的大小并无指定,但必须保证他能包含至少NAME_MAX个字节(不包含终止null字节)。由于文件名是以null字节结束的,因此在头文件中如何定义数组d_name并没有多大关系。数值大小并不表示文件名的长度。
DIR结构是一个内部结构,
上述的函数用这个内部结构保存当前正在被读的目录的有关信息。其做用相似于FILE结构。
由opendir和fdopendir返回的指向DIR结构的指针由另外5个函数使用。opendir执行初始化操做,使第一个readdir返回目录中的第一个目录项。DIR结构由fdopendir建立时,readdir返回的第一项取决于传给fdopendir函数的文件描述符相关联的文件偏移量。注意,目录中各目录项的顺序与实现有关。他么一般不按字母顺序排列。
下例子为一个遍历文件层次结构的程序
#include "apue.h" #include "pathalloc.h" #include <dirent.h> #include <limits.h> // function type that is called for each filename 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) { err_quit("usage: ftw <starting-pathname>"); } ret = myftw(argv[1], myfunc); // does it all ntot = nreg + ndir + nblk + nchr + nfifo + nslink + nsock; if (ntot == 0) { ntot = 1; // avoid divide by 0; print 0 for all counts } 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("FIFLs = %7ld, %5.2f %%\n", nfifo, nfifo * 100.0 / ntot); printf("symbolic links = %7ld, %5.2f %%\n", nslink, nslink * 100.0 / ntot); printf("sockets = %7ld, %5.2f %%\n", 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 thar can't be read #define FTW_NS 4 // file that we can't stat static char *fullpath; // contains full pathname for every file static size_t pathlen; static int myftw(char *pathname, Myfunc *func) // we return whatever func() returns { fullpath = path_alloc(&pathlen); // malloc PATH_MAX+1 bytes if (pathlen <= strlen(pathname)) { pathlen = strlen(pathname) * 2; if ((fullpath = realloc(fullpath, pathlen)) == NULL) { err_sys("realloc failed"); } } 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)); } /* * It's a directory. First call func() for the directory, * then process each filename in the directory. * */ 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 = realloc(fullpath, pathlen)) == NULL) { err_sys("realloc failed"); } } fullpath[n++] = '/'; fullpath[n] = 0; if ((dp = opendir(fullpath)) == NULL) { // can't read directory return func(fullpath, &statbuf, FTW_DNR); } while ((dirp = readdir(dp)) != NULL) { if (strcmp(dirp->d_name, ".") == 0 || strcmp(dirp->d_name, "..") == 0) { continue; // ignore dot and dot-dot } strcpy(&fullpath[n], dirp->d_name); // append name agter "/" if ((ret = dopath(func)) != 0) { // recursive break; // time to leave } } fullpath[n - 1] = 0; // erase everything from slash onward if (closedir(dp) < 0) { err_ret("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: // directories should have type = FTW_D err_dump("for S_IFDIR for %s", pathname); } break; case FTW_D: ++ndir; break; case FTW_DNR: err_ret("can't read directory %s", pathname); break; case FTW_NS: err_ret("stat error for %s", pathname); break; default: err_dump("unknow type %d for pathname %s", type, pathname); } return 0; }
下列例子为深度遍历文件层次结构
/************************************************************************* > File Name: ls.cpp > Author: Chen Tianzeng > Mail: 971859774@qq.com > Created Time: 2019年03月07日 星期四 11时16分12秒 ************************************************************************/ #include <iostream> #include <sys/stat.h> #include <dirent.h> #include <unistd.h> #include <cstring> #include <cstdio> #include <cstdlib> using namespace std; void ls_dirent(char *s) { DIR *dir; struct dirent *d; struct stat stats; if((dir=opendir(s))==NULL) { cout<<s<<endl; return; } chdir(s); while((d=readdir(dir))!=NULL) { lstat(d->d_name,&stats); if(S_ISDIR(stats.st_mode)) { if(strcmp(d->d_name,".")==0||strcmp(d->d_name,"..")==0) continue; else { char str[100]; memset(str,'\0',sizeof(str)); strcpy(str,s); strcat(str,"/"); strcat(str,d->d_name); ls_dirent(str); } } else { char dircwd[100]; memset(dircwd,'\0',100); strcpy(dircwd,s); strcat(dircwd,"/"); strcat(dircwd,d->d_name); cout<<dircwd<<endl; } } chdir(".."); closedir(dir); return ; } int main(int argc,char **argv) { char s[100]; cin>>s; ls_dirent(s); return 0; }
GitHub:https://github.com/tianzengBlog/test/tree/master/test/dir
st_dev和st_rdev这两个字段常常引发混淆,有关规则以下:
Linux将宏major和minor定义在头文件<sys/sysmacros.h>中,而该头文件又包括在<sys/type.h>中。
以下程序为每一个命令行参数打印设备号,另外,若此参数引用的是字符特殊文件或块特殊文件,则还会打印该特殊文件的st_rdev值。
int i; struct stat buf; if(stat(filename, &buf) < 0) { err_ret("stat error"); continue; } printf("dev = %d/%d", major(buf.st_dev), minor(buf.st_dev)); 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)); }