文件和目錄

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

文件類型

  1. 普通文件:在 UNIX/Linux 系統中一般所見到的文件,一個文件包括兩部分數據,一部分是元數據,如文件的類型、權限、大小、用戶、組、各類時間戳等,存儲在 i 節點中,另外一部分是內容數據,存儲在數據塊中。對於數據是文本數據仍是二進制數據無區別(例外:二進制可執行文件,爲了執行程序,內核必須理解其格式,這種格式肯定程序文本和數據加載的位置)。
  2. 目錄文件:系統經過 i 節點惟一地標識一個文件的存在,但人們更願意使用有意義的文件名來訪問文件,目錄就是用來創建文件名和 i 節點之間的映射的。目錄的本質就是一個普通文件,與其它普通文件惟一的區別就是它僅僅存儲文件名和 i 節點號的映射,每個這樣的映射,用目錄中的一個條目表示,謂之硬連接
  3. 塊特殊文件:這種類型的文件提供對設備(如磁盤)帶緩衝的訪問,每次訪問以固定長度爲單位進行。
  4. 字符特殊文件:這種類型的文件提供對設備不帶緩衝的訪問,每次訪問長度可變。系統中的全部設備要麼是字符特殊文件,要麼是塊特殊文件。
  5. FIFO:這種類型的文件用於進程間通訊,有時也稱爲命名管道。
  6. 套接字:這種類型的文件用於進程間的網絡通訊。套接字也可用於一臺宿主機上進程之間的非網絡通訊。
  7. 符號連接:這種類型的文件指向另外一個文件。
//文本信息包含在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和設置組ID

與一個進程關聯的ID有6個或更多,以下圖所示:git

實際用戶IDgithub

實際組ID數組

咱們實際是誰

有效用戶ID網絡

有效組IDapp

附加組IDsocket

用於文件訪問權限檢索

保存的設置用戶IDide

保存的設置組ID函數

由exec函數保存
  1. 實際用戶ID和實際組ID標識咱們到底是誰,這兩個字段在登陸時取自口令文件中的登陸項。一般,在一個登陸會話間這些值並不改變,可是超級用戶進程有方法改變它們。
  2. 有效用戶ID,有效組ID以及附加組ID決定了咱們的文件訪問權限。
  3. 保存的設置的用戶ID和保存的設置組ID在執行一個程序時包含了有效用戶ID和有效組ID的副本。

st_uid和st_gid

  一般,有效用戶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 其餘 -執行

  1. 用名字打開任一類型的文件時,對該名字中包含的每個目錄,包括它可能隱含的當前工做目錄都應具備執行權限,這就是對目錄的執行權限稱爲搜索位。例如,爲了打開文件/usr/include/stdio.h,須要對目錄/、/usr 和/usr/include具備執行權限。而後,須要具備對該文件自己的適當權限。若是當前工做目錄是/usr/include,那麼爲了打開文件 stdio.h,則須要有對該工做目錄的執行權限。注意:對於目錄的讀權限和執行權限的意義是不相同的。讀權限容許咱們讀目錄,獲取在該目錄中全部文件名的列表當一個目錄是咱們要訪問文件的路徑名的一個組成部分時,對該目錄的執行權限使咱們可經過該目錄(也就是搜索該目錄,尋找一個特定的文件名。)
  2. 文件的讀權限決定了咱們是否可以打開該文件進行讀操做。
  3. 文件的寫權限決定了咱們是否可以打開該文件進行寫操做。
  4. 爲了要在一個目錄中建立一個新文件,必須對該目錄具備寫權限和執行權限
  5. 爲了刪除一個現有的文件,必須對包含該文件的目錄具備寫權限和執行權限。對該文件自己則不須要有讀、寫權限
  6. 若是用7個exec 函數中的任何一個執行某個文件,都必須對該文件具備執行權限。該文件還必須是一個普通文件

