1. 程式人生 > >UNIX環境高階程式設計——第四章-檔案和目錄

UNIX環境高階程式設計——第四章-檔案和目錄

4.2 函式stat、fstat、lstat

1、函式原型:

#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); 所有4個函式的返回值:若成功:返回0;若出錯,返回-1

2、一旦給出pathname,stat函式將返回此命名檔案有關的資訊結構。

3、fstat函式獲得已在描述符fd上開啟檔案的有關資訊。

4、lstat函式類似於stat,但是當名稱空間是一個符號連結時,lstat返回該符號連結的有關資訊,而不是該符號連結引用的檔案的資訊。

5、fstatat函式為一個相對於當前開啟目錄,由(fd引數指向)的路徑名返回檔案統計資訊。flag引數控制著是否跟隨一個符號連結。
(1)當AT_SYMLINK_NOFOLLOW標誌被設定時,fstatat不會跟隨符號連結,而是返回符號連結本身的資訊。
(2)當fd引數的值是AT_FDCWD,並且pathname引數是一個相對路徑名,fstatat會計算相對於當前目錄的pathname引數。如果pathname是一個絕對路徑,fd引數就會被忽略。

6、第2個引數buf是一個指標,它指向一個我們必須提供的結構。函式來填充由buf指向的結構。結構的實際定義可能隨具體實現有所不同,基本形式如下:

struct stat {
mode_t      st_mode;        /* file type & mode (permission) */
ino_t       st_ino;         /* i-node number (serial number) */
dev_t       st_dev;         /* device number for special files */
nlink_t     st_nlink;       /* device number
(file system) */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ off_t st_size; /* size in bytes, for regular files */ struct timespec st_atime; /* time of last access */ struct timespec st_mtime; /* time of last modification */ struct timespec st_ctime; /* time of last file status change */ blksize_t st_blksize; /* best I/O block size */ blkcnt_t st_blocks; /* number of disk blocks allocated */ }

(2)timespec結構按照秒和納秒定義了時間,至少包括下面兩個欄位:

time_t  tv_sec;
long tv_nsec;

4.3 檔案型別

1、檔案型別包括如下幾種:

