文件和目錄全解

第四章:文件和目錄

stat、fstat、fstatat、lstat函數:獲取文件信息
    #include <sys/stat.h>
    int stat(const char *restrict pathname,struct stat *restrict buf);
    int fstat(int fd,struct stat *buf);
    int lstat(const char *restrict pathname,struct stat *restrict buf);
    int fstatat(int fd,const char *restrict pathname,struct stat *restrict buf,int flag);
    一旦給出pathname,stat函數就返回與此命名文件有關的信息結構。
    fstat函數獲得已在描述符fd上打開文件有關信息。

    lstat函數類似於stat,但是當命名文件是一個符號鏈接是,lstat返回該符號銜接有關信息,而不是由
    符號銜接引用的文件信息。

    fstatat函數爲一個相對於當前打開目錄(由fd參數指定)的路徑名返回文件統計信息。
    flag參數控制着是否跟隨着一個符號銜接。
    當AT_SYMLINK_NOFOLLOW標誌被設置時,fstatat不會跟隨符號銜接,而是返回符號銜接本身的信息,
    否則,默認情況下,返回的是符號銜接所指向的實際文件信息。
    如果fd參數的值是AT_FDCWD,並且pathname是一個相對路徑名,fstatat會計算相對於當前目錄的
    pathname參數。如果pathname是一個絕對路徑名,fd參數就會被忽略。

    第二個參數buf是一個指針,它指向一個我們必須提供的結構。函數用來填充由buf指向的結構。結構的
    實際定義可能隨具體實現有所不同,但其基本形式是:
    struct stat{
    mode_t      st_mode;//文件類型和mode(權限)
    ino_t       st_ino;//索引節點號(序列號)
    dev_t       st_dev;//設備號(文件系統)
    dev_t       st_rdev;//特殊文件的設備編號
    nlink_t     st_nlink;//銜接數
    uid_t       st_uid;//使用者ID號
    gid_t       st_gid;//用戶組ID
    off_t       st_size;//字節大小,用於常規文件。
    struct timespec st_atime;//最後訪問時間
    struct timespec st_mtime;//最後修改時間
    struct timespec st_ctime;//最後狀態改變的時間。
    blksize_t   st_blksize;//最大的I/O的塊大小
    blkcnt_t    st_blocks;//分配的磁盤塊的數量
    };
    使用stat最多的地方就是ls -l命令,獲取有關文件所以信息

文件類型:
    stat結構中的st_mode成員:
        S_ISREG()   普通文件
        S_ISDIR()   目錄文件
        S_ISCHR()   字符特殊文件
        S_ISBLK()   塊特殊文件
        S_ISFIFO()  管道或FIFO
        S_ISLNK()   符號銜接
        S_ISSOCK()  套接字

    stat結構中取定IPC對象的類型:在<sys/stat.h>
        S_TYPEISMQ()    消息隊列
        S_TYPEISSEM()   信號量
        S_TYPEISSHM()   共享存儲對象
    示例:
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/stat.h>//用於stat結構

    int main(int argc,char *argv[]){
        int i;
        struct stat buf;
        char *ptr;

        //需要給與程序額外的參數
        for(i=1;i<argc;i++){
                printf("%s: ",argv[i]);
            //lstat函數獲得已在描述符fd上打開文件有關信息
            //它能返回符號銜接,而stat不能
                if(lstat(argv[i],&buf)<0){
                        printf("lstat error");
                        continue;
                }

        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_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 mode **";
        printf("%s\n",ptr);
        }
        exit(0);
    }

    實際上st_mode成員判斷文件爲:
    例如:S_ISDIR
    #define S_ISDIR (mode) (((mode) & S_IFMT) == S_IFDIR)
    在早期的版本中就是將st_mode與屏蔽字S_IFMT進行邏輯與運算,然後與名爲S_IF***的常量比較


設置用戶ID和設置組ID:
    stat中的S_ISUID和S_ISGID

文件訪問權限:
    st_mode屏蔽   含義
    S_IRUSR     用戶讀
    S_IWUSR     寫
    S_IXUSR     執行

    S_IRGRP     用戶組讀
    S_IWGRP     寫
    S_IXGRP     執行

    S_IROTH     其他讀
    S_IWOTH     寫
    S_IXOTH     執行

    chmod用於修改這9個權限位。
    u表示:用戶(所有者)
    g表示:組
    o表示:其他
有效ID和實際ID:
    1、實際用戶ID和實際用戶組ID:標識我是誰。
    也就是登錄用戶的uid和gid,比如我的Linux以simon登錄,
    在Linux運行的所有的命令的實際用戶ID都是simon的uid,
    實際用戶組ID都是simon的gid(可以用id命令查看)。

    2、有效用戶ID和有效用戶組ID:進程用來決定我們對資源的訪問權限。
    一般情況下,有效用戶ID等於實際用戶ID,有效用戶組ID等於實際用戶組ID。
    當設置-用戶-ID(SUID)位設置,則有效用戶ID等於文件的所有者的uid,
    而不是實際用戶ID;同樣,如果設置了設置-用戶組-ID(SGID)位,則有效
    用戶組ID等於文件所有者的gid,而不是實際用戶組ID。