測試文件訪問權限位

  進程每次打開、建立或刪除一個文件時,內核就進行文件訪問權限測試。這種測試可能涉及文件的全部者(st_uid 和st_gid)、進程的有效 ID(有效用戶 ID 和有效組 ID)以及進程的附加組 ID。內核進行的測試按下面步驟依次進行:

  1. 若進程的有效用戶 ID 爲 0(即超級用戶),則容許訪問。
  2. 若進程的有效用戶 ID 等於文件的全部者 ID,那麼:若全部者適當的訪問權限位被設置,則容許訪問,不然拒絕訪問。適當的訪問權限位指的是:若進程爲讀而打開該文件,則用戶讀位應爲 1;若進程爲寫而打開該文件愛你,則用戶寫位應爲 1;若進程將執行該文件,則用戶執行位應爲 1.
  3. 若進程有效組 ID 或進程附加組 ID 之一等於文件的組 ID,那麼,若組適當的訪問權限位被設置,則容許訪問,不然拒絕訪問。
  4. 若其餘用戶適當的訪問權限位被設置,則容許訪問,不然拒絕訪問。

  順序的執行以上四步,若是進程擁有此文件(2步)按用戶訪問權限位批准或拒絕該進程對文件的訪問——不看組訪問權限,若是進程不擁有此文件,單進程屬於某個適當的組,按組訪問權限位批准或拒絕該進程對文件的訪問權限——不看其餘用戶的訪問權限。

新文件的目錄和全部權限

  新文件的用戶 ID 設置爲進程的有效用戶 ID。關於組 ID,POSIX.1 容許實現選擇下列之一做爲新文件的組 ID。

  • 新文件的組 ID 能夠是進程有效組 ID。
  • 新文件的組 ID 能夠是它所在目錄的組 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

注意:

  1. chomd只更新i結點最後一次訪問時間
  2. 對普通文件賦予粘着位,有沒有超級用戶權限,則mode中的粘着位自動關閉,防止用戶惡意設置粘着位,影響系統性能
  3. 新建立文件的組ID可能不是調用進程的所屬的組。若是新文件的組ID不等於該進程有效組ID或進程複述數組ID其中之一,並且進程沒有超級用戶權限,那麼設置組ID會被清除。防止用戶建立了一個設置組ID文件,而該文件是由非該用戶所屬的組擁有的

粘着位

對於文件:

  在之前舊的系統當中,若是一個程序文件一旦設置了粘着位,那麼當該程序停止的時候他的全部指令段將被保存到系統的交換分區當中,再次運行時能夠更快的調入系統.不過如今的操做系統已經再也不使用這種功能了.但這並不表示這功能已經徹底被廢棄

對於目錄:

  當一個目錄設置爲粘着位時,它將發揮特殊的做用,即當一個目錄被設置爲"粘着位"(用chmod a+t),則該目錄下的文件只能由

  1. 超級管理員刪除
  2. 該目錄的全部者刪除
  3. 該文件的全部者刪除

  也就是說,即使該目錄是任何人均可以寫,但也只有文件的屬主才能夠刪除文件

文件長度

  stat結構成員st_size表示以字節爲單位的文件長度,此字段只對普通文件、目錄文件和符號連接有意義。對於普通文件,其文件長度能夠是0,在讀這種文件時,將獲得文件結束(end-of-file)指示。對於目錄,文件長度一般是一個數(例如16或者512)的倍數。對於符號連接,文件長度是文件名中實際字節數。

  其中,文件長度7就是路徑名usr/lib的長度。現在,大多數UNIX系統提供字段st_blksize和st_blocks。其中,第一個是對文件I/O較爲合適的塊長度,第二個時所分配的實際512字節塊數量。

空洞文件

  空洞是由所設置的偏移量超過文件尾端,並寫了某些數據後形成的。對於沒有寫過的字節位置,read函數讀到的字節是0.若是使用實用程序(例如cat(1))複製這種文件,那麼全部這些空洞都會被填滿,其中全部實際數據字節皆填寫爲0.

文件系統

  咱們能夠把一個磁盤分紅一個或多個分區。每一個分區能夠包含一個文件系統

  i節點是固定長度的記錄項,它包含有關文件的大部分信息。若是更仔細的觀察一個柱面組的i節點和數據塊部分,則能夠看到 以下圖所示的狀況