1、普通檔案(regular file)。
2、目錄檔案(directory file3、塊裝置檔案(block special file)。這種型別的檔案提供對裝置(如磁碟)帶緩衝的訪問,每次訪問以固定長度為單位進行。
4、字元特殊檔案(character special file)。這種型別檔案提供對裝置不帶緩衝的訪問,每次訪問長度可變。系統中所有裝置要麼是塊特殊檔案。要麼是字元特殊檔案
5、FIFO。這種型別的檔案用於程序間的通訊,有時也稱為命名管道(named pipe)
6、套接字(socket)。這種型別的檔案用於程序間的網路通訊。套接字也可以用於在一臺宿主機上程序之間的非網路通訊。
7、符號連結(symbolic link)。這種型別的檔案指向另一個檔案

2、在以下巨集確定檔案型別。這些巨集的引數都是stat結構中的st_mode成員:

巨集           檔案型別
S_ISREG()       普通檔案
S_ISDIR()       目錄檔案
S_ISCHR()       字元特殊檔案
S_ISBLK()       塊特殊檔案
S_ISFIFO()      管道或FIFO
S_ISSLNK()      符號連結
S_ISSOCK()      套接字

3、POSIX.1允許實現將程序間通訊(IPC)物件(如訊息佇列和訊號量等)說明為檔案。以下的巨集可用來從stat結構中確定IPC物件的型別。它們的引數並非st_mode,而是指向stat結構的指標。

巨集           物件的型別
S_TYPEISMQ()        資訊佇列
S_TYPEISSEM()       訊號量
S_TYPEISSHM()       共享儲存物件

4、例項4-3:取其命令列引數,然後針對每一個命令列引數列印其檔案型別

/*************************************************************************
    > File Name: instance4-3.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: 2017年05月10日 星期三 07時49分07秒
 ************************************************************************/

#include "apue.h"

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

    for (i = 1; i <argc ; ++i) {
        printf("%s: ",argv[i]);
        if (lstat(argv[i] ,&buf) < 0) {
            err_ret("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_ISBLK(buf.st_mode))
            ptr="block 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);

}

4.4 設定使用者ID和設定組ID

1、與一個程序相關聯的ID有6個或更多:

實際使用者ID      我們實際上是誰
實際組ID       

有效使用者ID      用於檔案訪問許可權檢查
有效組ID
附屬組ID

儲存的設定使用者ID       由exec函式儲存
儲存的設定組ID 

2、當執行一個程式檔案時,程序的有效使用者ID通常就是實際使用者ID,有效組ID通常是實際組ID。但是可以在檔案模式字(st_mode)中設定一個特殊標誌,其含義是“當執行此檔案時,將程序的有效使用者ID設定為檔案所有者的使用者ID(st_uid)”。有效組ID類似。在檔案模式字中這兩位被成為設定使用者ID(set-user-ID)位和設定組ID(set-group-ID)位。

3、設定使用者ID位及設定組ID位都包含在檔案的st_mode值中。這兩位可分別用常量S_ISUIDS_ISGID測試。

4.5 檔案訪問許可權

1、st_mode值也包含了對檔案的訪問許可權位。每個檔案有9個訪問許可權位,可將它們分成3類:

取自<sys/stat.h>
st_mode遮蔽           含義
S_IRUSR             使用者讀
S_IWUSR             使用者寫
S_IXUSR             使用者執行

S_IRGRP             組讀
S_IWGRP             組寫
S_IXGRP             組執行

S_IROTH             其他讀
S_IWOTH             其他寫
S_IXOTH             其他執行

2、訪問許可權的規則:
(1)第一個規則:我們用名字開啟任一型別的檔案時,對該名字中包含的每一個目錄,包含它可能隱含的當前工作目錄都應具有執行許可權。這就是目錄其執行許可權位常被稱為搜尋位的原因。

(2)對於目錄的讀許可權和執行許可權的意義是不相同的。
一、讀許可權允許我們讀目錄,獲得在該目錄所有檔名的列表。
二、當一個目錄是我們要訪問檔案的路徑名的一個組成部分時,對該目錄的執行許可權使我們可通過該目錄(也就是搜尋該目錄,尋找一個特定的檔名)

(3)對一個檔案的讀許可權決定我們能夠開啟現有檔案進行讀操作。

(4)對一個檔案的寫許可權決定我們能夠開啟現有檔案進行寫操作。

(5)為了在open函式中對一個檔案指定O_TRUNC標誌,必須對該檔案具有寫許可權。

(6)為了在一個目錄中建立一個檔案,必須對該目錄具有寫和執行許可權。

(7)為了刪除一個現有檔案,必須對包含該檔案的目錄具有寫許可權和執行許可權。

3、核心進行檔案訪問許可權測試具體流程:
(1)若程序的有效ID 是0(超級使用者),則允許訪問。這給予了超級使用者對整個檔案系統進行處理的充分的自由。
(2)若程序的有效使用者ID等於檔案的所有者(也就是程序擁有此檔案),那麼如果所有者適當的訪問許可權位(讀、寫、執行許可權)被設定,則允許訪問,否則拒絕訪問。
(3)若程序的有效組ID或程序的附屬組ID之一等於檔案的組ID,那麼如果適當的訪問許可權位置被設定,則允許訪問,否則拒絕訪問。
(4)若其他使用者適當的訪問許可權被設定,則允許訪問,否則拒絕訪問。

4.6 新檔案的目錄的所有權

1、新檔案的所有權規則:
(1)新檔案的使用者ID設定為程序的有效使用者ID
(2)新檔案的組ID可以是程序的有效組ID
(3)新檔案的組ID可以是它所在目錄的組ID

4.7 函式access和faccessat

1、核心以程序的有效使用者ID和有效組ID為基礎執行其訪問許可權測試。access和faccessat函式是按實際使用者ID和實際組ID進行訪問許可權測試的。

#include <unistd.h>
int access(const char *pathname, int mode);
int faccess(int fd, const char *pathname, int mode, int flag);

2、accessat函式與access函式在兩種情況下是相同的:
(1)pathname引數是絕對路徑
(2)fd引數取值為AT_FDCWD而pathname是相對路徑
否則,faccessat計算相對於開啟目錄(由fd引數指向)的pathname。

3、flag引數可以用於改變faccessat的行為,如果flag設定為AT_EACCESS,訪問檢查用的是呼叫程序的有效使用者ID和有效組ID

5、access函式的用法

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

4.8 函式umask

1、umask函式為設定檔案模式建立遮蔽字,並返回之前的值。

#include <sys/stat.h>
mode_t  umask(mode_t cmask);
                        返回值,之前的檔案模式建立遮蔽字。

2、例項:

/*************************************************************************
    > File Name: ins4_9.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Tue 08 Aug 2017 10:51:21 AM CST
 ************************************************************************/

/*  modifity the permission of file bar and foo  */
/*
-rw------- 1 king king 0 Aug  8 10:46 bar
-rw-rw-rw- 1 king king 0 Aug  8 10:46 foo
*/

#include "apue.h"

int
main(void)
{
        struct  stat    statbuf;
        /*  turn on set-group-ID and turn off group-execute  */

        if (stat("foo", &statbuf) < 0)
                err_sys("stat error for foo");
        if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
                err_sys("chmod error for foo");

        /*  set absolute mode to "rw-r--r--"  */

        if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
                err_sys("chmod error for bar");
        exit (0);
}

3、更改程序的檔案模式建立遮蔽字並不影響其父程序(常常是shell)的遮蔽字。

4.9 函式chmod、fchmod和fchmodat

1、chmod、fchmod、fchmodat這3個函式使我們可以更改現有檔案的訪問許可權。

#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);
                            3個函式返回值,若成功,返回0,若出錯,返回-1

2、chmod函式在指定的檔案上操作,而fchmod函式則對已開啟的檔案進行操作。

3、fchmodat與chmod在下面兩種情況是相同的:
(1)pathname引數為絕對路徑
(2)fd取值為AT_FDCWD,而pathname引數為相對路徑。
否則,fchmodat計算相對於開啟目錄(由fd引數指向)的pathname。

4、flag引數可以用於改變fchmodat的行為,當設定了AT_SYMLINK_NOFOLLOW標誌時,fchmodat並不會跟隨符號連結。

5、為了改變一個檔案的許可權位,程序的有效使用者ID必須等於檔案的所有者ID,或者程序必須具有超級使用者許可權。

6、引數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_IWOTH                 其他執行

7、chmod例項:

/*************************************************************************
    > File Name: ins4_9.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Tue 08 Aug 2017 10:51:21 AM CST
 ************************************************************************/

/*  modifity the permission of file bar and foo  */
/*
-rw------- 1 king king 0 Aug  8 10:46 bar
-rw-rw-rw- 1 king king 0 Aug  8 10:46 foo
*/

#include <apue.h>

int
main(void)
{
        struct  stat    statbuf;
        /*  turn on set-group-ID and turn off group-execute  */

        if (stat("foo", &statbuf) < 0)
                err_sys("stat error for foo");
        if (chmod("foo", (statbuf.st_mode & ~S_IXGRP) | S_ISGID) < 0)
                err_sys("chmod error for foo");

        /*  set absolute mode to "rw-r--r--"  */

        if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
                err_sys("chmod error for bar");
        exit (0);
}

4.10 粘著位

1、S_ISVTX位被稱為粘著位(sticky bit)。如果一個可執行程式檔案的這一位被設定,那麼該程式第一次被執行,在其終止時,程式的正文部分的一個副本仍被儲存在交換區(程式的正文部分是機器指令)。這使得下一次執行該程式時還能較快地將其裝載入記憶體。

2、Single UNIX Specification允許針對目錄設定粘著位。如果對一個目錄設定了粘著位,只有對該目錄具有寫許可權的使用者並且滿足下列條件之一,才能刪除或重新命名該目錄下的檔案:

1、擁有此檔案
2、擁有此目錄
3、是超級使用者

4.11 函式chown、fchown、fchownat和lchown

1、下面的幾個chown函式可以用於更改檔案的使用者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);

2、在符號連結情況下,lchown和fchownat(設定了AT_SYMLINK_NOFOLLOW標誌)更改符號連結本身的所有者,而不是該符號連結所指向的檔案的所有者。

3、fchown函式改變fd引數指向的開啟檔案的所有者,既然它在一個已經開啟的檔案上操作,就不能用於改變符號連結的所有者。

4、fchownat函式與chown函式或者lchown函式在下面兩種情況下是相同的:
(1)pathname引數為絕對路徑
(2)fd引數取值為AT_FDCWD而pathname引數為相對路徑。
在以上兩種情況下,如果flag引數設定了AT_SYMLINK_NOFOLLOW標誌,fchownat與lchown行為相同,如果flag引數清除了AT_SYMLINK_NOFOLLOW標誌,fchownat和chown行為相同。

5、_POSIX_CHOWN_RESTRICTED常量可選地定義在標頭檔案

4.12 檔案長度

1、stat結構成員st_size表示以位元組位單位的檔案的長度,此欄位對普通檔案、目錄檔案和符號連結有意義。

2、對於普通檔案,其長度可以是0,在開始讀這種檔案時,將得到檔案結束(end-of-file)指示。

3、對於目錄,檔案長度通常是一個數(如16或512)的整倍數。

4、對於符號連結,檔案長度是在檔名中的實際位元組數
(注意,因為符號連結的長度總是由st_size指示,所以它並不包含C語言用作名字結尾的NULL位元組

5、st_blksize是對檔案I/O比較合適的塊長度。

6、st_blocks是所分配的實際512位元組塊塊數。

檔案中的空洞

1、空洞是由所設定的偏移量超過檔案尾端,並寫入了某些資料後造成的。

4.13 檔案截斷

1、將一個檔案長度截斷為0是一個特例,在開啟檔案時使用O_TRUNC標誌可以做到這一點。為了截斷檔案可以呼叫函式truncate和ftruncate

#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
                                兩個函式的返回值:若成功,返回0,若出錯,返回-1

2、如果該檔案以前的長度大於length,則超過length以外的資料就不能再訪問。
如果以前的長度小於length,檔案長度將增加,形成空洞。

4.14 檔案系統

1、UFS快速檔案系統圖示:
這裡寫圖片描述

————————————————————–

這裡寫圖片描述

2、每個i節點中都有一個連結計數,其值是指向該i節點的目錄項數。只有當連結計數減少至0時,才可刪除檔案(也就是釋放該檔案佔用的資料塊)。

2、在stat結構中,連結計數包含在st_nlink成員中,其基本資料型別是nlink_t。這種連結型別稱為硬連結

3、另一種連結型別稱為符號連結(symbolic link)。符號連結檔案的實際內容(在資料塊中)包含了該符號連結所指向檔案的名字。

4、i節點包含了檔案有關的所有資訊:
(1)檔案型別
(2)檔案訪問許可權位
(3)檔案長度
(4)指向檔案資料塊的指標等。

5、stat結構中的大多數資訊都取自i節點。只有兩個重要資料存放在目錄項中:
(1)i節點編號
(2)檔名

6、因為目錄項中的i節點編號指向同一個檔案系統的相應i節點,一個目錄項不能指向另一個檔案系統的i節點。所以ln命令不能跨越檔案系統。

7、當不更改檔案系統的情況下為一個檔案重新命名時,該檔案的實際內容並未移動,只需構造一個指向現有i節點的新目錄項,並刪除老的目錄項。連結計數不會改變。

這裡寫圖片描述
8、編號為2549的i節點,起型別欄位表示它是一個目錄,連結計數為2.任何一個葉目錄(不包含任何其他目錄的目錄)的連結計數總是2,數值2來自於命名該目錄(testsdir)的目錄項以及在目錄中的.項
編號為1267的i節點,其型別表示它是一個目錄,連結計數大於或等於3。它大於或等於3的原因是,至少有3個目錄項指向它:
(1)一個是命名它的目錄項(圖中沒有表示出來)
(2)第二個是該目錄項中的.專案
(3)第三個是在其子目錄testdir中的..項。

4.15 函式link、linkat、unlink、unlinkat和remove

1、任何一個檔案可以有多個目錄項指向其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

2、這兩個函式建立一個目錄項newpath,它引用現有檔案existingpath。如果newpath已經存在,則返回出錯。

3、對於linkat函式,現有檔案是通過efd和existingpath引數指定的,新的路徑名是通過nfd和newpath引數指定的。
(1)預設情況下,兩個路徑中的任一個是相對路徑,那麼它需要通過相對於對應的檔案描述符進行計算
(2)如果兩個檔案描述符中的任一個設定為AT_FDCWD,那麼相對路徑名(如果它是相對路徑)就通過相對於當前目錄進行計算。

4、當現有檔案是符號連結時,由flag引數來控制linkat函式是建立指向現有符號連結的連結還是建立指向現有符號連結所指向的檔案的連結
(1)如果flag引數設定了AT_SYMLINK_FOLLOW標誌,則建立指向符號連結目標的連結
(2)如果flag**沒有設定AT_SYMLINK_FOLLOW標誌,則建立一個指向符號連結本身的連結**。

5、刪除一個現有的目錄項,可以呼叫unlink函式。

#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname);
                                兩個函式的返回值:若成功,返回0,若出錯,返回-1

(1)這兩個函式刪除目錄項,並將由pathname所引用的檔案的連結計數減1。如果對該檔案還有其他連結,則仍可以通過其他連結訪問該檔案的資料。
(2)如果出錯,則不對該檔案做任何更改。

7、為了解除對檔案的連結,必須對包含該目錄項的目錄具有寫和執行許可權

8、如果對該目錄設定了粘著位,則對該目錄必須具有寫許可權,並且具備下面三個條件之一:
(1)擁有此檔案
(2)擁有此目錄
(3)具有超級使用者許可權

9、只有當連結計數達到0時,該檔案的內容才可被刪除。另一個條件也會組織刪除檔案的內容——只要有程序打開了該檔案,其內容也不能刪除。

10、如果pathname是相對路徑名,那麼unlinkat函式計算相對於由fd檔案描述符引數代表的目錄的路徑名。如果fd引數設定為AT_FDCWD,那麼通過相對呼叫程序的當前工作目錄來計算路徑名。如果pathname是絕對路徑名,那麼fd引數被忽略。

11、開啟一個檔案,然後解除它的連結。執行該程式的程序然後睡眠15秒

/*************************************************************************                                              
    > File Name: ins4_15.c    
    > Author: King            
    > Mail: [email protected]                      
    > Created Time: Tue 08 Aug 2017 02:56:22 PM CST         
 ************************************************************************/                                              

#include <apue.h>             
#include <fcntl.h>            

int                           
main(void)                    
{                             
        if (open("tempfile", O_RDWR) < 0)                   
                err_sys("open error");                      
        if (unlink("tempfile") < 0)                         
                err_sys("unlink error");                    
        printf("file unlink\n");                            
        sleep(15);            
        printf("done\n");     
        exit (0);             
}   

12、unlink的這種特性經常被程式用來確保即使是在程式崩潰時,它所建立的臨時檔案也不會遺留下來

13、我們也可以用remove函式解除對一個檔案或目錄的連結。對於檔案,remove的功能與unlink相同,對於目錄,remoe的功能與rmdir相同。

#include <stdio.h>
int remove(const char *pathname);
                                        返回值:若成功,返回0;若出錯,返回-1

4.16 函式rename和renameat

1、檔案或目錄可以用rename函式或renameat函式進行重新命名

#include <stdio.h>
int rename(const char *oldname, const char *newname);
int rename(int oldfd, const char *oldname, int newfd, const char *newname);
                                        兩個函式的返回值:若成功,返回0,若出錯,返回-1

2、如果oldname制定的是一個檔案而不是目錄,那麼為該檔案或符號連結重新命名。
(1)該情況下,如果newname存在,則它不能引用一個目錄。
(2)如果newname已經存在,而且不是一個目錄,則先將該目錄項刪除然後將oldname重新命名為newname
(3)對包含oldname的目錄以及包含newname的目錄,呼叫程序必須具有寫許可權,因為將更改這兩個目錄。

3、若oldname指的是一個目錄,那麼為該目錄重新命名。
(1)如果newname已存在,則它必須引用一個目錄,而且該目錄應當是空目錄(只有.和..項)。
(2)如果newname存在(而且是一個空目錄),則現將其刪除,然後將oldname重新命名為newname。
(3)另外,當為一個目錄重新命名時,newname不能包含oldname作為其路徑字首。如不能講/usr/foo重新命名為/usr/foo/testdir
(4)不能對.和..重新命名。確切的說,..和.都不能出現在oldname和newname的最後部分

4、特例:如果oldname和newname引用同一個檔案,則函式不做任何更改而且正確返回

5、如果newname已經存在,則呼叫程序需要有寫許可權(如同刪除情況一樣)。另外,呼叫程序講刪除oldname目錄項,並可能要建立newname目錄項,所以它需要對包含oldname及包含newname的目錄具有寫和執行許可權

6、除了當oldname或newname指向相對路徑名時,其他情況下renameat函式與rename函式功能相同。
(1)如果oldname指定了相對路徑,就相對於oldfd引數引用目錄來計算oldname。
(2)如果newname指定了相對路徑,就相對於newfd引用的目錄來計算newname。
(3)oldfd或newfd引數(或兩者)都能設定成AT_FDCWD,此時相對於當前目錄來計算相應的路徑名。

4.17 符號連結

1、引入符號連結的原因是為了避開硬連結的一些限制:
(1)硬連結通常要求連結和檔案位於同一檔案系統中
(2)只有超級使用者才能建立指向目錄的硬連結(在底層檔案系統支援的情況下)

2、對符號連結以及它指向何種物件並無任何檔案系統限制,任何使用者都可以建立指向目錄的符號連結。符號連結一般用於講一個檔案或整個目錄結構移到系統中的另一個位置。

3、當使用以名字引用檔案的函式時,應當瞭解該函式是否處理符號連結。
這裡寫圖片描述
(1)此圖的例外:同時O_CREAT和O_EXCL兩者呼叫open函式。此情況下,若路徑名引用符號連結,open將出錯返回,errno設定為EEXIST。這種處理方式的意圖是堵塞一個安全性漏洞,以防止具有特權的程序被誘騙寫錯誤的檔案。

4.18 建立和讀取符號連結

1、可以用symlink或symlinkat函式建立一個符號連結:

#include <unistd.h>
int symlink(const char *actualpath, const sympath);
int symlinkat(const char *actualpath,int fd, const char *sympath);
                                        兩個函式返回值:若成功,返回0;若出錯,返回-1

2、在建立符號連結時候,並不要求actualpath已經存在,並且actualpath和sympath並不需要位於同一個檔案系統中。

3、如果sympath引數指定的是絕對路徑或者fd引數設定了AT_FDCWD值,那麼symlinkat函式就等同於symlink函式。
否則,sympath引數根據相對開啟檔案描述符引用目錄(由fd引數指定)進行計算。

4、因為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);
                                        兩個函式返回值:若成功,返回讀取的位元組數;若出錯,返回-1

4.19 檔案的時間

1、對每個檔案維護3個時間
這裡寫圖片描述

2、注意,修改時間(st_ntim)和狀態更改時間(st_ctim)之間的區別。
(1)修改時間是檔案內容最後一次被修改的時間
(2)狀態更改時間是該檔案的i節點最後一次被修改的時間

3、i節點被影響的操作:
(1)更改檔案的訪問許可權
(2)更改使用者ID
(3)更改連結數等
它們並沒有更改檔案的實際內容。因為i節點中的所有資訊都是與檔案的實際內容分開存放的。所以,出來要記錄檔案資料修改時間(st_mtim)外,還需要記錄狀態更改時間(st_ctim),也就是更改i節點中資訊的時間。

4、注意,系統並不維護對一個i節點的最後一次訪問時間(st_atim),所以access和stat函式並不更改這3個時間中的任一個。

5、ls命令按這3個時間值中的一個排序進行顯示。系統預設(用-l 或 -t 選項呼叫時)是按檔案的修改時間(st_mtim)的先後順序顯示。-u選項使ls命令是按訪問時間(st_atim)排序。-c選項則使其按狀態更改時間(st_ctim)排序。

6、目錄是包含目錄項(檔名和相關i節點編號)的檔案,增加、刪除或修改目錄項會影響到它所在目錄相關的3個時間

7、各種函式對訪問、修改和狀態更改時間的作用:
這裡寫圖片描述

4.20 futimens、utimensat和utimes

1、一個檔案的訪問時間和修改時間可以用以下幾個函式更改。futimens和utimensat函式可以指定納秒級精度的時間戳。可以用到的資料結構是與stat函式族相同的timespec結構。

#include <sys/stat.h>
int futimens(int fd, const struct timespec times[2]);
int utimensat(int fd, const char *path, const struct timespec times[2], int flag);
                struct timespec {
                         time_t          tv_sec;
                         long            tv_nsec;
                 };

                                        兩個函式返回值:若成功,返回0;若出錯,返回-1

2、這兩個函式的times陣列引數的第一個元素是包含訪問時間,第二個元素包含更改時間。

3、時間戳可以按下列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。

4、執行這些函式所要求的優先權取決於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,就不執行任何許可權檢查。

5、futimens**需要開啟檔案來更改它的時間**,utimensat函式提供了一種使用檔名更改時間的方法。(1)pathname引數相對於fd引數進行計算的,fd要麼是開啟目錄的檔案描述符,要麼設定為特殊值AT_FDCWD(強制通過相對於呼叫程序的當前目錄計算pathname)
(2)如果pathname指定了絕對路徑,那麼fd引數被忽略。

6、utimensat的flag引數可以進一步修改預設行為。
(1)如果設定了AT_SYMLINK_NOFOLLOW標誌,則符號連結本身的時間就會被修改(如果路徑名指向符號連結)。預設的行為是跟隨符號連結,並把檔案的時間改成符號連結的時間。

7、第三個函式utimes包含在 UNIX Specification的XSI擴充套件選項中。

#include <sys/time.h>
int utimes(const char *pathname, const struct timeval times[2]);

8、足以,我們不能對狀態更改時間st_ctim(i節點最近被修改時間)指定一個值,因為呼叫utimes函式時,此欄位會被自動更新。

9、例項:使用帶O_TRUNC選項的open函式講檔案長度截斷為0,但並不更改其訪問時間和修改時間。

/*************************************************************************                                              
    > File Name: ins4_21.c    
    > Author: King            
    > Mail: [email protected]                      
    > Created Time: Sun 13 Aug 2017 10:09:06 AM CST         
 ************************************************************************/                                              

/*  turncate the file length to zero, and keep the modifity time and change time  */                                    
#include "apue.h"             
#include <fcntl.h>            

int                           
main(int argc, char *argv[])  
{                             
        int             i,fd; 
        struct  stat    statbuf;                            
        struct  timespec        times[2];                   
        if (argc < 2)         
                err_quit("Usage: ./ins4_21 <filename>");    
        for (i = 1; i < argc; i++)                          
        {                     
                if (stat(argv[i],&statbuf) < 0)         /*  fetch current times  */                                     
                {             
                        err_ret("%s: stat error", argv[i]); 
                }             

                if ((fd = open(argv[i],O_RDWR | O_TRUNC)) < 0)                                                          
                {             
                        err_ret("%s: open error", argv[i]); 
                        continue;                           
                }             
                times[0] = statbuf.st_atim;                 
                times[1] = statbuf.st_mtim;                 

                if (futimens(fd, times) < 0)                
                        err_ret("%s: futimens error", argv[i]);                                                         
                close(fd);    
        }                     
        exit (0);             
} 

4.21 函式mkdir、mkdirat和rmdir

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

2、這兩個函式建立一個新的空目錄。其中,.和..是自動建立的。所指定的檔案訪問許可權mode由程序的檔案模式建立遮蔽字修改。

3、mkdirat函式當fd引數具有特殊值AT_FDCWD或者引數指定了絕對路徑時,mkdir和mkdirat完全一樣。
否則,fd引數是一個開啟目錄,相對路徑名根據此開啟目錄進行計算。

4、rmdir函式可以刪除一個空目錄。空目錄只包含.和..這兩項的目錄

#include <unistd.h>
int rmdir(const char *pathname);
                                    返回值:若成功,返回0;若出錯,返回-1

5、如果呼叫此函式使目錄的連結計數成為0,並且也沒有其他程序開啟此目錄,則釋放由此目錄佔用的空間。如果在連結計數達到0時,有一個或多個程序開啟此目錄,則在此函式的返回值前刪除最後一個連結及.和..項。

4.22 讀目錄

1、對某個目錄具有訪問許可權的任一使用者都可以讀該目錄,但是,為了防止檔案系統產生混亂,只有核心才能寫目錄

#include <dirent.h>
DIR *opendir(const char *pathname);
DIR *fdopendirat(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);

2、fdopendir函式可以吧檔案描述符轉換成目錄處理函式需要的DIR結構。

3、telldir和seekdir函式 是Single UNIX Specification中的XSI擴充套件。

4、定義在標頭檔案<dirent.h>中的dirent結構與實現有關。實現對此結構所做的定義至少包含下列兩個成員:

ino_t   d_ino;                          /*i-node number*/
char    d_name[]                        /*null-terminated filename*/

(1)d_name大小沒有指定,但必須保證它能包含至少NAME_MAX個位元組(不包含終止null位元組)。

5、由opendir和fdopendir返回的指向DIR結構的指標由另外5個函式使用。
(1)opendir執行初始化操作,使第一個readdir返回目錄中的第一個目錄項(i節點編號和檔名)
(2)DIR結構由fdopendir建立時,readdir返回的第一項取決於傳給fdopendir函式的檔案描述符相關的穩健偏移量。
(3)注意,目錄中各目錄項的順序與實現有關,他們通常並不按字母順序排列

6、例項:便利檔案層次結構程式:

/*************************************************************************
    > File Name: ins4_22.c
    > Author: King
    > Mail: [email protected] 
    > Created Time: Sun 13 Aug 2017 03:53:05 PM CST
 ************************************************************************/

/*  The program of document traversal  */
/*  Before you complie ins4_22.c, you must annotation error.c in apue.h 
 *  and copy error.c 、pathallocate.c to this directory(error.c in Linux /usr/include/error.c).
 *  Complie : gcc ins4_22.c error.c pathallocate.c -o ins4_22
 */

#include "apue.h"