文件和目錄 1. 文件系統 我們可以把一個磁碟分成一個或多個分區,每個分區包含一個文件系統,這個文件系統由很多柱面組成,而柱面中有一個非常重要的概念叫做 i 節點。 i 節點包含了文件的大部分信息,如文件類型,文件訪問許可權位,文件大小和指向文件數據的指針等,大多數信息都存在st_mode成員中,有兩 ...
目錄
文件和目錄
1. 文件系統
我們可以把一個磁碟分成一個或多個分區,每個分區包含一個文件系統,這個文件系統由很多柱面組成,而柱面中有一個非常重要的概念叫做 i 節點。
i 節點包含了文件的大部分信息,如文件類型,文件訪問許可權位,文件大小和指向文件數據的指針等,大多數信息都存在st_mode
成員中,有兩個重要參數存放在目錄中,文件名和 i 節點編號。
2. 文件屬性結構體
struct stat
{
mode_t st_mode; //文件類型和文件許可權
uid_t st_uid; //用戶ID
gid_t st_gid; //組ID
nlink_t st_nlink; //連到該文件的硬連接數目,剛建立的文件值為1
off_t st_size; //文件位元組數,即文件大小,鏈接文件的話是所指路徑名的長度
blksize_t st_blksize; //文件系統上進行I/O操作時的最優塊大小
blkcnt_t st_blocks; //塊數,du命令就是blocks
time_t st_atime; //最後一次訪問時間,access
time_t st_mtime; //最後一次修改時間,modify
time_t st_ctime; //最後一次文件屬性改變時間,chmod
ino_t st_ino; //文件inode結點號
dev_t st_dev; //文件系統的設備號,該文件系統包含了文件名及對應的i節點
dev_t st_rdev; //針對字元設備和塊設備,實際的設備號,主/次設備號
};
可以通過以下函數獲取文件的信息結構:
#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);
stat
函數可以根據 pathname,獲取對應文件的信息結構;fstat
函數,則可以根據文件描述符fd,獲取對應文件的信息結構;lstat
函數,針對鏈接文件,lstat
函數返回的是鏈接文件本身的信息,而不是鏈接文件指向的文件的信息;fstatat
函數,可以根據文件描述符 fd 和 pathname 來確定要獲取的文件信息,如果獲取的是鏈接文件,flag 參數用來決定獲取的是鏈接文件本身的信息還是鏈接文件所指向的文件信息。
i-node 表和文件數據的映射關係如下圖:
3. 文件信息
st_mode
是 16位的,用二進位表示時,包含三部分信息:
文件類型 設置位 文件許可權
**** *** *** *** ***
3.1 文件類型
最常見的文件類型就是普通文件和目錄文件了,其次還有一些其他類型的文件:
- 普通文件 (-);
- 目錄文件(d);
- 符號鏈接(l):類似於 windows 中的快捷方式;
- 字元特殊文件(c):這種類型的文件提供了對設備不帶緩衝的訪問,每次訪問長度可變;
- 塊特殊文件(b):這種類型的文件提供對設備(如磁碟)帶緩衝的訪問,每次訪問以固定長度為單位進行;
- FIFO(f):有時也叫管道,用於進程間通信;
- 套接字(s):用於進程間的網路通信。
可以通過以下函數來判斷文件類型:
#include <sys/stat.h>
S_ISREG(); //普通文件,是的話,返回1,參數傳入st_mode
S_ISDIR(); //目錄文件
S_ISLNK(); //符號鏈接文件
S_ISCHR(); //字元設備文件
S_ISBLK(); //塊設備文件
S_ISFIFO(); //FIFO文件
S_ISSOCK(); //套接字
3.2 設置位
設置位分為三種:
設置位 | 功能 |
---|---|
S_ISUID | 執行時會將有效用戶ID設置為用戶ID |
S_ISGID | 執行時會將有效組ID設置為組ID |
S_ISVTX | 粘著位 |
粘著位只對目錄有效,起限制刪除的作用,shell
中可以通過chmod +t filename
來設置粘著位。設置了粘著位的目錄,只有當操作用戶擁有該文件,或擁有該目錄,或者是超級用戶,且對該目錄具有寫許可權,才能刪除或重命名該目錄下的文件。
3.3 文件所有權
文件的許可權可以分為讀,寫,執行三種許可權,根據不同的所有者可以分為以下三類:
訪問許可權 | 說明 |
---|---|
S_IRUSR | 用戶讀 |
S_IWUSR | 用戶寫 |
S_IXUSR | 用戶執行 |
S_IRGRP | 組讀 |
S_IWGRP | 組寫 |
S_IXGRP | 組執行 |
S_IROTH | 其他讀 |
S_IWOTH | 其他寫 |
S_IXOTH | 其他執行 |
一般用u
表示用戶,用g
表示組,用o
表示其他。
在檢查文件許可權時,會按照以下順序執行:
- 對於特權級進程,授予其所有訪問許可權;
- 若進程 ID 與文件的用戶 ID 相同,那麼就是文件的屬主許可權;
- 上述條件不滿足的話,會判斷進程的組 ID 和文件的組 ID 是否匹配,匹配就賦予文件的屬組許可權;
- 若以上三點都不滿足,內核會根據 other 許可權,授予進程相應許可權。
對於許可權需要註意的點:
- 對於目錄來說,目錄具備讀許可權,指的是可以獲取該目錄下的文件列表名;而目錄具備執行許可權,指的是能通過目錄,所有目錄下的文件,舉個例子,如果要打開 /usr/include/stdio.h文件,需要對目錄 /,/usr,/usr/include 有執行許可權。一般目錄都需要具備執行許可權。
- 如果要在一個目錄下創建或刪除一個文件,那麼該目錄需要具備寫和執行許可權;
- 新文件的用戶 ID 設置為進程的有效用戶 ID,組 ID 可以使進程的有效組 ID ,也可以是所在目錄的組 ID。
3.4 文件許可權操作介面
3.4.1 文件許可權屏蔽字 umask
在進程創建一個新文件或新目錄時,就一定會使用文件模式創建屏蔽字,對應位為 1 的話說明屏蔽對應許可權:
屏蔽位 | 含義 |
---|---|
0400 | 用戶讀 |
0200 | 用戶寫 |
0100 | 用戶執行 |
0040 | 組讀 |
0020 | 組寫 |
0010 | 組執行 |
0004 | 其他讀 |
0002 | 其他寫 |
0001 | 其他執行 |
函數如下:
#include <sys/stat.h>
mode_t umask(mode_t cmask); //返回之前的文件模式創建屏蔽字
這個參數系統預設就有的,可以通過 shell 命令 umask
查看
umask
3.4.2 文件許可權測試 access 和 faccessat
進程打開一個文件時,預設是以進程有效用戶 ID 和有效組 ID 來訪問的, 但是有時候可能想測試實際用戶 ID 和實際組 ID的許可權,可以通過這兩個函數來實現:
#include <unistd.h>
int access(const char *pathname, int mode);
int faccessat(int fd, const char *pathname, int mode, int flag);
參數說明:
mode: R_OK=測試讀許可權;
W_OK=測試寫許可權;
X_OK=測試執行許可權;
F_OK=有這個文件嗎
返回值:
成功=0; 失敗=-1
但因為不是原子的,所以這函數不安全。
3.4.3 文件許可權修改函數 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);
該函數還可以設置執行時用戶 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 | 其他執行 |
粘著位:對目錄使用,主要是用來限制刪除或重命名目錄下的文件的。
只有對該目錄具備寫許可權的用戶,且滿足以下條件之一的,才能刪除或重命名該目錄下的文件:
- 擁有此文件;
- 擁有此目錄;
- 是超級用戶
4. 文件所有者
st_uid
和st_gid
分別指明瞭文件所屬主和所屬組。
每個文件都有有一個文件所有者和組所有者,我們把當前的所有者和組所有者分別稱為有效用戶 ID和有效組 ID,有的情況下可能還有個附屬組ID。
一般文件新創建時,其用戶 ID 就是創建它的進程的有效用戶 ID,文件的組 ID 就是該進程的有效組 ID。
一般情況下,有效用戶 ID 就是實際用戶 ID,有效組 ID 就是實際組 ID,這兩個參數保存在st_uid
和st_gid
中,可以通過函數S_ISUID
和S_ISGID
測試。
與一個進程相關的 ID 梳理如下:
ID 種類 | 說明 |
---|---|
實際用戶 ID | 我們實際是誰 |
實際組 ID | |
有效用戶 ID | 當前有效的所有者 |
有效組 ID | |
附屬組 ID | |
保存的設置用戶 ID | 由 exec 函數保存 |
保存的設置組 ID |
文件用戶 ID 和組 ID 可以通過以下函數修改,chown
,fchown
,fchownat
和 lchown
:
#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);
函數功能:
改變對應文件的用戶ID和組ID.
只有特權進程才能使用chown
函數來改變文件的用戶 ID,對於非特權進程,如果進程的有效用戶 ID 與文件的用戶 ID 相匹配,那麼可使用chown
函數將文件的組 ID 更換為其從屬的任一屬組的 ID。
參數owner
或者group
為 -1 時表明不變。
5. 文件鏈接
結構成員變數st_nlink
代表了文件的硬鏈接數。
文件鏈接類型分為硬鏈接和符號鏈接,兩種區別在於:
- 硬鏈接通常要求鏈接和文件在同一文件系統中;
- 只有超級用戶才能創建指向目錄的硬鏈接(主要是為了防止在文件系統中引入迴圈)。
5.1 硬鏈接
硬鏈接通常要求鏈接和文件在同一文件夾,一般也不允許鏈接到目錄,因為鏈接到目錄可能會出現迴圈的情況,如:
有文件 /temp
在 /temp 中創建一個硬鏈接文件 link 和普通文件 a,那麼遍歷文件的時候,可能會出現以下情況:
/temp/a
/temp/link/a
/temp/link/link/a
...
上述情況下,如果是符號鏈接造成的迴圈,可以通過unlink
函數解除,因為unlink
不跟隨符號鏈接。
硬鏈接文件的創建函數如下:
#include <unistd.h>
int link(const char *existingpath, const char *newpath);
int linkat(int efd, const char *exitingpath, int nfd, const char *newpath, int flag);
參數說明:
existingpath: 要創建硬鏈接的源文件的路徑,不應該是符號鏈接文件的路徑;
newpath: 創建的硬鏈接的文件路徑,如果該路徑已存在,會報錯;
flag: 通過該標誌位控制是否解引用鏈接文件.
硬鏈接就是為文件創建一個名字,多個文件名通過相同的 inode 編號,指向同一文件,操作文件時,隨便用哪一個都行。
5.2 符號鏈接
符號鏈接在使用上的關鍵就是看函數能不能處理符號鏈接,一般情況下對符號鏈接的處理都是處理鏈接到的文件,而非處理鏈接文件本身。
5.2.1 符號鏈接創建函數 symlink,symlinkat
#include <unistd.h>
int symlink(const char *pathname, const car *sympath);
int symlinkat(const char *pathname, int fd, const char *sympath);
pathname
可以不存在,因為即使存在,也不能保證它不會被刪除,就會使該符號鏈接變為懸空鏈接,這時,對該符號鏈接解引用時都會出錯。
這裡要註意的是symlink
的目標文件的路徑不是根據當前路徑計算的,而是根據符號鏈接文件的路徑計算的,如:
有目錄dir1,dir1中有文件a.txt,現在想在dir1目錄下創建a.txt文件的符號鏈接文件b.txt,
並不是
symlink("./dir1/a.txt", "./dir1/b.txt"); -- 1
而是
symlink("./a.txt", "./dir1/b.txt"); -- 2
1 中寫法的話會從 ./dir1/dir1/a.txt 中找 a.txt 文件,這樣明顯是不正確的
2 中寫法的話就是從 ./dir1/a.txt 中找 a.txt 文件
看上去會有點繞,shell 中的ls -s
,命令創建符號鏈接文件也是這樣子的,否則會找不到。
符號鏈接的長度指的是指向的目標文件的文件名長度。
5.2.2 符號鏈接讀取函數 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);
5.2.3 鏈接文件刪除函數unlink()
鏈接文件的刪除必須滿足以下兩個條件:
- 文件的鏈接數為 0;
- 當前沒有進程打開該文件。
可通過以下函數刪除:
#include <unistd.h>
int unlink(const char *pathname);
int unlinkat(int fd, const char *pathname, int flag);
要註意,unlink
無法刪除目錄文件,刪除目錄文件要用rmdir
或remove
函數。
6. 文件大小
stat
結構成員st_size
表示以位元組為單位的文件的長度,只對普通文件,目錄文件和鏈接文件有效。
對於普通文件,文件長度可以是 0,在讀這類文件時,將得到文件結束(end-of-file)指示;對於目錄,文件長度通常是一個數的整數倍,如 16 的整數倍或 512 的整數倍;對於符號鏈接,文件長度是在文件名中的位元組數,對於共用記憶體對象,該欄位表示對象的大小,如:
lib 文件是個鏈接文件,鏈接到 usr/lib
那麼連接文件 lib 的文件長度為 7,也就是名字長度
st_blksize
是對文件 I/O 較合適的塊長度,一般是 4095。
st_blocks
是所分配的實際 512 位元組塊塊數,並不是所有的 OS 都是 512 位元組,這個要根據實際情況區分。
對於文件長度,有幾種特殊情況:
6.1 文件空洞
文件空洞是文件的偏移量超過文件尾端,並寫入了某些數據後造成的。
6.2 文件截斷
有時候需要在文件尾端截去一些數據以縮短文件,這種情況下啊就需要用到文件截斷,相關函數如下:
#include <unistd.h>
int truncate(const char *pathname, off_t length);
int ftruncate(int fd, off_t length);
如果文件當前長度大於參數length
,將會截斷;如果當前長度小於參數length
,會在文件尾部添加一系列空位元組或是一個文件空洞。
du
命令顯示的是文件實際所占的塊的大小,所以對於有空洞文件,可能會出現st_size
比st_blocks
大的情況,因為st_size
是包含空洞文件的空洞的。
7. 文件的時間
文件的時間可以分為以下三種:
- 文件數據最後訪問時間:st_atime;
- 文件數據最後修改時間:st_mtime;
- 文件狀態最後改變時間:st_ctime。
需要註意修改時間和文件狀態改變的時間的區別,修改指的是文件內容最後一次被修改的時間,狀態更改指的是該文件 i 節點最後一次被修改的時間,如文件訪問許可權,用戶ID的更改等。
系統預設按照修改時間排序,也可以通過以下命令修改排序方式:
ls -u //按訪問時間排序
ls -c //按狀態更改時間排序
文件的訪問時間和修改時間是可以通過函數修改的,但是文件狀態不行,文件狀態發生變化時,會自動更新該欄位數據。utime
函數調用成功後會將狀態最後改變時間設置為當前時間。
#include <utime.h>
int utime(const char *pathname, const struct utimbuf *buf);
參數說明:
pathname: 文件所在路徑;
buf: 要修改的時間值,為NULL的話,設置為當前時間;
返回說明:
成功=0; 失敗=-1
utimbuf
結構體定義如下:
struct utimbuf {
time_t actime; //訪問時間
time_t modtime; //修改時間
};
Linux 還提供了源於 BSD 的utimes
系統調用,其功能類似於utime
:
#include <sys/time.h>
int utimes(const char *pathname, const struct timeval tv[2]);
utimes
跟utime
的最大區別在於提供了微秒級別的時間。
還可以使用futimes
函數使用文件描述符來指定文件,對於符號鏈接文件,可以使用lutimes
函數來對符號鏈接本身做操作:
#include <sys/time.h>
int futimes(int fd, const struct timeval tv[2]);
int lutimes(const char *pathname, const struct timeval tv[2]); //不會對符號鏈接文件解引用
還可以使用納秒級別的時間修改函數:
#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);
參數說明:
times 數組的第一個是訪問時間,第二個是修改時間
結構體timespec
定義如下:
struct timespec {
time_t tv_sec; //秒
long tv_nsec; //納秒
};
有以下幾種情況:
- times 參數是一個空指針,那麼訪問時間和修改時間都設置為當前時間;
- times 參數任意一個元素的
tv_nsec
欄位是 UTIME_NOW,相應的時間戳設置為當前時間(註意,不是tv_sec
欄位); - times 參數任意一個元素的
tv_nsec
欄位是 UTIME_OMIT,相應的時間戳保持不變; - 如果既不是 UTIME_NOW,也不是 UTIME_OMIT,相應的時間戳設置為相應的
tv_sec
和tv_nsec
欄位的值。
修改的條件是進程對該文件具備寫許可權,且進程的有效用戶 ID 等於 該文件的所有者 ID,或者是超級用戶。
對於utimensat
函數,如果fd
指定為AT_FDCWD
,此時對path
參數的解讀與utimes
類似。flags
參數一般為 0,但是對於符號鏈接文件,如果想對鏈接本身操作,可以將flags
參數設置為AT_SYMLINK_NOFOLLOW
。
8. 設備特殊文件
每個文件系統所在的存儲設備都由其主、次設備號表示,可以使用major()
和minor()
分別訪問主、次設備號。
系統中與每個文件名關聯的st_dev
值是文件系統的設備號,st_ino
欄位則該包含文件的 i 節點。利用以上兩者,可在所有文件系統中唯一標識某個文件。
只是有字元特殊文件和塊特殊文件才有st_rdev
值,此值包含了實際設備的設備號。
9. 通用文件操作函數
9.1 文件重命名 rename/renameat
當不更換文件系統為一個文件重命名時,該文件的實際內容並未移動,只需要構造一個指向現有 i 節點的新目錄項,並刪除老的目錄項。鏈接計數並不會改變。
#include <stdio.h>
int rename(const char *oldname, const char *newname);
int renameat(int oldfd, const char *oldname, int newfd, const char *newname);
如果newname
已存在,那麼會將其覆蓋,如果oldname
和newname
一樣,則不發生變化。
以下幾點需要註意:
- 如果 oldname 或 newname 指代的是符號鏈接,那麼處理的是符號鏈接本身,而非鏈接的文件;
- 如果是對目錄文件重命名,要保證
newname
要麼不存在,要麼存在的目錄文件是個空目錄,因為rename
是不會移動數據的,且要註意,newname
不能包含oldname
作為路徑名首碼; rename
只能用於同一文件系統。
9.2 文件刪除 remove,rmdir
rmdir
函數可以刪除一個空目錄。
#include <unistd.h>
int rmdir(const char *pathname);
remove
函數可以用來解除對一個文件或目錄的鏈接,對於文件來說,remove
功能和unlink
一樣,對於目錄來說,remove
功能和rmdir
一樣。
#include <stdio.h>
int remove(const char *pathname);
10. 目錄文件
10.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);
目錄文件的創建所指定的文件訪問許可權 mode 由進程的文件模式創建屏蔽字修改(umask)。
常見的錯誤是指定與文件相同的 mode(只具備讀,寫許可權),但是,對於目錄,通常需要執行許可權,以允許訪問該目錄中的文件名。
可以使用rmdir
函數刪除目錄文件:
#include <unistd.h>
int rmdir(const char *pathname);
10.2 讀目錄
#include <dirent.h>
DIR *opendir(const char *pathname);//路徑名方式打開一個目錄
DIR *fopendir(int fd); //以文件描述符的方式打開一個目錄,可以將文件描述符轉換為 DIR 結構
struct dirent *readdir(DIR *dp); //讀目錄,一次返回一條目錄
void rewinddir(DIR *dp); //將目錄流重新移動到原點
int closedir(DIR *dp); //關閉目錄
long telldir(DIR *dp);
void seekdir(DIR *dp, long loc);
opendir
函數在執行時會為目錄相關聯的文件描述符設置close_on_exec
標誌,以確保在執行exec
時自動關閉該文件描述符。
dirent
定義如下:
struct dirent {
ino_t d_ino; //inode節點號
char d_name[256]; //目錄文件名
};
10.3 文件樹遍歷 nftw()
#define _XOPEN_SOURCE 500
#include <ftw.h>
int nftw(const char *dirpath, int (*func)(const char *pathname, const struct stat *statbuf, int typeflag, struct FTW *ftwbuf), int nopenfd, int flags);
參數說明:
dirpath: 目錄路徑;
func: 目錄樹中每個文件要執行的函數;
nopenfd: 可使用的文件描述符的最大值,一層一個;
flags: 操作標誌;
函數功能:
預設以前序遍歷方式遍歷文件樹,併為每個文件調用一個func函數
參數flags
含義如下:
- FTW_CHDIR:在處理目錄之前會先調用
chdir
函數進入該目錄,主要是配合func
函數使用; - FTW_DEPTH:使用後序遍歷;
- FTW_MOUNT:不會越界進入另一個文件系統;
- FTW_PHYS:對符號鏈接文件不會解引用。
函數func
用的是stat
函數,所以對鏈接文件,預設是解引用的。
typeflag
參數定義如下:
- FTW_D:目錄文件;
- FTW_DNR:是一個不能讀取的目錄文件;
- FTW_DP:正在對一個目錄進行後序遍歷,當前項是一個目錄,其所包含的文件和子目錄已完成處理;
- FTW_F:該文件的類型是除目錄和符號鏈接以外的任何類型;
- FTW_NS:對該文件調用
stat
失敗; - FTW_SL:是一個符號鏈接文件;
- FTW_SLN:是一個懸空的符號鏈接文件。
參數ftwbuf
是一個FTW
結構型的,定義如下:
struct FTW {
int base; //文件名在fpath中的偏移地址
int level; //目錄樹的層次,也就是深度
};
每次調用func
都需要返回一個整型值,如果返回 0,那麼nftw
函數還會繼續對樹進行遍歷,若返回非 0 值,則通知nftw
停止對樹的遍歷,nftw
函數的返回值與func
的返回值相同。
nftw
內部實現會動態分配記憶體,所以如果直接通過longjmp
函數跳轉出去,至少會引起記憶體泄露問題。
10.4 工作路徑
可以通過chdir
或fchddir
函數來修改當前工作路徑:
#include <unistd.h>
int chdir(const char *pathname);
int fchdir(int fd);
需要註意的是工作路徑是進程的一個屬性,所以它隻影響調用chdir
的進程本身,而不影響其他進程。
可以通過getcwd
函數來獲取當前工作路徑:
#include <unistd.h>
char *getcwd(char *buf, size_t size);
參數說明:
buf: 保存返回的當前工作路徑;
size: 緩衝區大小,超過會報錯
如果buf
為 NULL 的話,且size
為 0 的話,系統會按需分配一個緩衝區,並將指向該緩衝區的指針作為函數的返回值。但是此時需要註意用完要釋放,否則會造成記憶體泄漏。
10.5 修改進程的根目錄 chroot()
#define _BSD_SOURCE
#include <unistd.h>
int chroot(const char *pathname);
chroot
函數可以修改當前進程的根目錄,主要功能就在於限制當前進程訪問其他文件,只能訪問當前根目錄下的文件。
但是這個機制並不是完全安全的:
對於特權級程式來說,可以使用mknod
函數創建一個記憶體設備文件,然後可以通過這個記憶體設備文件訪問 RAM 的內容;
對於非特權級程式來說,也要註意以下幾種情況:
- 調用
chroot
修改完根目錄後,沒有立即將當前工作目錄更新過來,此時還是可以訪問其他文件的; - 如果在調用
chroot
之前有打開一個監禁區外的文件,那麼在調用chroot
後可以利用這個文件描述符實現越獄; - 利用域套接字也是可以實現越獄的。
10.6 解析路徑名 realpath()
#include <stdlib.h>
char *realpath(const char *pathname, char *resolved_path);
參數說明:
pathname: 要解析的路徑;
resolved_path: 保存解析結果;
返回說明:
成功=返回指向結果的指針; 失敗=NULL
會對pathname
一 一解析,最後生成絕對路徑名。
10.7 解析路徑名字元串 dirname()和basename()
#include <libgen.h>
char *dirname(char *pathname); //返回一個路徑的目錄部分
char *basename(char *pathname); //返回一個路徑的文件名部分