注意:
  1. 在圖中有兩個目錄項指向同一個i節點。每一個i節點中都有一個連接計數,其值是指向該i節點的目錄項數。只有當連接計數減小至0時,纔可刪除該文件(也就是能夠釋放該文件佔用的數據塊)。這就是爲何「刪除對一個文件的連接」操做並不老是意味着「釋放該文件佔用的磁盤塊」的緣由。在stat結構中,連接計數包含在st_nlink成員中,其基本系統數據類型是nlink_t。這種連接類型稱爲硬連接,LINK_MAX指定了一個文件鏈接數的最大值。
  2. 另一種連接類型稱爲符號連接。對於這種連接,該文件的實際內容(在數據塊中)包含了該符號連接所指向的文件的名字。在上面的例子中:該目錄項中的文件名是3字符的字符串lib,而在該文件中包含了7個數據字節usr/lib。gaii節點中的文件類型是S_IFLNK,因而系統知道這是一個符號連接。
  3. i節點包含了大多數與文件有關的信息:文件類型、文件訪問權限位、文件長度和指向該文件所佔用的數據塊的指針等。stat結構的大多數信息都是取自i節點。只有兩項數據存放在目錄項中:文件名和i節點編號。i節點編號的數據類型是ino_t
  4. 每一個文件系統各自對他們的i節點進行編號,所以目錄項中的i節點編號數指向同一文件系統中的相應i節點,不能使一個目錄項指向另外一個文件系統的i節點。

  5. 當在不更換文件系統狀況下爲一個文件改名時,該文件的實際內容並未移動,只需構造一個指向現有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。若是還有指向該文件的其餘連接,則仍然能夠經過其餘連接訪問該文件的數據。若是出錯,怎不對該文件作任何更改。
  爲了解除對文件的連接,必須對包含該目錄項目錄具備寫和執行權限。若是對該目錄設置了粘着位,則對該目錄必須具備寫權限,而且具有下面三個條件之一:

  1. 擁有該文件
  2. 擁有該目錄
  3. 具備超級用戶特權

  只有連接計數達到0時,該文件的內容才能夠被刪除。只要有進程打開了該文件,其內容也不能刪除。關閉一個文件時,內核首先檢查打開該文件的進程數。若是該數達到0,而後內核檢查其鏈接數,若是這個數也是0,那麼就刪除該文件的內容。

  unlink的這種性質常常被程序用來確保及時是在該程序奔潰時,他所建立的臨時文件也不會遺留下來。進程用open或create建立一個文件,而後當即調用unlink。由於該文件仍舊是打開的,因此不會將其內容刪除。只有當進程關閉該文件或終止時(在這種狀況下,內核會關閉該進程打開的所有文件),該文件的內容纔會被刪除。
  若是pathname是符號連接,那麼unlink刪除該符號連接,而不會刪除由該連接所引發的文件。給出符號連接名狀況下,沒有一個函數能刪除該連接所引用的文件。

符號連接

  符號連接是指向一個文件的間接指針,它與硬連接有所不一樣,硬連接直接指向文件的i節點。引入符號連接的緣由是爲了避開硬連接的一些限制:

  1. 硬連接一般要求連接和文件位於同一文件系統中。
  2. 只有超級用戶才能建立指向目錄的硬連接。

  對符號連接以及他指向各類對象並沒有任何文件系統限制,任何用戶均可以建立指向目錄的符號連接。符號連接通常用於將一個文件或整個目錄結構移到系統中的另外一個位置。
  當使用以名字引用文件的函數時,應當瞭解該函數是否處理符號連接。也就是該函數是否跟隨符號連接到達他所鏈接的文件。若是該函數具備處理符號連接的功能,則其路徑名參數引用由符號連接所指向的文件。不然,路徑名參數將引用連接自己,而不是該連接指向的文件。 

  下表列出了本章所說明的各個函數是否處理符號連接,表中沒有列出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;
  在建立此符號連接時,並不要求actualpath已經存在。而且,actualpath和sympath並不須要位於同一文件系統中。由於open函數跟隨符號連接,因此須要有一種方法打開連接自己,並讀該連接中的名字。readlink函數提供了這種功能。
#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;
}
View Code

  下列例子爲深度遍歷文件層次結構

/*************************************************************************
    > 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;
}
View Code

GitHub:https://github.com/tianzengBlog/test/tree/master/test/dir

特殊設備文件

  st_dev和st_rdev這兩個字段常常引發混淆,有關規則以下:

  1. 每一個文件系統所在的存儲設備都由其主、次設備號表示。設備號所用的數據類型是基本系統數據類型dev_t。主設備號標識設備驅動程序,有時編碼爲於其通訊的外設扮;次設備號標識特定的子設備。
  2. 咱們一般使用兩個宏:major和minor來訪問主、次設備號,大多數實現都定義這兩個宏。這就意味着咱們無需關心這兩個數是如何存放在dev_t對象中的。
  3. 系統中與每一個文件名關聯的st_dev值是文件系統的設備號,該文件系統包含了這一文件名以及與其對應的i節點。
  4. 只有字符特殊設備和塊特殊設備纔有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));
    }

文件訪問權限位小結

相關文章
相關標籤/搜索