新文件和目錄的所有權:
    新文件的用戶ID設置爲進程的有效用戶ID,而組ID如下:
    1、新文件的組ID可以是進程的有效組ID
    2、新文件的組ID可以是它所在目錄的組ID

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);
    兩個函數的返回值:若成功,返回0,失敗返回-1

    其中若測試文件是否存在,mode就爲F_OK;否則按下列常量的按位或:
        mode    說明
        R_OK    測試讀權限
        W_OK    寫
        X_OK    執行

    faccessat和access兩種情況相同:
    1、pathname參數爲絕對路徑
    2、fd參數爲AT_FDCWD而pathname參數爲相對路徑,否則faccessat計算相對於打開
    目錄(fd參數指向)的pathanme。

    flag參數可以用於改變faccessat的行爲,如果flag設置爲AT_EACCESS,訪問檢查是調用進程的
    有效用戶ID和有效組ID,而不是實際用戶ID和實際組ID。

    access函數示例:
        #include <unistd.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <fcntl.h>

        int  main(int argc,char *argv[]){
            if(argc != 2)
                printf("usage:a.out <pathname>");
            if(access(argv[1],R_OK)<0)
                printf("access error for %s \n",argv[1]);
            else
                printf("read access OK\n");
            if(open(argv[1],O_RDONLY)<0)
                printf("open error for %s \n",argv[1]);
            else
                printf("open for reading Ok\n");
            exit(0);
        }

umask函數:爲進程設置文件模式創建屏蔽字,並返回之前的值.(這是少數幾個沒有出錯返回函數中的一個)。
    #include <sys/stat.h>
    mode_t umask(mode_t cmask);
    其中的cmask參數是9個常量(S_IRUSR、S_IWUSR)等,按位或構成的。
    在文件模式創建屏蔽字中爲1的位,在文件mode中的相應位一定被關閉。

    下列中第一個,umask值爲0,創建第二個時,umask禁止所以組和其他用戶的訪問權限:
        #include <unistd.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <fcntl.h>

        #define RWRWRW (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)

        int main(int argc,char *argv[]){
            umask(0);
            if(creat("foo",RWRWRW)<0)
                printf("creat error for foo");
            umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
            if(creat("bar",RWRWRW)<0)
                printf("creat error for bar");
            exit(0);
        }
        測試:
        umask
        ls -l foo bar
        umask

    用戶可以設置umask值以控制他們所創建文件的默認權限。該值表示成八進制數,一位代表一種要屏蔽的權限。
    設置了相應位後,它所對應的權限就會被拒絕。
    常用的幾種umask值是002、022和027.
    (002:第一位是所有者,第二位是所屬組,第三位是其他用戶)
    (4:讀        2:寫     1:執行)
    002阻止其他用戶寫入你的文件。
    022阻止同組成員和其他用戶寫入你的文件。
    027阻止同組成員寫你的文件以及其他用戶讀、寫或者執行你的文件

    屏蔽位     含義
    0400        用戶讀
    0200        用戶寫
    0100        用戶執行
    0040        組讀
    0020        組寫
    0010        組執行
    0004        其他讀
    0002        其他寫
    0001        其他執行

chmod、fchmod和fchmodat函數:更改文件訪問權限
    #include <sys/stat.h>
    int chmod(const char *pathname,mode_t mode);
    int fchmod(int fd,mode_t mode);
    int fchmodat(int fd,const char *pathname,mode_t mode,int flag);

    chmod函數在指定的文件上進行操作

    fchmod函數則對已打開的文件進行操作

    fchmodat函數與chmod函數在下面兩種情況下是相同的:
    1、pathname參數爲絕對路徑
    2、fd參數取值爲AT_FDCWD,而pathname參數位相對路徑,否則fchmodat計算相對於打開目錄(由fd參數指向)
    的pathname。
    (當fd是AT_FDCWD的時候,表明是基於當前工作目錄下的pathname)

    flag參數可以用於改變fchmodat的行爲,當設置了AT_SYMLINK_NOFOLLOW標誌時,fchmodat並不會跟隨符號
    銜接。

    爲了改變一個文件的權限,進程的有效用戶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     其他執行

    演示umask函數:
    ls -l foo bar

