遍歷目錄下的文件時要用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)); }