天天看點

《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄

檔案和目錄

一、 stat 結構和權限相關

  1. 四個

    stat

    函數:傳回檔案或者目錄的資訊結構:
    #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

        :檔案或者目錄的名字
      • buf

        :存放資訊結構的緩沖區
      • fd

        :打開的檔案描述符
        • 對于

          fstat

          ,該檔案就是待檢視資訊的檔案
        • 對于

          fstatat

          ,該檔案是并不是待檢視資訊的檔案。待檢視資訊的檔案時已該

          fd

          對于的目錄相對路徑定位的
      • flag

        :控制着

        fstatat

        函數是否跟随一個符号連結。
      對于

      fstatat

      函數:
      • 待檢視的檔案名是由

        fd

        pathname

        共同決定的。
        • 如果

          pathname

          是個絕對路徑,則忽略

          fd

          參數
        • 如果

          pathname

          是個相對路徑路徑,且

          fd=AT_FDCWD

          ,則在目前工作目錄的路徑下查找

          pathname

        • 如果

          pathname

          是個相對路徑路徑,且

          fd!=AT_FDCWD

          ,則在

          fd

          對應的打開目錄下查找

          pathname

      • flag

        :控制着

        fstatat

        函數是否跟随一個符号連結。當

        !AT_SYMLINK_FOLLOW

        标志被設定時,檢視的是

        pathname

        (如果它是個符号連結)本身的資訊;否則預設檢視的是

        pathname

        (如果它是個符号連結)連結引用的檔案的資訊。
    • 傳回值:
      • 成功:傳回 0
      • 失敗: 傳回 -1
    注意:
    • lstat

      類似于

      stat

      ,但是當

      pathname

      是個符号連結時,

      lstat

      檢視的是該符号連結的有關資訊;而

      stat

      是檢視該符号連結引用的檔案的資訊。
    • ubuntu 16.04

      上,雖然有

      AT_SYMLINK_NOFOLLOW

      這個常量,但是不支援。必須用

      !AT_SYMLINK_FOLLOW

      。其常量定義為:
      • AT_SYMLINK_FOLLOW

        : 1024 (有效)
      • !AT_SYMLINK_FOLLOW

        : 0(有效)
      • AT_SYMLINK_NOFOLLOW

        : 256(無效)
      • AT_SYMLINK_FOLLOW

        : -1025(無效)
  2. stat

    資料結構:其定義可能與具體作業系統相關,但是基本形式為:
    struct stat{
    mode_t          st_mode;    //檔案權限和類型資訊
    ino_t           st_ino;     //i-node 号
    dev_t           st_dev;     // 裝置号
    dev_t           st_rdev;    // 特殊檔案的裝置号
    nlink_t         st_nlink;   // 硬連結數量
    uid_t           st_uid;     // owner 的使用者ID
    gid_t           st_gid;     // owner 的組ID
    off_t           st_size;    //對普通檔案,它是檔案位元組大小
    struct timespec st_atime;   // 上次通路時間
    struct timespec st_mtile;   // 上次修改時間
    struct timespec st_ctime;   // 上次檔案狀态改變的時間
    blksize_t       st_blksize; // 最佳的 I/O block 大小
    blkcnt_t        st_blocks;  //配置設定的磁盤塊數量
    }
               
    其中

    timespec

    結構與具體作業系統相關,但是至少包括下面兩個字段:
    struct timespec{
    time_t tv_sec;  // 秒
    long tv_nsec;   //納秒
        }
               
  3. UNIX 檔案類型:
    • 普通檔案:最常見的檔案類型,這種檔案包含了某種形式的資料。至于這種資料是二進制還是文本,對核心無差別。普通檔案的内容解釋由具體的應用程式進行。
    • 目錄檔案:這種檔案包含了其他檔案的名字,以及指向這些檔案有關資訊的指針。
      • 隻有核心可以直接寫目錄檔案(通常使用者寫目錄檔案都要通過核心)
      • 對某個目錄檔案具有讀權限的任何一個程序都可以讀取該目錄的内容
    • 塊特殊檔案:這種類型的檔案提供對裝置(如磁盤)帶緩沖的通路。每次通路以固定長度為機關進行。
    • 字元特殊檔案:這種類型的檔案提供對裝置不帶緩沖的通路,每次通路長度可變。
    系統的所有裝置,要麼是字元特殊檔案,要麼是塊特殊檔案
  4. FIFO

    :這種類型的檔案用于程序間通信,有時也稱為命名管道
  5. 套接字:這種類型的檔案用于程序間的網絡通信(也可用于單機上程序的非網絡通信)
  6. 符号連結:這種類型的檔案指向另一個檔案
  7. 檔案類型資訊存放在

    stat.st_mode

    成員中,可以用下列的宏測試檔案類型:
    • S_ISREG()

      :測試是否普通檔案
    • S_ISDIR()

      :測試是否目錄檔案
    • S_ISCHR()

      :測試是否字元特殊檔案
    • S_ISBLK()

      :測試是否塊特殊檔案
    • S_ISFIFO()

      :測試是否

      FIFO

    • S_ISLNK()

      :測試是否符号連結檔案
    • S_ISSOCK()

      :測試是否套接字
    另外

    POSIX.1

    允許将程序間通信對象說明為檔案。但是下面的宏測試的不是

    stat.st_mode

    ,而是

    stat*

    stat

    指針):
    • S_TYPEISMQ()

      :測試是否消息隊列
    • S_TYPEISSEM()

      :測試是否信号量
    • S_TYPEISSHM()

      :測試是否共享存儲對象
  8. 與一個程序有關的ID有很多:
    • 實際使用者 ID 和實際組 ID: 标志我們究竟是誰。當我們登入進作業系統時,這兩個值就确定了!
    • 有效使用者 ID、有效組ID、附屬組 ID: 用于檔案通路權限檢查。
    • 儲存的設定使用者ID、儲存的設定組ID:由

      exec

      函數儲存
    每個檔案都有一個所有者群組所有者,分别有

    stat.st_uid

    stat.st_gid

    指定。當一個檔案時可執行檔案時,如果執行這個檔案,那麼程序的有效使用者ID就是實際使用者ID,有效組ID就是實際組ID,除了下面的情況:
    • 當在

      stat.st_mode

      中設定了一個特殊标志:設定使用者ID位時,則将程序的有效使用者ID設定為檔案所有者的使用者ID
    • 當在

      stat.st_mode

      中設定了一個特殊标志:設定組ID位時,則将程序的有效組ID設定為檔案所有者的組ID
      任何程序都是由可執行檔案被執行而得到。是以位于磁盤上的可執行檔案的所屬的使用者ID群組ID會影響到程序的使用者ID群組ID
    如果某個可執行檔案所有者是

    root

    ,且該檔案的設定使用者ID位已經被設定,那麼無論誰執行這個可執行檔案時,該可執行檔案産生的程序就具有超級使用者權限。 設定使用者ID位、設定組ID位 都包含在`stat.st_mode`中,可以通過下列兩個宏測試:
    • S_ISUID()

      :測試是否設定了設定使用者ID位
    • ·S_ISGID()`:測試是否設定了設定組ID位
  9. 檔案通路權限:所有檔案類型(包括目錄,字元特别檔案等)都有通路權限。每個檔案都有9個通路權限位:
    • S_IRUSR

      :使用者讀
    • S_IWUSR

      :使用者寫
    • S_IXUSR

      :使用者執行
    • S_IRGRP

      :組讀
    • S_IWGRP

      :組寫
    • S_IXGRP

      :組執行
    • S_IROTH

      :其他讀
    • S_IWOTH

      :其他寫
    • S_IXOTH

      :其他執行
    通路權限規則:
    • 當用名字

      pathname

      打開任何一個類型的檔案時,對

      pathname

      中包含的每一個目錄,包括

      pathname

      可能隐含的目前工作目錄都應該具有執行權限
      是以目錄的執行權限位也稱之為搜尋位
    • 對一個檔案的讀權限決定了我們能否打開現有檔案進行讀操作
    • 對一個檔案的寫權限決定了我們能否打開現有檔案進行寫操作
    • 如果你在

      open

      函數中對一個檔案指定了

      O_TRUNC

      标志,則必須對該檔案具有寫權限
    • 為了在一個目錄中常見一個新檔案,必須對該目錄具有寫權限和執行權限
    • 為了删除一個現有檔案,必須對包含該檔案的目錄具有寫權限和執行權限。對該檔案本身沒有權限的限制
    • 如果用7個

      exec

      函數中的任何一個執行某個檔案,則必須對該檔案具有執行權限,且該檔案必須是個普通檔案
    程序每次打開、建立、删除一個檔案時,核心就進行檔案通路權限測試。這種測試如下:
    • 若程序的有效使用者ID是0(超級使用者),則對該檔案的任何通路行為都準許
    • 若程序的有效使用者ID等于檔案的所有者ID(也就是程序擁有此檔案):
      • 如果該檔案的使用者讀權限開放,則核心允許程序讀該檔案
      • 如果該檔案的使用者寫權限開放,則核心允許程序寫該檔案
      • 如果該檔案的使用者執行權限開放,則核心允許程序執行該檔案
    • 若程序的有效組ID或者程序的附屬組ID之一等于檔案的組ID:
      • 如果該檔案的組讀權限開放,則核心允許程序讀該檔案
      • 如果該檔案的組寫權限開放,則核心允許程序寫該檔案
      • 如果該檔案的使用者執行權限開放,則核心允許程序執行該檔案
    • 否則:
      • 如果該檔案的其他讀權限開放,則核心允許程序讀該檔案
      • 如果該檔案的其他寫權限開放,則核心允許程序寫該檔案
      • 如果該檔案的其他戶執行權限開放,則核心允許程序執行該檔案
    隻要有一個權限通過,則不再進行測試。若所有權限都不通過,則不允許通路。
  10. 對一個目錄的讀權限和可執行權限是不同的:
    • 目錄讀權限:允許讀目錄,進而獲得在該目錄中所有檔案名的清單
    • 目錄可執行權限:允許搜尋該目錄,進而尋找一個特定的檔案名
  11. 當一個程序通過

    open

    或者

    creat

    建立一個新檔案時:
    • 新檔案的使用者ID被設定為程序的有效使用者ID
    • 新檔案的組ID可以有兩個值之一:
      • 程序的有效組ID
      • 檔案所在目錄的組ID
    具體選擇哪個,由具體作業系統決定
  12. 示例
    #include <stdio.h>
    
    
    #include<sys/stat.h>
    
    
    #include<unistd.h>
    
    
    #include<fcntl.h>
    
    
    typedef struct stat Stat;
    
    void test_file_type(Stat* file_stat,const char* file_name)
    {
        printf("\t%s type is:",file_name);
        if(S_ISREG(file_stat->st_mode))
            printf(" regular file,  ") ;
        if(S_ISDIR(file_stat->st_mode))
            printf("directory file  ") ;
        if(S_ISCHR(file_stat->st_mode))
            printf("char file,  ") ;
        if(S_ISBLK(file_stat->st_mode))
             printf(" block file,  ") ;
    if(S_ISFIFO(file_stat->st_mode))
             printf("fifo  file,  ") ;
        if(S_ISLNK(file_stat->st_mode))
             printf("ink file,  ") ;
        if(S_ISSOCK(file_stat->st_mode))
            printf("socket,  ") ;
        if(S_TYPEISMQ(file_stat))
            printf("message queue file, ") ;
        if(S_TYPEISSEM(file_stat))
             printf("semaphore,  ") ;
        if(S_TYPEISSHM(file_stat))
             printf("share memory,  ") ;
        printf("\n");
    }
    void test_file_id(Stat* file_stat,const char* file_name)
    {
     printf("\t%s file id:");
     printf("owner id < %d >, ",file_stat->st_uid);
        printf("group id < %d >, ",file_stat->st_gid);
        if(S_ISUID&file_stat->st_mode)
            printf("set_user_id, ");
        if(S_ISGID&file_stat->st_mode)
            printf("set_group_id , ");
        printf("\n");
    }
    void test_file_permission(Stat* file_stat,const char* file_name)
    {
     printf("\t%s file permission:");
        if(S_IRUSR&file_stat->st_mode)
            printf("user read, ");
        if(S_IWUSR&file_stat->st_mode)
             printf("user write, ");
        if(S_IXUSR&file_stat->st_mode)
             printf("user exec, ");
        if(S_IRGRP&file_stat->st_mode)
                printf("group read, ");
     if(S_IWGRP&file_stat->st_mode)
             printf("group write, ");
        if(S_IXGRP&file_stat->st_mode)
             printf("group exec, ");
        if(S_IROTH&file_stat->st_mode)
            printf("other read, ");
        if(S_IWOTH&file_stat->st_mode)
             printf("other write, ");
        if(S_IXOTH&file_stat->st_mode)
            printf("other exec, ");
        printf("\n");
    }
    
    int main(int argc, char *argv[])
    {
        Stat stat_buf;
    int fd;
        printf("Test regular file:\n");
        stat("/home/huaxz1986/main.c",&stat_buf);
     test_file_type(&stat_buf,"/home/huaxz1986/main.c");
        test_file_id(&stat_buf,"/home/huaxz1986/main.c");
        test_file_permission(&stat_buf,"/home/huaxz1986/main.c");
    
        printf("Test directory file:\n");
        stat("/home/huaxz1986/APUE",&stat_buf);
        test_file_type(&stat_buf,"/home/huaxz1986/APUE");
        test_file_id(&stat_buf,"/home/huaxz1986/APUE");
        test_file_permission(&stat_buf,"/home/huaxz1986/APUE");
    
     printf("Test block file:\n");
     stat("/dev/loop0",&stat_buf);
        test_file_type(&stat_buf,"/dev/loop0E");
        test_file_id(&stat_buf,"/dev/loop0");
        test_file_permission(&stat_buf,"/dev/loop0");
    
        printf("Test char file:\n");
        stat("/dev/mem",&stat_buf);
        test_file_type(&stat_buf,"/dev/mem");
        test_file_id(&stat_buf,"/dev/mem");
        test_file_permission(&stat_buf,"/dev/mem");
    
        printf("Test link file:\n");
        stat("/dev/cdrom",&stat_buf);
        test_file_type(&stat_buf,"/dev/cdrom");
        test_file_id(&stat_buf,"/dev/cdrom");
        test_file_permission(&stat_buf,"/dev/cdrom");
    
        printf("Test fifo file:\n");
     stat("/run/systemd/initctl/fifo",&stat_buf);
     test_file_type(&stat_buf,"/run/systemd/initctl/fifo");
     test_file_id(&stat_buf,"/run/systemd/initctl/fifo");
        test_file_permission(&stat_buf,"/run/systemd/initctl/fifo");
    
        printf("Create new file:\n");
     fd=openat(AT_FDCWD,"test",O_WRONLY|O_CREAT,S_IRUSR);
        fstat(fd,&stat_buf);
        test_file_type(&stat_buf,"./test");
        test_file_id(&stat_buf,"./test");
        test_file_permission(&stat_buf,"./test");
    
        return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄

二、通路測試和檔案模式建立屏蔽字

  1. 當用

    open()

    函數打開一個檔案時,核心根據程序的有效使用者ID和有效組ID為依據來執行通路權限測試。但是如果你想測試程序的實際使用者ID和實際組ID是否能夠通過權限測試時,可以用下列兩個函數:
    #include<unistd.h>
    
    int access(const char *pathname,int mode);
    int faccess(int fd,const char*pathname,int mode,int flag);
               
    • 參數:
      • pathname

        :檔案路徑名
      • mode

        :指定要測試的模式。
        • 如果要測試檔案是否已存在,則

          mode

          設為

          F_OK

        • 如果要測試程序的實際使用者ID和實際組ID的權限,則可以為下列常量的按位或
          • R_OK

            :測試讀權限
          • W_OK

            :測試寫權限
          • X_OK

            :測試執行權限
      對于

      faccess

      函數:
      • fd

        :一個打開目錄檔案的描述符,或者

        AT_FDCWD

      • pathname

        • 如果為絕對路徑,則忽略

          fd

          參數
        • 如果為相對路徑,則相對路徑的目錄由

          fd

          指定。
          • fd=AT_FDCWD

            ,則表示相對于目前工作目錄
          • 否則相對于

            fd

            對于的打開的目錄
      • flag

        :如果是

        AT_EACCESS

        ,則通路檢查使用程序的有效使用者ID和有效組ID,而不是實際使用者ID和實際組ID
    • 傳回值:
      • 成功:傳回0
      • 出錯: 傳回 -1
  2. 檔案模式建立屏蔽字:當程序建立一個新的目錄或者檔案時,會使用檔案模式建立屏蔽字。在檔案模式建立屏蔽字中為1的位,在檔案

    mode

    中的相應位一定被關閉。設定程序的檔案模式建立屏蔽字的函數為:
    #include<sys/stat.h>
    
    mode_t umask(mode_t cmask);
               
    • 參數:
      • cmask

        :要設定的新的檔案模式建立屏蔽字
    • 傳回值:
      • 成功:舊的檔案模式建立屏蔽字
      • 函數未指定失敗時傳回何值
    如果你在通過

    creat

    或者

    open

    函數指定了

    mode

    ,那麼該

    mode

    必須通過檔案模式建立屏蔽字的屏蔽之後才是最終新建立的檔案的權限模式。
    shell 有一個

    umask

    指令。我們可以通過該指令來設定或者列印目前的檔案模式建立屏蔽字
  3. 示例
    #include<stdio.h>
    
    
    #include<unistd.h>
    
    
    #include<string.h>
    
    
    #include<errno.h>
    
    
    #include<sys/stat.h>
    
    
    #include<fcntl.h>
    
    typedef struct stat Stat;
    
    void test_access(const char*pathname,int mode)
    {
     int ok;
     printf("\tAccess %s:",pathname);
     ok=access(pathname,mode);
     if(ok==-)
     {
          printf("failed! Beause %s \n",strerror(errno));
     }
        else
        {
            printf("ok!\n");
         }
    }
    void test_umask(mode_t cmask)
    {
         mode_t old_mode;
         old_mode=umask(cmask);
         printf("\t old mask is:");
         print_mode(old_mode);
         printf("\t current mask is:");
         print_mode(cmask);
    }
    void print_mode(mode_t expected_mode)
    {
     if(S_IRUSR&expected_mode)
             printf("U_RD, ");
        if(S_IWUSR&expected_mode)
             printf("U_WR, ");
      if(S_IXUSR&expected_mode)
              printf("U_EXC, ");
        if(S_IRGRP&expected_mode)
              printf("G_RD, ");
    if(S_IWGRP&expected_mode)
               printf("G_WR, ");
     if(S_IXGRP&expected_mode)
              printf("G_EXC, ");
     if(S_IROTH&expected_mode)
             printf("O_RD, ");
        if(S_IWOTH&expected_mode)
              printf("O_WR, ");
      if(S_IXOTH&expected_mode)
             printf("O_EXC, ");
        printf("\n");
    }
    
    void test_file_mode(int expected_mode,const char* file_name)
    {
         printf("\t Expected file mode:");
         print_mode(expected_mode);
        Stat stat_buf;
        int fd;
            fd=openat(AT_FDCWD,file_name,O_CREAT|O_EXCL|O_RDWR,expected_mode);
            if(fd==-)
         {
             printf("open %s in test_file_mode,because %s",file_name,strerror(errno));
             return ;
            }
         fstat(fd,&stat_buf);
         printf("\n \t Real file mode:");
         print_mode(stat_buf.st_mode);
         unlinkat(AT_FDCWD,"test_file",!AT_SYMLINK_FOLLOW);
    }
    
    int main(int argc, char *argv[])
    {
     printf("Test access: no exist file:\n");
     test_access("/no/exist",F_OK);
     printf("Test access: no write:\n");
     test_access("/etc/shadow",W_OK);
    printf("Test access: write ok:\n");
    test_access("/home/huaxz1986/APUE",W_OK);
     printf("\n\n\n");
     printf("Current creat file:\n");
     test_file_mode(S_IRWXU|S_IRWXG|S_IRWXO,"test1");
     printf("umask:\n");
     test_umask(S_IRUSR|S_IRGRP|S_IROTH);
     printf("After umask :\n");
     test_file_mode(S_IRWXU|S_IRWXG|S_IRWXO,"test2");
    return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄
    可以看到:
    • access

      函數:對于不存在的檔案名通路失敗;對沒有寫權限的名字寫通路失敗
    • 被建立的檔案的通路權限是由檔案建立屏蔽字、建立檔案時指定的權限二者共同作用的

三、修改檔案通路權限和檔案所屬使用者

  1. 修改檔案的現有的通路權限:
    #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);
               
    • 參數:
      • pathname

        :檔案路徑名
      • mode

        :檔案修改後的權限。
      對于

      fchmod

      函數:
      • fd

        :打開的檔案描述符
      對于

      fchmod

      函數:
      • fd

        :一個打開目錄檔案的描述符,或者

        AT_FDCWD

      • pathname

        • 如果為絕對路徑,則忽略

          fd

          參數
        • 如果為相對路徑,則相對路徑的目錄由

          fd

          指定。
          • fd=AT_FDCWD

            ,則表示相對于目前工作目錄
          • 否則相對于

            fd

            對于的打開的目錄
      • flag

        :如果是

        !AT_SYMLINK_FOLLOW

        ,則

        fchmodtat

        并不跟随符号連結
    • 傳回值:
      • 成功:傳回0
      • 出錯: 傳回 -1
    參數

    mode

    可以是下面常量的按位或:(來自頭檔案

    <sys/stat.h>

    • 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

      :使用者執行
  2. 粘着位:如果對一個目錄設定了粘着位,則任何對該目錄具有寫權限的程序都能夠在該目錄中建立檔案。但是:隻有滿足下列條件之一的使用者才能删除或者重命名該目錄下的檔案:
    • 擁有此檔案
    • 擁有此目錄
    • 是超級使用者
    對于未設定粘着位的目錄,則隻要使用者對該目錄有寫權限,那麼就有修改和重命名該目錄下其他檔案的能力
  3. 修改使用者的ID群組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);
               
    • 參數:
      • pathname

        :檔案路徑名
      • owner

        :檔案修改後的使用者ID
      • group

        :檔案修改後的組ID
      對于

      fchown

      函數:
      • fd

        :打開的檔案描述符,要修改的就是這個檔案
      對于

      fchmod

      函數:
      • fd

        :一個打開目錄檔案的描述符,或者

        AT_FDCWD

      • pathname

        • 如果為絕對路徑,則忽略

          fd

          參數
        • 如果為相對路徑,則相對路徑的目錄由

          fd

          指定。
          • fd=AT_FDCWD

            ,則表示相對于目前工作目錄
          • 否則相對于

            fd

            對于的打開的目錄
      • flag

        :如果是

        !AT_SYMLINK_FOLLOW

        ,則

        fchmodtat

        并不跟随符号連結,修改的是符号連結本身而不是符号連結指向的檔案
    • 傳回值:
      • 成功: 傳回 0
      • 出錯: 傳回 -1
    有兩點注意:
    • lchown

      函數更改的是符号連結本身,而

      chown

      遇到符号連結時更改的是符号連結指向的檔案
    • 如果這些函數由非超級使用者程序調用,則成功傳回時,該檔案的設定使用者ID和設定組ID位都被清除
  4. 示例
    #include <stdio.h>
    
    
    #include<fcntl.h>
    
    
    #include<sys/stat.h>
    
    
    #include<unistd.h>
    
    
    #include<errno.h>
    
    
    #include<string.h>
    
    typedef struct stat Stat;
    void print_mode(mode_t expected_mode)
    {
      printf("\t");
        if(S_IRUSR&expected_mode)
            printf("U_RD, ");
        if(S_IWUSR&expected_mode)
            printf("U_WR, ");
     if(S_IXUSR&expected_mode)
            printf("U_EXC, ");
        if(S_IRGRP&expected_mode)
            printf("G_RD, ");
        if(S_IWGRP&expected_mode)
            printf("G_WR, ");
    if(S_IXGRP&expected_mode)
            printf("G_EXC, ");
        if(S_IROTH&expected_mode)
            printf("O_RD, ");
        if(S_IWOTH&expected_mode)
            printf("O_WR, ");
        if(S_IXOTH&expected_mode)
            printf("O_EXC, ");
        printf("\n");
    }
    
    
    int main(int argc, char *argv[])
    {
        Stat stat_buf;
        int fd;
    
        fd=openat(AT_FDCWD,"test",O_CREAT|O_RDWR,S_IRWXU|S_IRWXG|S_IRWXO);
        fstat(fd,&stat_buf);
     printf("Current mode:\n");
        print_mode(stat_buf.st_mode);
        printf("Current user: %d, current group: %d\n",stat_buf.st_uid,stat_buf.st_gid);
    
        if(fchmod(fd,S_IRWXU)==-)
        {
             printf("chmod failed,beause of %s",strerror(errno));
    
     }else
        {
            printf("After Change mode:\n");
            fstat(fd,&stat_buf);
            print_mode(stat_buf.st_mode);
    
        }
    
        if(fchown(fd,,)==-)
        {
            printf("chown failed,beause of %s\n",strerror(errno));
    
        }else
        {
        fstat(fd,&stat_buf);
        printf("After Change owner--> user : %d, current group: %
    d\n",stat_buf.st_uid,stat_buf.st_gid);
        }
    
        return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄
    可以看到:
    • 修改檔案所屬的使用者群組,需要超級使用者權限。普通使用者無法修改,即使該使用者就是該檔案的所有者也不行

四、修改檔案長度

  1. 檔案長度:

    stat.st_size

    字段存放的是以位元組為機關的檔案的長度。此字段隻對普通檔案、目錄檔案、符号連結才有意義:
    • 對普通檔案:其長度就是檔案的大小。長度為0表示該檔案為空
    • 對目錄檔案:其長度通常是個整數(如16或者512)的整數倍
    • 對符号連結:其長度是符号連結本身存放的某個檔案名的實際位元組數(它并不包含字元串的

      null

      位元組,因為這些字元是存放在檔案中,而不是存放在記憶體中的字元串)
    另外

    stat.st_blksize

    存放的是對于檔案 I/O 較合适的塊長度;

    stat.st_blocks

    存放的是所配置設定的塊的數量(一個塊512位元組)。注意:
    • 對于普通檔案,可能包含空洞。空洞是由于設定的檔案偏移量超過了檔案末尾,然後寫入了某些資料造成的。對于空洞檔案:
      • 空洞檔案的存儲需要的磁盤塊數量可能遠小于檔案大小。檔案大小是檔案末尾到檔案頭的位元組數
      • 讀取空洞檔案的空洞時,對于沒有寫過的位元組位置

        read

        傳回的是位元組0
  2. 截斷檔案:通常可以用帶

    O_TRUNC

    選項的

    open()

    函數來清空一個檔案(截斷到0)。但是如果希望截斷檔案使得檔案大小為指定位元組數,則可以用下列的函數:
    #include<unistd.h>
    
    int truncate(const char*pathname,off_t length);
    int ftruncate(int fd,off_t length);
               
    • 參數:
      • pathname

        :檔案路徑名
      • length

        :檔案修改後大小(位元組數)
      • fd

        :打開的檔案描述符,要修改的就是這個檔案
    • 傳回值:
      • 成功: 傳回 0
      • 出錯: 傳回 -1
    有兩點注意:
    • length

      小于檔案的原大小,則修改檔案大小之後,檔案新的尾端之後的位置不再可以通路
    • length

      大于檔案的原大小,則修改檔案大小之後,會形成空洞。即從檔案原大小新的尾端形成了空洞
  3. 示例
    #include <stdio.h>
    
    
    #include<sys/stat.h>
    
    
    #include<string.h>
    
    
    #include<errno.h>
    
    
    #include<unistd.h>
    
    
    #include<fcntl.h>
    
    typedef struct stat Stat;
    void print_file_size(int fd)
    {
        int ok;
        Stat stat;
        ok=fstat(fd,&stat);
        if(ok==)
        {
             printf("\tfstat error,because of %s\n",strerror(errno));
        }else
        {
            printf("\tfile size is :%d; blksize:%d; blocks:%d
        \n",stat.st_size,stat.st_blksize,stat.st_blocks);
        }
    }
    void test_truncate(int fd,off_t len)
    {
            int ok;
         ok=ftruncate(fd,len);
         if(ok==-)
            {
                printf("\tftruncate error,because of %s\n",strerror(errno));
            }else
            {
                print_file_size(fd);
            }
    }
    void test_open_trunc(const char* filename)
    {
        int fd;
            fd=openat(AT_FDCWD,filename,O_TRUNC|O_RDWR);
            if(fd==-)
            {
             printf("\topenat %s failed,because of %s\n",strerror(errno));
            }else
            {
                print_file_size(fd);
            }
    }
    void read_fd(int fd,int len)
    {
        char bytes[];
            int ok;
            ok=read(fd,bytes,len);
            int i;
            if(ok==-)
            {
                printf("\tread error,because of %s\n",strerror(errno));
            }else
            {
                lseek(fd,,SEEK_SET);
                printf("\tthe bytes is:");
                for(i=;i<ok;i++)
                {
                    printf("\t%d",bytes[i]);
                }
                printf("\n");
            }
    }
    
    int main(int argc, char *argv[])
    {
            int fd;
    
            fd=openat(AT_FDCWD,"test",O_CREAT|O_RDWR,S_IRWXU);
         if(fd==-)
                 return;
            write(fd,"abcdefg",);
            lseek(fd,,SEEK_SET);
            printf("Original size:\n");
            print_file_size(fd);
            printf("Truncate to 100\n");
            test_truncate(fd,);
            read_fd(fd,);
            printf("Truncate to 2\n");
            test_truncate(fd,);
            read_fd(fd,);
            close(fd);
         printf("Test open with trunc:\n");
         test_open_trunc("test");
        read_fd(fd,);
            return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄
    可以看到:
    • 對于檔案空洞,它不占用任何磁盤空間;空洞部分讀出的内容全為0
    • 對于非常小的檔案,比如這裡的 8 位元組文字,磁盤配置設定了 8個塊(4kb)。

五、UNIX檔案系統、硬連結、軟連結、删除、重命名

  1. UNIX檔案系統簡介(傳統的基于BSD的UNIX檔案系統,稱作

    UFS

    ):
    • 一個磁盤可以劃分成一個或者多個分區,每個分區可以包含一個檔案系統。每個檔案系統包含一些柱面組。每個柱面組包括:
      • 一個 i 節點圖:用于訓示哪些 i 節點已經被使用,哪些未被使用
      • 一個 塊位圖:用于訓示哪些資料塊已經被使用,哪些為被使用
      • 一個 i 節點組。它包含的是許多 i 節點。
      • 一個資料區:存放具體的資料塊和目錄塊
    • 資料區包含兩種類型的塊:
      • 目錄塊:它的内容是

        <i 節點編号>|<檔案名>

        這種格式的記錄的清單
      • 資料塊:它的内容就是具體檔案的資料
    • i 節點是固定長度的記錄項,它包含有關檔案的大部分資訊
      • 每個 i 節點都有一個連結計數,其值是指向 i 節點的目錄的項數(這種連結類型稱之為硬連結)。隻有當該連結計數減少為0時,才可以删除該連結檔案(也就是釋放該檔案占用的資料塊)。
        • stat

          結構中,連結計數包含在

          st_nlink

          成員中(POSIX常量:

          LINK_MAX

          指定了一個檔案連結數的最大值)
      • 每個 i 節點包含了檔案有關的所有資訊:檔案類型、檔案權限通路位、檔案長度和指向檔案資料塊的指針
        • stat

          結構中的大多數資訊來自于 i 結點。隻有兩項重要資料存放在目錄項中:檔案名、i節點編号
      • 目錄項中的 i 節點編号隻能指向同一個檔案系統中的相應的 i 節點。
    是以硬連結不能跨檔案系統
  2. 當在不更換檔案系統的情況下重命名一個檔案時,該檔案的實際内容并未移動。隻需要構造一個指向現有 i 節點的新目錄項,并删除來的目錄項。此時該 i節點的連結計數不會改變

    這就是

    mv

    指令的操作方式
  3. 與硬連結對應的概念是軟連結。軟連結也稱作符号連結,它是一種特殊的檔案。該檔案的實際内容(在資料塊中)包含了該符号連結所指向的檔案的名字。同時該檔案的 i 節點訓示了該檔案類型是

    S_IFLNK

    ,于是系統知道了這個檔案是個符号連結。
    • 硬連結直接指向檔案的

      i

      節點
    • 軟連結是對一個檔案的間接指針
    引入符号連結的原因是為了避開硬連結的一些限制:
    • 硬連結通常要求連結和檔案位于同一個檔案系統中
    • 隻有超級使用者才能建立指向目錄的硬連結(在底層檔案系統支援的情況下)

    對于符号連結以及它指向何種類型的檔案并沒有什麼限制。任何使用者都可以建立指向目錄的符号連結。但是使用符号連結有可能在檔案系統中引入循環

    對于處理檔案和目錄的函數,如果傳遞的是一個符号連結的檔案名,則應該注意:函數是否跟随符号連結,即函數是處理符号連結指向的檔案,還是處理符号連結本身。

    • 跟随符号連結(即處理符号連結指向的檔案)的函數有:

      access

      chdir

      chmod

      chown

      creat

      exec

      link

      open

      opendir

      pathconf

      stat

      truncate

    • 不跟随符号連結(即處理符号連結檔案本身)的函數有:

      lchown

      lstat

      readlink

      remove

      rename

      unlink

      • 一個例外的情況:如果用

        O_CREAT

        O_EXCL

        選項調用

        open

        ,此時若參數是個符号連結的檔案名,則

        open

        出錯傳回(并不考慮符号連結指向的檔案是否存在),同時将

        errno

        設為

        EEXIST

  4. 任何一個目錄

    dirxxx

    的硬連結至少為2:
    • 該目錄的内容中有一條名為的

      .

      記錄,該記錄的

      <i節點編号>

      指向

      dirxxx

      目錄的節點
    • 該目錄的父目錄的内容中有一條記錄,記錄的名字

      dirxxx

      ,記錄的

      <i節點編号>

      指向

      dirxxx

      目錄的節點
    • 若該目錄有子目錄。

      dirxxx

      的任何子目錄的内容有一條名為

      ..

      的記錄,該記錄的

      <i節點編号>

      指向

      dirxxx

      目錄的節點
    是以父目錄中的每個子目錄都使得父目錄的連結計數加 1
  5. 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);
               
    • 參數:
      • existingpath

        :現有的檔案的檔案名(新建立的硬連結指向它)
      • newpath

        :新建立的目錄項
        • 如果

          newpath

          已存在,則傳回出錯
        • 隻建立

          newpath

          中的最後一個分量,路徑中的其他部分應當已經存在。
      假設

      newpath

      為:

      /home/aaa/b/c.txt

      ,則要求

      /home/aaa/b

      已經存在,隻建立

      c.txt

  6. 對于

    linkat

    函數:
    • 現有的檔案名是通過

      efd

      existingpath

      指定。
      • existingpath

        是絕對路徑,則忽略

        efd

      • existingpath

        是相對路徑,則:
        • efd=AT_FDCWD

          ,則

          existingpath

          是相對于目前工作目錄來計算
        • efd

          是一個打開的目錄檔案的檔案描述符,則

          existingpath

          是相對于

          efd

          對應的目錄檔案
    • 建立的檔案名是通過

      nfd

      newpath

      指定。
      • newpath

        是絕對路徑,則忽略

        nfd

      • newpath

        是相對路徑,則:
        • nfd=AT_FDCWD

          ,則

          newpath

          是相對于目前工作目錄來計算
        • nfd

          是一個打開的目錄檔案的檔案描述符,則

          newpath

          是相對于

          nfd

          對應的目錄檔案
    • flag

      :當現有檔案是符号連結時的行為:
      • flag=AT_SYMLINK_FOLLOW

        :建立符号連結指向的檔案的硬連結(跟随行為)
      • flag=!AT_SYMLINK_FOLLOW

        :建立符号連結本身的硬連結(預設行為)
  7. 傳回值:
    • 成功: 傳回 0
    • 失敗: 傳回 -1
  8. 這兩個函數建立新目錄項并對連結計數加1。建立新目錄項和增加連結計數是一個原子操作。

    另外,大多數作業系統中,隻有超級使用者才能建立指向一個目錄的硬連結,因為這樣做很有可能在檔案系統中形成循環。

  9. unlink

    函數:删除一個現有的目錄項
    #include<unistd.h>
    
    int unlink(const char*pathname);
    int unlinkat(int fd,const char*pathname,int flag);
               
    • 參數:
      • pathname

        :現有的、待删除的目錄項的完整路徑名。
      對于

      unlinkat

      函數:
      • 現有的檔案名是通過

        fd

        pathname

        指定。
        • pathname

          是絕對路徑,則忽略

          fd

        • pathname

          是相對路徑,則:
          • fd=AT_FDCWD

            ,則

            pathname

            是相對于目前工作目錄來計算
          • fd

            是一個打開的目錄檔案的檔案描述符,則

            pathname

            是相對于

            fd

            對應的目錄檔案
      • flag

        • flag=AT_REMOVEDIR

          :可以類似于

          rmdir

          一樣的删除目錄
        • flag=!AT_REMOVEDIR

          :與

          unlink

          執行同樣的操作
    • 傳回值:
      • 成功: 傳回 0
      • 失敗: 傳回 -1
    為了解除對檔案的連結,必須對包含該目錄項的目錄具有寫和執行權限。如果還對該目錄設定了粘着位,則對該目錄必須具有寫權限以及下列三個條件之一:
    • 擁有該檔案
    • 擁有該目錄
    • 具有超級使用者權限
    這兩個函數删除目錄項并對連結計數減1。建立新目錄和增加連結計數是一個原子操作。
    • 如果該檔案的硬連結數不為0, 則還可以通過其他連結通路該檔案的内容
    • 如果該檔案的硬連結數為0,而沒有程序打開該檔案,則該檔案的内容才有被删除
    • 如果該檔案的硬連結數為0,但是有程序打開了該檔案,則該檔案的内容不能被删除。當程序關閉檔案時,核心會檢查打開該檔案的程序個數;當這個數量為0,核心再去檢查其連結計數。如果連結計數也是0,則就删除該檔案的内容。
    這個特性常用于建立臨時檔案,先

    open,create

    一個檔案,然後立即調用

    unlink

    。這樣即使程式崩潰,它所建立的臨時檔案也不會遺留下來
  10. 如果删除目錄項出錯,則不對該檔案做任何更改
  11. 如果

    pathname

    是個符号連結,則

    unlink

    删除該符号連結,而不會删除由該符号連結所引用的檔案。

    如果僅僅給出符号連結的檔案名,沒有一個函數可以删除由該符号連結所引用的檔案

    如果檔案系統支援,超級使用者可以調用

    unlink

    ,其參數

    pathname

    指定一個目錄
    通常推薦用

    rmdir

    函數,其語義更加清晰
  12. link/unlink

    執行個體:
    #include<stdio.h>
    
    
    #include<unistd.h>
    
    
    #include<sys/stat.h>
    
    
    #include<fcntl.h>
    
    
    #include<string.h>
    
    
    #include<errno.h>
    
    
    typedef struct stat Stat;
    void print_link_num(const char*path)
    {
            Stat stat;
            if(-==fstatat(AT_FDCWD,path,&stat,))
            {
             printf("\tfstatat %s error,because %s\n",path,strerror(errno));
            }else
            {
                printf("\t%s link is %d\n",path,stat.st_nlink);
            }
    }
    void add_link(const char *path,const char *new_path)
    {
            if(-==linkat(AT_FDCWD,path,AT_FDCWD,new_path,))
            {
             printf("\tadd_link %s error,because %s\n",path,strerror(errno));
            }else
            {
                print_link_num(path);
            }
    }
    void del_link(const char *path)
    {
            if(-==unlinkat(AT_FDCWD,path,))
            {
                printf("\tun_link %s error,because %s\n",path,strerror(errno));
            }else
            {
             print_link_num(path);
            }
    }
    
    int main(int argc, char *argv[])
    {
            int fd;
            fd=openat(AT_FDCWD,"test",O_CREAT|O_RDWR,S_IRWXU);
            if (-==fd) return;
            close(fd);
    
            printf("The original link num\n");
            print_link_num("test");  // link_num=1
            printf("Add link\n");
            add_link("test","new_test");// link_num=2
            printf("\t The new file link:\n\t");
            print_link_num("new_test");
         printf("Del new file link\n");
         del_link("new_test"); // link_num=1
            printf("\tThe original file link\n\t");
            print_link_num("test");
            printf("Del original file link\n");
            del_link("test"); // link_num=0
            // test link is 0
            printf("Del after he original file link is 0:\n");
            del_link("test");
            return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄
    可以看到:
    • test

      new_test

      這兩個檔案共享一個 i 結點。是以該節點的 硬連結數為2
    • 一旦删除

      new_test

      ,則對

      new_test

      執行

      fstatat

      失敗(因為已經被

      unlink

      )。同時

      test

      的硬連結數為1
    • 一旦

      test

      也被删除,則

      i

      節點被釋放。執行

      unlink

      失敗。
  13. remove

    函數:解除對一個目錄或者檔案的連結。
    #include<stdio.h>
    
    int remove(const char *pathname);
               
    • 參數
      • pathname

        :檔案名或者目錄名
    • 傳回值:
      • 成功:傳回0
      • 失敗:傳回 -1
    對于檔案,

    remove

    功能與

    unlink

    相同;對于目錄,

    remove

    功能與

    rmdir

    相同
  14. rename/renameat

    函數:重命名檔案或目錄
    #inluce<stdio.h>
    
    int rename(const char*oldname,const char *newname);
    int renameat(int oldfd,const char*oldname,int newfd,const char* newname);
               
    • 參數:
      • oldname

        :現有的檔案名或者目錄名
      • newname

        :重命名的名字
        • 如果

          oldname

          是個檔案名,則為該檔案或者符号連結重命名。
          • 此時若

            newname

            已存在:若

            newname

            是個目錄則報錯;若

            newname

            不是個目錄:則先将

            newname

            目錄項删除,然後将

            oldname

            重命名為

            newname

          • 此時若

            newname

            不存在:則直接将

            oldname

            重命名為

            newname

        • 如果

          oldname

          是個目錄名,則為該目錄重命名。
          • 此時若

            newname

            已存在:若

            newname

            是個目錄且該目錄是個空目錄,則先将它删除,然後

            oldname

            重命名為

            newname

            ;若

            newname

            是個目錄且該目錄不是個空目錄,則報錯;若

            newname

            不是個目錄,則報錯
          • 此時若

            newname

            不存在:則直接将

            oldname

            重命名為

            newname

      oldname

      不能是

      newname

      的字首。因為重命名時,需要删除

      oldname

  15. 如果

    oldname

    或者

    newname

    引用的是符号連結,則處理的是符号連結本身,而不是它引用的檔案
  16. 不能對

    .

    ..

    重命名。即

    .

    ..

    不能出現在

    oldname

    newname

    的最後部分
  17. newname

    oldname

    引用同一個檔案,則函數不作任何更改而成功傳回
  18. 對于

    renameat

    函數:
    • 現有的檔案名或目錄名是通過

      oldfd

      oldname

      指定。
      • oldname

        是絕對路徑,則忽略

        oldfd

      • oldname

        是相對路徑,則:
        • oldfd=AT_FDCWD

          ,則

          oldname

          是相對于目前工作目錄來計算
        • oldfd

          是一個打開的目錄檔案的檔案描述符,則

          oldname

          是相對于

          oldfd

          對應的目錄檔案
    • 重命名的檔案名或目錄名是通過

      newfd

      newname

      指定。
      • newname

        是絕對路徑,則忽略

        newfd

      • newname

        是相對路徑,則:
        • newfd=AT_FDCWD

          ,則

          newname

          是相對于目前工作目錄來計算
        • newfd

          是一個打開的目錄檔案的檔案描述符,則

          newname

          是相對于

          newfd

          對應的目錄檔案
    • flag

      :當現有檔案是符号連結時的行為:
      • flag=AT_SYMLINK_FOLLOW

        :建立符号連結指向的檔案的硬連結(跟随行為)
      • flag=!AT_SYMLINK_FOLLOW

        :建立符号連結本身的硬連結(預設行為)
  19. 傳回值:
    • 成功: 傳回 0
    • 失敗: 傳回 -1
  20. 對于包含

    oldname

    以及

    newname

    的目錄,調用程序必須具有寫和執行的權限,因為将同時更改這兩個目錄。
  21. symlink/symlinkat

    函數:建立一個符号連結
    #include<unistd.h>
    
    int symlink(const char*actualpath,const char *sympath);
    int symlinkat(const char*actualpath,int fd,const char*sympath);
               
    • 參數:
      • actualpath

        :符号連結要指向的檔案或者目錄(可能尚不存在)
      • sympath

        :符号連結的名字
      二者不要求位于同一個檔案系統中
    對于

    symlinkat

    函數:
    • 符号連結的名字是通過

      fd

      sympath

      指定。
      • sympath

        是絕對路徑,則忽略

        fd

      • sympath

        是相對路徑,則:
        • fd=AT_FDCWD

          ,則

          sympath

          是相對于目前工作目錄來計算
        • fd

          是一個打開的目錄檔案的檔案描述符,則

          sympath

          是相對于

          fd

          對應的目錄檔案
  22. 傳回值:
    • 成功: 傳回 0
    • 失敗: 傳回 -1
  23. readlink/readlinkat

    函數:打開符号連結本身

    open

    函數是跟随連結的,即打開符号連結指向的檔案
    #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);
               
    • 參數:
      • pathname

        :符号連結的名字
      • buf

        :存放符号連結内容的緩沖區
      • bufsize

        :期望讀入緩沖區的位元組數
      對于

      readlinkat

      函數:
      • 符号連結的名字是通過

        fd

        pathname

        指定。
        • pathname

          是絕對路徑,則忽略

          fd

        • pathname

          是相對路徑,則:
          • fd=AT_FDCWD

            ,則

            pathname

            是相對于目前工作目錄來計算
          • fd

            是一個打開的目錄檔案的檔案描述符,則

            pathname

            是相對于

            fd

            對應的目錄檔案
    • 傳回值:
      • 成功: 傳回實際上讀取的位元組數
      • 失敗: 傳回 -1

    readlink

    readlinkat

    函數組合了

    open、read、close

    函數的所有操作。

    注意:讀入

    buf

    中的符号連結的内容,并不是以

    null

    位元組終止。

    null

    位元組終止的是記憶體中的字元串這種資料結構。而符号連結檔案的内容是簡單的字元序列,并不是字元串。
  24. 符号連結示例:
    #include <stdio.h>
    
    
    #include<unistd.h>
    
    
    #include<sys/stat.h>
    
    
    #include<string.h>
    
    
    #include<errno.h>
    
    
    #include<fcntl.h>
    
    
    typedef struct stat Stat;
    
    void test_file_type(const char* file_name)
            {
                Stat _stat;
                if(-==stat(file_name,&_stat))
                {
                    printf("test_file_type file %s fail,because of %s
    \n",file_name,strerror(errno));
                    return;
                }
                printf("%s file type is:",file_name);
                if(S_ISLNK(_stat.st_mode))
                     printf("link file,  ") ;
                if(S_ISREG(_stat.st_mode))
                    printf(" regular file,  ") ;
                printf("\n");
            }
    void creat_file(const char* file_name)
    {
        int fd;
        fd=open(file_name,O_CREAT|O_RDWR,S_IRWXU);
        if(-==fd)
        {
            printf("creat file %s fail,because of %s\n",file_name,strerror(errno));
        }
        else{
            close(fd);
        }
    }
    int  creat_symlink(const char* actual_name,const char* sym_name)
    {
        int ok;
        ok=symlink(actual_name,sym_name);
        if(-==ok)
        {
            printf("creat symlink %s fail,because of %s\n",sym_name,strerror(errno));
        }
        return ok;
    }
    void read_symlink(const char* sym_name)
    {
        char buffer[];
        int len,i;
        len=readlink(sym_name,&buffer,);
        if(-==len)
        {
             printf("read symlink %s fail,because of %s\n",sym_name,strerror(errno));
        }else
        {
            printf("length <%d>, content:",len);
            for(i=;i<len;i++)
            {
                printf("%c",buffer[i]);
            }
            printf("\n");
        }
    }
    
    int main(int argc, char *argv[])
    {
    
        creat_file("/home/huaxz1986/test");
        test_file_type("/home/huaxz1986/test");
        creat_symlink("/home/huaxz1986/test","/home/huaxz1986/sym_test");
        test_file_type("/home/huaxz1986/sym_test");
        read_symlink("/home/huaxz1986/sym_test");
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄
    可以看到:
    • 符号連結檔案的内容就是它連結到的那個檔案的絕對路徑名,其中路徑名字元序列不包含

      null

      位元組
    • ubuntu 16.04

      中,經多次測試,符号連結檔案和普通檔案的

      st_mode

      完全相同。

六、修改檔案的時間

  1. 檔案的時間:在

    stat

    結構中存放着檔案的三個時間:
    • st_atim

      :檔案資料的最後通路時間
    • st_mtim

      :檔案資料的最後修改時間
    • st_ctim

      : i 節點狀态的最後更改時間
    關于這三個時間:
    • 有很多操作,比如修改檔案權限,修改檔案的所有者等操作,他們隻修改 i 節點狀态(隻影響

      st_ctim

      ),但是并不修改檔案資料,也并不通路檔案資料
    • 系統并不維護對

      i

      節點的最後通路時間。是以對于

      access

      函數和

      stat

      函數,他們并不修改這三個時間中的任何一個
    • 建立一個檔案不僅影響了檔案本身的這三個時間,也會影響該檔案目錄的這三個時間
  2. futimens/utimensat/utimes

    函數:修改檔案的通路和修改時間
    #include<sys/stat.h>
    
    int futimens(int fd,const struct timespec times[]);
    int utimensat(int fd,const char*path,const struct timespec times[],int flag);
    
    #include<sys/time.h>
    
    int utimes(const char*pathname,const struct timeval times[]);
               
    • 參數:

      對于

      futimens

      utimensat

      函數:
      • times

        :指向待修改檔案的指定的檔案資料通路和檔案資料修改時間的指針。
        對于C語言,參數中的數組自動轉換為指向數組的指針
        • 這兩個時間是月曆時間,是自 1970:01:01–00:00:00 以來經曆的秒數。不足秒的部分用納秒表示
        • 數組的第一個元素指定

          st_atim

          ;數組的第二個元素指定

          st_ctim

        • times

          可以按照下列四種方式之一指定:
          • times

            為空指針: 則将檔案的資料通路時間和檔案資料修改時間設定為目前時間
        此時要求程序的有效使用者ID等于該檔案所有者的ID;或者程序對該檔案有寫權限;或者程序是個超級使用者程序
      • times

        參數是指向

        timespec

        數組的指針:
        • 若數組的任何一個元素的

          tv_nsec

          字段為

          UTIME_NOW

          ,則相應的時間戳就設定為目前時間,忽略相應的

          tv_sec

          字段

          此時要求程序的有效使用者ID等于該檔案所有者的ID;或者程序對該檔案有寫權限;或者程序是個超級使用者程序

        • 若數組的任何一個元素的

          tv_nsec

          字段為

          UTIME_OMIT

          ,則相應的時間戳保持不變,忽略相應的

          tv_sec

          字段

          若兩個時間戳都忽略,則不需要任何權限限制

        • 若數組的任何一個元素的

          tv_nsec

          字段為不是上面的兩種之一,則相應的時間戳就設定為相應的

          tv_sec

          tv_nsec

          字段

          此時要求程序的有效使用者ID等于該檔案所有者的ID;或者程序是個超級使用者程序(對檔案隻有寫權限是不夠的)

  3. 對于

    utimes

    函數:
    • pathname

      :檔案的路徑名
    • times

      :指向

      timeval

      數組的指針。

      timeval

      結構用秒和微秒表示。
      struct timeval{
      time_t tv_sec;//秒
      long tv_usec; //微秒
                 
    對于

    futimens

    函數:
    • fd

      :待修改檔案的打開的檔案描述符
    對于

    utimensat

    函數:
    • 待打開檔案的名字是通過

      fd

      path

      指定。
      • path

        是絕對路徑,則忽略

        fd

      • path

        是相對路徑,則:
        • fd=AT_FDCWD

          ,則

          path

          是相對于目前工作目錄來計算
        • fd

          是一個打開的目錄檔案的檔案描述符,則

          path

          是相對于

          fd

          對應的目錄檔案
    • flag

      :若待修改的檔案是符号連結
      • 如果為

        !AT_SYMLINK_FOLLOW

        ,則符号連結本身的時間就會被修改
      • 預設情況下,修改的是符号連結指向的檔案的時間(跟随行為)
  4. 傳回值:
    • 成功: 傳回 0
    • 失敗: 傳回 -1
  5. 我們不能對

    st_ctim

    (i節點最後被修改時間)指定一個值。這個時間是被自動更新的。
  6. 示例:
    #include <stdio.h>
    
    
    #include<sys/stat.h>
    
    
    #include<string.h>
    
    
    #include<errno.h>
    
    
    #include<unistd.h>
    
    
    #include<fcntl.h>
    
    typedef struct stat Stat;
    typedef struct timespec Timespec;
    void print_time(const char*path)
    {
        Stat buffer;
        if(-==stat(path,&buffer))
        {
            printf("stat file <%s> error,because %s",path,strerror(errno));
        }else
        {
            printf("\t%s time:\n",path);
            printf("\t\tdata last access time:<%d s,%d ns
    >\n",buffer.st_atim.tv_sec,buffer.st_atim.tv_nsec);
            printf("\t\tdata last modify time:<%d s,%d ns>  
    \n",buffer.st_mtim.tv_sec,buffer.st_mtim.tv_nsec);
            printf("\t\tinfo last access time:<%d s,%d ns
    >\n",buffer.st_ctim.tv_sec,buffer.st_ctim.tv_nsec);
        }
    }
    void creat_file(const char* file_name)
    {
        int fd;
        fd=open(file_name,O_CREAT|O_RDWR,S_IRWXU);
        if(-==fd)
        {
         printf("creat file %s fail,because of %s\n",file_name,strerror(errno));
        }
        else{
            close(fd);
        }
    }
    void set_time(const char* file_name,Timespec  time[])
    {
        if(utimensat(AT_FDCWD,file_name,time,)==-)
        {
            printf("utimensat file <%s> error,because %s",file_name,strerror(errno));
        }else
        {
            print_time(file_name);
        }
    }
    
    int main(int argc, char *argv[])
    {
        Timespec times[];
        times[].tv_nsec=;
        times[].tv_sec=;
        times[].tv_nsec=;
        printf("Create file:\n");
        creat_file("/home/huaxz1986/test_time");
        print_time("/home/huaxz1986/test_time");
        sleep();
        printf("Access not modify time:\n");
        access("/home/huaxz1986/test_time",F_OK);
        print_time("/home/huaxz1986/test_time");
        sleep();
        printf("Chmod only modify st_ctime:\n");
        chmod("/home/huaxz1986/test_time",S_IRUSR|S_IWUSR);
        print_time("/home/huaxz1986/test_time");
        sleep();
        printf("Set data access time , data modify time to now:");
        set_time("/home/huaxz1986/test_time",NULL);
        sleep();
        printf("Set data access time , data modify time  ");
        set_time("/home/huaxz1986/test_time",times);
        return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄
    可以看到:
    • st_ctim

      是由系統自動維護的,程式員無法手動指定

七、目錄操作

  1. mkdir/mkdirat

    函數建立一個空目錄:
    #include<sys/stat.h>
    
    int mkdir(const char*pathname,mode_t mode);
    int mkdirat(int fd,const char *pathname,mode_t mode);
               
    • 參數:
      • pathname

        :被建立目錄的名字
      • mode

        :被建立目錄的權限
      對于

      mkdirat

      ,被建立目錄的名字是由

      fd

      pathname

      共同決定的。
      • pathname

        是絕對路徑,則忽略

        fd

      • pathname

        是相對路徑,則:
        • fd=AT_FDCWD

          ,則

          pathname

          是相對于目前工作目錄來計算
        • fd

          是一個打開的目錄檔案的檔案描述符,則

          pathname

          是相對于

          fd

          對應的目錄檔案
    • 傳回值:
      • 成功: 傳回0
      • 失敗: 傳回 -1
    注意:
    • 他們建立的目錄是空目錄。
    • 對于目錄,通常至少要設定一個執行權限位,以允許通路該目錄中的檔案名
  2. rmdir

    函數:删除一個空目錄
    #include<unistd.h>
    
    int rmdir(const char *pathname);
               
    • 參數:
      • pathname

        :待删除的空目錄的名字
    • 傳回值:
      • 成功: 傳回0
      • 失敗: 傳回 -1
    如果調用此函數使得目錄的連結計數為0時:
    • 如果此時沒有其他程序打開該目錄,則釋放由此目錄占用的空間。
    • 如果此時有一個或者多個程序打開此目錄,則在此函數傳回時删除最後一個連結以及

      .

      ..

      項,直到最後一個打開該目錄的程序關閉該目錄時此目錄才真正被釋放。
      • 此時,在此目錄中不能再建立新檔案。
  3. 讀、寫目錄:對于某個目錄具有通路權限的任何使用者都可以讀該目錄。但是為了防止檔案系統産生混亂,隻有核心才能寫目錄。
    一個目錄的寫權限和執行權限位決定了在該目錄中能否建立新檔案以及删除檔案,它們并不能寫目錄本身
    #include<dirent.h>
    
    DIR *opendir(const char *pathname);
    DIR *fdopendir(int fd);
    struct dirent *readdir(DIR *dp);
    void rewinddir(DIR *dp);
    int closedir(DIR *dp);
    long telldir(DIR *dp);
    void seekdir(DIR *dp,long loc);
               
    各個函數:
    • opendir

      :打開目錄。
      • 參數:

        pathname

        :目錄的名字
      • 傳回值:成功傳回目錄指針;失敗傳回

        NULL

    • fdopendir

      :打開目錄。
      • 參數:

        fd

        :目錄檔案的檔案描述符
      • 傳回值:成功傳回目錄指針;失敗傳回

        NULL

    • readdir

      :讀取目錄
      • 參數:

        dp

        :目錄指針
      • 傳回值: 成功則傳回目錄項的指針;失敗傳回

        NULL

    • rewinddir

      :将目錄的檔案偏移量清零(這樣下次讀取就是從頭開始)
      • 參數:

        dp

        :目錄指針
    • closedir

      :關閉目錄。
      • 參數:

        dp

        :目錄指針
      • 傳回值:成功傳回 0 ;失敗傳回 -1
    • telldir

      :傳回目錄的檔案偏移量
      • 參數:

        dp

        :目錄指針
      • 傳回值:成功傳回目錄的檔案偏移量 ;失敗傳回 -1
    • seekdir

      :設定目錄的目前位置
      • 參數:

        dp

        :目錄指針;

        loc

        :要設定的檔案偏移量
    對于

    DIR

    結構,它是一個内部結構。起作用類似于

    FILE

    結構。

    對于

    dirent

    結構,它是定義在

    <dirent.h>

    頭檔案中。其與具體作業系統相關。但是它至少定義了兩個成員:
    struct dirent{
    ino_t d_ino; // i 節點編号
    char d_name[];// 以 null 結尾的檔案名字元串
    }
               

    d_name

    項的大小并沒有指定,但必須保證它能包含至少

    NAME_MAX

    個位元組(不包含終止

    null

    位元組)
    目錄中各目錄項的順序與作業系統有關。它們通常不按照字母順序排列
  4. 目前工作目錄:每個程序都有一個目前工作目錄。此目錄是搜尋所有相對路徑名的起點。
    目前工作目錄是本程序的一個屬性
    與目前工作目錄相關的有三個函數:
    #include<unistd.h>
    
    int chdir(const char *pathname);
    int fchdir(int fd);
    char *getcwd(char *buf,size_t size);
               
    各個函數:
    • chdir

      :更改目前工作目錄。
      • 參數:

        pathname

        :将該目錄作為目前工作目錄
      • 傳回值:成功傳回 0 ;失敗傳回 -1
    • fchdir

      :更改目前工作目錄。
      • 參數:

        fd

        :将該

        fd

        檔案描述符對應的目錄作為目前工作目錄
      • 傳回值:成功傳回 0 ;失敗傳回 -1
    • getcwd

      :傳回目前工作目錄的名字
      • 參數:

        buf

        :緩沖區位址;

        size

        :緩沖區長度。這兩個參數決定了目前工作目錄名字字元串存放的位置。
    緩沖區必須足夠長以容納絕對路徑名加上一個終止

    null

    位元組。否則傳回出錯。
  5. 傳回值: 成功則傳回

    buf

    ;失敗傳回

    NULL

  6. 示例
    #include <stdio.h>
    
    
    #include<unistd.h>
    
    
    #include<string.h>
    
    
    #include<errno.h>
    
    
    #include<dirent.h>S
    
    typedef struct dirent Dirent;
    void print_current_work_dir()
    {
        char buffer[];
        if(getcwd(buffer,)==NULL)
        {
            printf("\t getcwd failed,because:%s\n",strerror(errno));
        }else
        {
         printf("\t current work dir is :%s\n",buffer);
        }
    }
    void change_current_work_dir(const char*path)
    {
        if(chdir(path)==-)
        {
            printf("change current work dir to %s failed,because:%s\n"
    ,path,strerror(errno));
        }else
        {
            print_current_work_dir();
        }
    }
    void list_dir(const char *path)
    {
     DIR * dir;
        Dirent * dir_ent;
        dir=opendir(path);
        if(dir==NULL)
        {
            printf("\t open dir %s  failed,because:%s\n",path,strerror(errno));
            return;
        }
        printf("%s contents is :\n",path);
        while((dir_ent=readdir(dir)) !=NULL)
        {
         printf("\tid:<%d>, file_name :<%s>\n",dir_ent->d_ino,dir_ent->d_name);
        }
        closedir(dir);
    }
    
    int main(int argc, char *argv[])
    {
     printf("Current work dir:\n");
     print_current_work_dir();
        printf("Expected change current work dir to /home/huaxz1986\n");
        change_current_work_dir("/home/huaxz1986");
        list_dir("/home/huaxz1986");
        return ;
    }
               
    《UNIX環境進階程式設計》---4檔案和目錄檔案和目錄