粘着位:(S_ISVTX)
    如果一個可執行程序文件的這一位被設置了,那麼當該程序第一次被執行,在終止時,程序正文部分的
    一個副本仍被保存在交換區(程序的正文部分時機器指令)。這使得下次執行該程序時能較快地將其裝載
    入內存。
    如果一個目錄被設置了粘着位,只有對該目錄具有寫權限的用戶並且滿足下列條件之一,才能刪除或重
    命令該目錄下的文件:
        1、擁有此文件
        2、擁有此目錄
        3、是超級用戶
    目錄/tmp和/var/tmp是設置粘着位的典型候選者---任何用戶都可在這兩個目錄中創建文件。任一用戶
    對這兩個目錄的權限通常是讀、寫和執行。但是不應能刪除或重命名屬於其他人的文件,爲此在這兩個目錄
    的文件模式中都設置了粘着位。  

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);
    4個函數的返回值:若成功,返回0.若出錯,返回-1。

    除了引用的文件是符號銜接之外,這4個函數的操作類似。在符號銜接情況下,lchown和fchownat
    (設置了AT_SYMLINK_NOFOLLOW標誌)更改符號銜接本身的所有者,而不是該符號銜接所指向的文件的所有者。

    fchown函數改變fd參數指向的打開文件的所有者,既然它在一個已打開的文件上操作,就不能用於改變符號銜接
    的所有者。

    fchownat函數與chown或者lchown函數在下面有兩種情況下是相同的:
    1、pathname參數爲絕對路徑
    2、fd參數取值爲AT_FDCWD而pathname參數爲相對路徑
    在這兩種情況下,
    如果flag參數設置了AT_SYMLINK_NOFOLLOW標誌,fchownat與lchown行爲相同,
    如果flag參數清除了AT_SYMLINK_NOFOLLOW標誌,則fchownat與chown行爲相同。
    如果fd參數設置爲打開目錄的文件描述符,並且pathname參數是一個相對路徑名,fchownat函數計算
    相對於打開目錄的pathname。


文件長度:
    stat結構成員st_size表示已字節爲單位的文件長度。此字段只對普通文件、目錄文件和符號銜接有意義。

文件中的空洞:
    空洞是由所設置的偏移量超過文件尾端,並寫入某些數據後造成的。


文件截斷:
    有時候我們需要在文件尾端截去一些數據已縮短文件。將一個文件的長度截斷爲0是一個特例,在打開文件時
    使用O_TRUNC標誌可以做到這一點。爲了截斷文件愛你可以調用函數truncate和ftruncate。
    #include <unistd.h>
    int truncate(const char *pathname,off_t length);
    int ftruncate(int fd,off_t length);

    這兩個函數將一個現有文件長度截斷爲length。如果該文件以前的長度大與length,則超過length以外的
    數據就不再能訪問。如果以前的長度小於length,文件長度增加,在以前的文件尾端和新的文件尾端只見的數據
    將讀作0(也就是可能在文件中創建了一個空洞)。

文件系統:
    主要的信息:
    1、每個i節點中都有一個銜接計數,其值是指向該i節點的目錄項數。只有當銜接計數減少到0時,纔可刪除該文件
    (也就是可以釋放該文件佔用的數據塊)。這就是爲什麼“解除一個文件的銜接”操作並不總是意味着“釋放該文件
    佔用的磁盤塊”的原因。在stat結構中,銜接計數包含在st_nlink成員中,其基本系統數據類型時nlink_t.這種
    銜接類型是硬銜接。LINK_MAX指定了一個文件銜接數的最大值。

    2、另外一種銜接類型稱爲符號銜接。符號銜接文件的實際內容(在數據塊中)包含了該符號銜接所指向的文件的名字
    例如:
    rwxrwxrwx 1 root 7 sep 25 07:14 lib->usr/lib
    該i節點中的文件類型是S_IFLNK,於是系統知道這是一個符號銜接。

    3、i節點包含了文件有關的所有信息:文件類型、文件訪問權限位、文件長度、指向文件數據塊的指針等。
    stat結構中的大多數信息都取自i節點。只有兩項重要數據存放在目錄項中:文件名和i節點編號。其他的
    數據項(如文件名長度和目錄記錄長度)。i節點編號的數據類型是ino_t.

    4、因爲目錄項中的i節點編號指向同一文件系統中的相應i節點,一個目錄項不能指向另一個文件系統的
    i節點。這就是爲什麼ln命令(構造一個指向一個現有文件的新目錄項)不能跨越文件系統的原因。

    5、當在不更換文件系統的情況下爲一個文件重命名時,該文件的實際內容併爲移動,只需構造一個指向現有
    i節點的新目錄項,並刪除老的目錄項。銜接計數不會改變。例如,爲將文件/usr/lib/foo重命名爲/usr/foo
    如果目錄/usr/lib和/usr在同一文件系統中,則文件foo的內容無需移動。這就是mv命令的通常操作方式。

    注意在父目錄中的每一個子目錄都使該父目錄的銜接計數增加1

link、linkat、unlink、unlinkat、remove函數:
任何一個文件可以有多個目錄項指向其i節點。創建一個指向現有文件的銜接的方法時使用link函數或linkat函數。

    #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中的最後一個分量,路徑中的其他部分應當已經存在。

    對於linkat函數,現有文件時通過efd和existingpath參數指定的,新的路徑名時通過nfd和newpath參數指定
    的。默認情況下,如果兩個路徑名中的任一個是相對路徑,那麼它需要通過相對於對應的文件描述符進行計算。如果
    兩個文件描述符中的任一個設置爲AT_FDCWD,那麼相應的路徑名(如果它是相對路徑)就通過相對於當前目錄進行
    計算。如果任一路徑名是絕對路徑,相應的文件描述符參數就會被忽略。

    當現有文件是符號銜接時,由flag參數控制linkat函數時創建指向現有符號銜接的銜接(重點是現有符號)還是創建
    指向現有符號銜接所指向的文件的銜接(銜接的文件)。
    如果在flag參數設置了AT_SYMLINK_FOLLOW標誌,就創建指向符號銜接目標的銜接。
    如果這個標誌被清除了,則創建一個指向符號銜接本身的銜接。

    創建新目錄項和增加銜接計數應當是一個原子操作。

爲了刪除一個現有的目錄項,可以調用unlink函數。
    #include <unistd.h>
    int unlink(const char *pathname);
    int unlinkat(int fd,const char *pathname,int flag);
    兩個函數的返回值:若成功,返回0;若出錯,返回-1

    這兩個函數刪除目錄項,並將由pathname所引用文件的銜接計數減1。如果對該文件還有其他銜接,則仍可通
    過其他銜接訪問該文件的數據,如果出錯,則部隊該文件做任何更改。

    我們在前面已經提及,爲了解除對文件的銜接,必須對包含該目錄項的目錄具有寫和執行權限。如果對該目錄
    設置了粘着位,則對該目錄必須具有寫權限,並且具備下面3個條件之一:
        1、擁有該文件
        2、擁有該目錄
        3、具有超級用戶權限
    只有當銜接計數達到0時,該文件的內容纔可被刪除。另一個條件也會阻止刪除文件的內容---只要有進程打開了該
    文件,其內容也不能刪除。關閉一個文件時,內核首先檢查打開該文件的進程個數;如果這個計數達到0,內容再去
    檢查其銜接計數;如果計數也是0,那麼就刪除該文件的內容。

    如果pathname參數是相對路徑名,那麼unlinkat函數計算相對於由fd文件描述符參數代表的目錄的路徑名。如果
    fd參數設置了AT_FDCWD,那麼通過相對於調用進程的當前工作目錄來計算路徑名。如果pathname參數是絕對路徑名
    那麼fd參數被忽略。

    flag參數給出了一種方法,是調用進程可以改變unlinkat函數的默認行爲。當AT_REMOVEDIR標誌被設置時,
    unlinkat函數可以類似於rmdir一樣刪除目錄。如果這個標誌被清除,unlinkat與unlink執行同樣的操作。


    我們可以使用remove函數解除對一個文件或目錄的銜接。
    對於文件,remove的功能與unlink相同。
    對於目錄,remove的功能與rmdir相同。
    #include <stdio.h>
    int remove(const char *pathname);


rename和renameat函數:
    文件或者目錄可以用rename函數或者renameat函數進行重命名。
    #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。

    根據oldname指的文件、目錄還是符號鏈接,由幾種情況:
    1、如果oldname指的是一個文件而不是目錄,那麼爲該文件或者符號銜接重命名。在這種情況下,如果newname
    已存在,則它不能引用一個目錄。如果nawname已存在,而且不是一個目錄,則先將該目錄項刪除然後將oldname
    重命名爲newname。對包含oldname的目錄以及包含newname的目錄,調用進程必須具有寫權限,因爲將更改這
    兩個目錄。

    2、如果oldname指的是一個目錄,那麼爲該目錄重命名。如果newname已存在,則它必須引用一個目錄,而且該
    目錄應當是空目錄(空目錄指的是該目錄中只有.和..項)。如果newname存在(並且是一個空目錄),則先將其
    刪除,然後將oldname重命名爲newname。另外,當爲一個目錄重命名時,newname不能包含oldname作爲其
    路徑前綴。例如,不能將/usr/foo重命名爲/usr/foo/testdir,因爲舊名字(/usr/foo)是新名字的路徑前綴
    因而不能將其刪除。

    3、如若oldname或者newname引用符號銜接,則處理的是符號銜接本身,而不是它所引用的文件

    4、不能對.和..重命名。就不說,不能讓.和..出現在oldname和newname最後的部分。

    5、作爲一個特例,如果oldname和newname引用同一個文件,則函數不做任何更改返回成功。

    renameat函數:
    如果oldname參數指定了一個相對路徑,就相對於oldfd參數引用的目錄來計算oldname。
    類似的,如果newname指定了相對路徑,就相對於newfd引用的目錄來計算newname。
    newfd和oldfd參數都能設置成AT_FDCWD,此時相對於當前目錄來計算相應的路徑名。

符號銜接:
    它是對一個文件的間接的指針。
    它與硬銜接不同,硬銜接直接指向文件的i節點。引入符號銜接的原因是爲了避開硬銜接的一些限制。
    1、硬銜接通常要求銜接和文件位於同一文件系統中
    2、只有超級用戶才能創建指向目錄的硬銜接(在底層文件系統支持的情況下)。

    對符號銜接以及它指向何種對象並無任何文件系統限制,任何用戶都可以創建指向目錄的符號銜接。
    符號銜接一般用於將一個文件或整個目錄結構移到系統中的另外一個位置。

    例如:
        mkdir foo
        touch foo/a
        ln -s ../foo foo/testdir
        ls -l foo


    另外一個例子:
        ln -s /no/such/file myfile  並不要求/no/such/file存在
        ls myfile
        cat myfile
        ls -l myfile

創建和讀取符號銜接:
    可以使用symlink或者symlinkat函數創建一個符號銜接:
    #include <unistd.h>
    int symlink(const char *actualpath,const char *sympath);
    int symlinkat(const char *actualpath,int fd,const char *sympath);
    兩個函數的返回值:若成功,返回0,失敗返回-1.

    函數創建一個指向actualpath的新目錄項sympath。在創建此符號銜接時,並不要求
    actualpath已經存在,並且actualpath和sympath並不需要爲與同一文件系統中。

    symlinkat函數和symlink函數類似,但sympath參數根據相對於打開文件描述符引用的目錄(由fd參數指定)進行
    計算。如果sympath參數指定的是絕對路徑或者fd參數設置了AT_FDCWD值,那麼symlinkat就等同於symlink函數

    因爲open函數跟隨符號銜接,所以需要有一種方法打開該銜接本身,並讀該銜接中的名字。readlink和
    readlinkat函數提供了這種功能:
    #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);

    兩個函數組合了open、read和close的所以操作。如果函數成功執行,則返回讀入buf的字節數。在buf中返回的符號銜接
    的內容不以null字節終止。

    當pathname參數指定的是絕對路徑名或者fd參數的值爲AT_FDCWD,readlinkat函數的行爲和readlink相同。
    但是,如果fd參數是一個打開目錄的有效文件描述符並且pathname參數是相對路徑名,則readlinkat計算相對於
    由fd代表的打開目錄的路徑名。


文件的時間:
    對每個文件維護3個時間字段,他們的意義:
    字段      說明          例子      ls(l)選項
    st_atim     文件數據的最後訪問時間 read        -u
    st_mtim     文件數據的最後修改時間 write       默認
    st_ctim     i節點狀態的最後更改時間    chmod、chown -c

    注意,修改時間(st_mtim)和狀態更改時間(st_ctim)之間的區別。
    修改時間是文件內容最後一次被修改的時間。
    狀態更改時間是該文件的i節點最後一次被修改的時間。
    影響到i節點的操作,如更改文件的訪問權限、更改用戶ID、更改銜接數等,但它們並沒有更改文件的實際內容。

    系統並不維護對一個i節點的最後一次訪問時間,所以access和stat函數並不更改這3個時間中的任一個。

    系統管理員常常使用訪問時間來刪除在一定時間範圍內沒有被訪問過的文件。典型的例子就是刪除在過去一週內沒有
    訪問過的名爲a.——ut或core的文件。find()命令常被用來進行這種類型的操作。

    修改時間和狀態更改時間可被用來歸檔那些內容已經被修改或i節點已經被更改的文件。

    ls命令按這3個時間值中的一個排序進行顯示的。系統默認(用-l或-t選項調用時)是按文件的修改時間的先後排序
    顯示。-u選項使ls命令按訪問時間排序,-c選項則使其狀態更改時間排序。

futimens、utimensat和utimes函數:
    一個文件的訪問和修改時間可以使用下面的函數修改:
    futimens和utimensat函數可以指定納秒級精度的時間戳。
    用到的數據結構是於stat函數族相同的timespec結構。
    #include <sys/stat.h>
    int futimens(int fd,const struct timespec times[2]);
    int utimensat(int fd,const char *pathname,const struct timespec times[2],int flag);
    返回值:若成功,返回0,若出錯,返回-1。

    struct timespec{
    __time_t tv_sec;  /*seconds 秒*/
    long int tv_nsec; /*nanoseconds 納秒*/
    }

    這兩個函數的times數組參數的第一個元素包含訪問時間,第二個包含的是修改時間。
    這兩個時間值是日曆時間。

    時間戳可以按下列4種方式之一進行指定:
    1、如果times參數是一個空指針,則訪問時間和修改時間兩者都設置爲當前時間
    2、如果times參是是指向兩個timespec結構的數組,任一數組元素的tv_nsec字段的值
    爲UTIME_NOW,相應的時間戳就設置爲當前時間,忽略相應的tv_sec字段。
    3、如果times參數指向兩個timespec結構的數組,任一數組元素的tv_nsec字段的值
    爲UTIME_OMIT,相應的時間戳保持不變,忽略相應的tv_sec字段。
    4、如果times參數指向兩個timespec結構的數組,且tv_nsec字段的值不是UTIME_NOW
    和UTIME_OMIT,在這種情況下,相應的時間戳設置爲相應的tv_sec和tv_nsec字段的值

    執行這些函數所要求的優先權取決於times參數的值。
        1、如果times是一個空指針,或者任一tv_nsec字段設爲UTIME_NOW,則進程的有效用戶ID
        必須等於該文件的所有者ID;進程對該文件必須具有寫權限,或者進程是一個超級用戶進程。
        2、如果times是一個空指針,並且任一tv_nsec字段不是UTIME_NOW也不是UTIME_OMIT,
        則進程的有效用戶ID必須等於該文件的所有者ID,或者進程必須是一個超級用戶進程。對文件
        具有寫權限是不夠的。
        3、如果times是非空指針,並且兩個tv_nsec字段的值都爲UTIME_OMIT,就不執行任何的權限
        檢查。

    futimens函數需要打開文件來更改它的時間,utimensat函數提供了一種使用文件名更改文件時間的方法。
    pathname參數是相對於fd參數進行計算的,fd要麼是打開目錄的文件描述符,要麼設置爲特殊值AT_FDCWD
    (強制通過相對於調用進程的當前目錄計算pathname)。如果pathname指定了絕對路徑,那麼fd參數被忽略。

    utimesat的flag參數可用於進一步修改默認行爲。如果設置了AT_SYMLINK_NOFOLLOW標誌,則符號銜接
    本身的時間就會被修改(如果路徑名指向符號銜接)。默認的行爲是跟隨符號銜接,並把文件的時間改成符號
    銜接的時間。

    futimens和utimensat函數都包含在POSIX.1中,第三個參數utimes包含在Single UNIX Specification
    中:
    #include <sys/time.h>
    int utimes(const char *pathname,const struct timeval times[2]);
        函數的返回值:若成功,返回0,若失敗,返回-1 

    utimes函數對路徑名進行操作。times參數是指向包含兩個時間戳(訪問時間和修改時間)
    元素的數組的指針,兩個時間戳是用秒和微妙表示的。

    struct timeval  
    {  
    __time_t tv_sec;        /* 秒。 */  
    __suseconds_t tv_usec;  /* 微秒。 */  
    };  

    注意,我們不能對狀態更改時間st_ctim(i節點最近被修改的時間)指定一個值,因爲調用utimes
    函數時,此字段會被自動更新。


    下列:
    程序使用帶O_TRUNC選項的open函數將文件長度截斷爲0,當並不更改其訪問時間和修改時間。
    爲了做到這一點,首先用stat函數得到這些時間,然後截斷文件,最後再用futimens函數重置
    這兩個時間。

        #include <unistd.h>
        #include <stdio.h>
        #include <stdlib.h>
        #include <fcntl.h>

        int main(int argc,char *argv[])
        {
            int i,fd;
            struct stat statbuf;
            struct timespec times[2];

            for(i=1;i<argc;i++){
                if(stat(argv[i],&statbuf)<0){
                        printf("%s: stat error",argv[i]);
                        continue;
                }

                if((fd=open(argv[i],O_RDWR | O_TRUNC))<0){
                        printf("%s: open error",argv[i]);
                }

                times[0]=statbuf.st_atim;
                times[1]=statbuf.st_mtim;
                if(futimens(fd,times)<0){
                        printf("%s: futimens error",argv[i]);
                }
                close(fd);
            }
            exit(0);
        }
                執行:
        touch times
        ls -l times     查看長度和最後修改時間      
        ls -lu times    查看最後訪問時間
        date        當前時間
        ./a times   執行
        ls -l times 檢查結果
        ls -lu times    檢查最後訪問時間
        ls -lc times    檢查狀態更改時間

        如果正確:最後修改時間和最後訪問時間未變。但是狀態更改時間則更改爲程序運行時的時間。

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);
        兩個函數返回值:若成功,返回0;若出錯,返回-1。

    這兩個函數創建一個新的空目錄。其中.和..目錄項是自動創建的。所指定的文件訪問權限mode由進程的
    文件模式創建屏蔽字修改。

    常見的錯誤是指定與文件相同的mode(指定讀、寫權限)。但是,對目錄目錄通常至少要設置一個執行權限爲,以
    允許訪問該目錄中的文件名。

    在早期版本中,進程要調用mknod函數創建一個新目錄,但是隻有超級用戶進程才能使用mknod函數。爲了避免
    這一點,創建目錄的命令mkdir必須由跟用戶擁有,而且對它設置了設置用ID位。要通過一個進程創建一個目錄
    必須用system函數調用mkdir命令。   

    mkdirat函數與mkdir函數類似。當fd參數具有特殊值AT_FDCWD或者pathname參數指定了絕對路徑名時,
    mkdirat與mkdir完全一樣。否則,fd參數是一個打開目錄,相對路徑名根據此打開目錄進行計算。

    用rmdir函數可以刪除一個空目錄。空目錄是隻包含.和..這兩項的目錄。
    #include <unistd.h>
    int rmdir(const char *pathname);

    如果調用此函數使目錄的銜接計數成爲0,並且也沒有其他進程打開此目錄,則釋放由此目錄佔用的空間。
    如果在銜接數達到0時,有一個或多個進程打開此目錄,則在此函數返回前刪除最後一個銜接及.和..項。
    另外,在此目錄中不能再創建新文件。但是在最後一個進程關閉它之前並不釋放此目錄。
    即使一些進程打開該目錄,它們在此目錄下也不能執行其他操作。這樣處理的原因時,爲了使rmdir函數
    成功執行,該目錄必須時空的。

讀目錄:
    對某個目錄具有訪問權限的任一用戶都可以讀該目錄,但是,爲了防止文件系統產生混亂,只有內核才能
    寫目錄。一個目錄的寫權限位和執行權限位決定了在該目錄中能否創建新文件以及刪除文件,它們並不
    代表能夠寫目錄本身。

    在早期的文件系統中:每個目錄項時16個字節,其中14個字節時文件名,2個字節時i節點編號。

    #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);

    fdopendir函數最早出現在SUSv4中,它提供了一種方法,可以把打開文件描述符轉換爲目錄處理函數需要的DIR結構。

    telldir和seekdir函數不是基本POSIX.1標準的組成部分。特門是Single UNIX Specification中的XSI擴展,
    所以可以期望所以符合UNIX系統的實現都會提供這兩個函數。

    定義在頭文件<dirent.h>中的dirent結構與實現有關。
    struct dirent
    {
        long d_ino; /* inode number 索引節點號 */
        off_t d_off; /* offset to this dirent 在目錄文件中的偏移 */
        unsigned short d_reclen; /* length of this d_name 文件名長 */
        unsigned char d_type; /* the type of d_name 文件類型 */
        char d_name [NAME_MAX+1]; /* file name (null-terminated) 文件名,最長256字符 */
    }
    相關函數
    opendir(),readdir(),closedir();

    編寫一個遍歷文件層次結構的程序,其目的是得到各種類型的文件計數。
    Solaris提供了一個遍歷此層次結構的函數ftw(3),對於每一個文件它都調用一個用戶定義的函數。
    ftw函數的問題是:對於每一個文件,它都調用stat函數,這就是使程序跟隨符號銜接。
    例如,如果從根目錄(root)開始,並且有一個名爲/lib的符號銜接,它指向/ust/lib,則所有在目錄
    /usr/lib中的文件都會被計數兩次。爲了糾正就一點,Solaris提供了另一個函數nftw(3),它具有一個
    停止跟隨符號銜接的選項。

        #include <unistd.h>
        #include <dirent.h>
        #include <limits.h>
        #include <sys/stat.h>
        #include <stdlib.h>
        #include <stdio.h>

        char *path_alloc(size_t size)
        {
            if (size == 0)
            return (char *)malloc(PATH_MAX);
            else
            return (char *)malloc(size);
        }

        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)
                printf("usage: ftw <starting-pathname>");

            ret=myftw(argv[1],myfunc);
            ntot=nreg+ndir+nblk+nchr+nfifo+nslink+nsock;
            if(ntot==0)
                ntot=1;
            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("FIFOS         = %7ld, %5.2f %%\n",nfifo,nfifo*100.0/ntot);
            printf("symboloc links = %7ld, %5.2f %%\n",nslink,nslink*100.0/ntot);
            printf("sockets         = %7ld, %5.2f %%\n",nsock,nsock*100.0/ntot);

            exit(ret);

        }

        #define FTW_F 1
        #define FTW_D 2
        #define FTW_DNR 3
        #define FTW_NS 4

        static char *fullpath;
        static size_t pathlen;

        static int myftw(char *pathname,Myfunc *func)
        {
            fullpath=path_alloc(&pathlen);

            if(pathlen<=strlen(pathname))
            {
                pathlen=strlen(pathname)*2;
                if((fullpath=realloc(fullpath,pathlen))==NULL)
                        printf("realloc failed");
            }

            strcpy(fullpath,pathname);
            return(dopath(func));
        }

        static int dopath(Myfunc *func)
        {
            struct stat statbuf;
            struct dirent *dirp;
            DIR *dp;
            int ret,n;

            if(lstat(fullpath,&statbuf)<0)
                return(func(fullpath,&statbuf,FTW_NS));

            if(S_ISDIR(statbuf.st_mode)==0)
                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){
                pathlen*=2;
                if((fullpath=realloc(fullpath,pathlen))==NULL)
                        printf("realloc failed");
            }
            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);
                if((ret=dopath(func))!=0)
                        break;
            }
            fullpath[n-1]=0;
            if(closedir(dp)<0)
                printf("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: printf("for S_IFDIR for %s",pathname);
                }
                break;
            case FTW_D:
                ndir++;
                break;
            case FTW_DNR:
                printf("can't read directory %s",pathname);
                break;
            case FTW_NS:
                printf("stat error for %s",pathname);
            default:
                printf("unknown type %d for pathname %s",type,pathname);

          }
            return(0);
        }


chdir、fchdir和getcwd函數:
    每個進程都有一個當前工作目錄,此目錄是搜索所有相對路徑名的起點(不以斜線開始的路徑名爲相對路徑名)。
    當用戶登陸到UNIX系統的時候,其當前工作目錄通常是口令文件(/etc/passwd)中該用戶登陸項的第6個字段
    ----用戶的起始目錄(home directory)。當前工作目錄是進程的一個屬性,起始目錄則是登陸名的一個屬性。
    進程調用chdir或fchdir函數可以更改當前工作目錄:
    #include <unistd.h>
    int chdir(const char* pathname);
    int fchdir(int fd);
    在這兩個函數中,分別用pathname或打開文件描述符來指定新的當前工作目錄。

    因爲當前工作目錄是進程的一個屬性,所以它隻影響調用chdir的進程本身,而不影響其他進程。這就意味者程序並
    不會產生我們可能希望得到的結果。

    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>

    int main()
    {
        if(chdir("/tmp")<0)
                printf("chdir failed");
        printf("chdir to /tmp successed\n");
        exit(0);
    }

    執行:
    pwd
    ./chdir
    pwd

    從上可以看出,執行./chdir命令的shell當前工作目錄並沒有改變,這是shell執行程序工作方式的一個副作用。
    每個程序運行在獨立的進程中,shell的當前工作目錄並不會隨着程序調用chdir而改變。由此可見,爲了該改變
    shell進程自己的工作目錄,shell應當直接調用chdir函數,爲此,cd命令內建在shell中。

    因爲內核必須維護當前工作目錄的信息,所以我們應能獲取其當前值。遺憾的是,內核爲每個進程只保存指向該目錄
    v節點的指針等目錄本身的信息,並不保存該目錄的完整路徑名。

    獲得當前目錄完整的絕對路徑名:
    #include <unistd.h>
    char *getcwd(char *buf,size_t size);
    必須向此函數傳遞兩個參數,一個是緩衝區地址buf,另一個是緩衝區的長度size。
    這緩衝區必須有足夠的長度以容納絕對路徑名再加上一個終止null字節,否則返回出錯。

        #include <unistd.h>
        #include <stdio.h>
        #include <stdlib.h>
        #define PATH_MAX 100

        char *path_alloc(size_t size)
        {
            if(size==0)
                return (char*)malloc(PATH_MAX);
            else
                return (char*)malloc(size);
        }

        int main()
        {
            char *ptr;
            size_t size;

            if(chdir("/usr/spool/uucppublic")<0)
                printf("chdir failed");
            ptr=path_alloc(size);
            if(getcwd(ptr,size)==NULL)
                printf("getcwd failed");


         printf("cwd=%s\n",ptr);
            exit(0);
        }
    一般要返回到出發點是,先使用getcwd函數保存其值,再完成處理後,就可以將保存的原工作路徑名作爲調用參數傳
    送給chdir。
    而fchdir,它是使用open打開當前工作目錄,然後保存其返回的文件描述符。當希望回到原工作目錄時,只要簡單地
    將該文件描述符傳送給fchdir。

設備特殊文件:
    st_dev和st_rdev這兩個字段經常引起混淆。我們編寫ttyname函數時,需要使用這兩個字段。
    1、每個文件系統所在的儲存設備都由其主、次設備號表示。設備號所用的數據類型時基本系統數據類型dev_t。主設備號
    標識設備驅動程序,由是編碼爲與其通信的外設板;此設備號標識特定的子設備。一個磁盤驅動器經常包含若干個文件系統。
    在同一磁盤驅動器上的各文件系統通常具有相同的主設備號,但是次設備號卻不同。

    2、我們通常使用兩個宏:major和minor來訪問主、次設備號,大多數實現都定義這兩個宏。這就意味着我們無需關心這
    兩個數是如何存放在dev_t對象中。

    3、系統中與每個文件名關聯的st_dev值是文件系統的設備號,該文件系統包含了這一文件名以及與其對應的i節點。

    4、只有字符特殊文件和塊特殊文件纔有st_rdev值。此值包含實際設備的設備號。

    程序爲每個命令行參數打印設備號,另外,若此參數引用的是字符特殊文件或者塊特殊文件,則還打印該特殊文件的
    st_rdev值。


    例子:
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <sys/stat.h>//用於使用stat結構

    #ifdef SOLARIS  //solaris在<sys/mkdev.h>中定義了它們的函數原型。
    #include <sys/mkdev.h>
    #endif

    int main(int argc,char *argv[])
    {
        int i;
        struct stat buf;
        for(i=1;i<argc;i++){
                printf("%s:",argv[i]);
                if(stat(argv[i],&buf)<0)
                {
                        printf("stat error");
                        continue;
                }
            //其中的major是訪問主設備號,minor是訪問次設備號
                printf("dev=%d/%d",major(buf.st_dev),minor(buf.st_dev));//文件系統設備號
            //如果是字符特殊文件或者塊設備文件,它們由st_rdev值
            //st_rdev;//特殊文件的設備編號
                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));
                }
                printf("\n");
        }
        exit(0);
    }
    執行:
    ./dev / /home

文件訪問權限位小結:
    S_IRWXU=S_IRUSR | S_IRWUSR | S_IRXUSR
    S_IRWXG=S_IRGRP | S_IRWGRP | S_IRXGRP
    S_IRWXO=S_IROTH | S_IRWOTH | S_IRXOTH

    常量  說明      對普通文件的影響        對目錄文件的影響    
    S_ISUID 設置用戶ID  執行設置有效用戶ID  無
    S_ISGID 設置組ID       若組位設置,則執行時  將在目錄中創建的新的
                設置有效組ID,否則使 文件組ID設置爲目錄組ID
                強制性所起作用(若支持)
    S_ISVTX 粘着位     在交換緩存程序正文(若支持)  限制目錄中刪除和重命名文件

    S_IRUSR 用戶讀     用戶可讀文件      用戶可讀目錄
    S_IWUSR 用戶寫     用戶可寫文件      用戶可刪除和創建文件
    S_IXUSR 用戶執行        用戶可執行文件     用戶可在目錄中搜索給定路徑名

    S_IRGRP 組讀      組可讀文件       組可讀目錄
    S_IWGRP 組寫      組可寫文件       組可刪除和創建文件
    S_IXGRP 組執行     組可執行文件      組可搜索給定路徑名

    S_IROTH 其他讀     其他讀文件       其他可讀目錄
    S_IWOTH 其他寫     其他寫文件       其他可刪除和創建文件
    S_IXOTH 其他執行        其他執行文件      其他可在目錄中搜索給丁路徑名
發佈了50 篇原創文章 · 獲贊 21 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章