13-stat 結構體 st_mode 欄位
上一篇我們使用了 stat 函式取得了 test.txt 檔案的相關屬性,這些屬性都儲存在一個叫 struct stat 的結構體中:
struct stat {
dev_t st_dev; /* 包含這個檔案的裝置 ID */
ino_t st_ino; /* inode 編號 */
mode_t st_mode; /* 訪問許可權 */
nlink_t st_nlink; /* 硬連結數量 */
uid_t st_uid; /* 使用者ID */
gid_t st_gid; /* 組ID */
dev_t st_rdev; /* 裝置ID */
off_t st_size; /* 檔案佔用的位元組數 */
blksize_t st_blksize; /* 檔案系統塊大小 */
blkcnt_t st_blocks; /* 檔案佔用了幾個512位元組 */
time_t st_atime; /* 最後訪問時間 */
time_t st_mtime; /* 最後更改時間 */
time_t st_ctime; /* 最後狀態更改時間 */
};
本篇我們只介紹 st_mode 欄位。在上一篇中,我們得到的 st_mode 欄位的10進位制值是 33204. 記住這個值,待會我們要分析。
1 一堆 ID
- 實際使用者 ID、實際組 ID
- 有效使用者 ID、有效組 ID
- 設定使用者 ID、設定組 ID
1、實際使用者ID(uid)和實際使用者組ID(gid):標識我是誰。也就是登入使用者的uid和gid,比如我的Linux以allen登入,在Linux執行的所有的命令的實際使用者ID都是allen的uid,實際使用者組ID都是allen的gid(可以用id命令檢視)。
2、有效使用者ID(euid)和有效使用者組ID(egid):程序用來決定我們對資源的訪問許可權。一般情況下,有效使用者ID等於實際使用者ID,有效使用者組ID等於實際使用者組ID。當設定-使用者-ID(SUID)位設定,則有效使用者ID等於檔案的所有者的uid,而不是實際使用者ID;同樣,如果設定了設定-使用者組-ID(SGID)位,則有效使用者組ID等於檔案所有者的gid,而不是實際使用者組ID。
例:
- 你在登入 linux 時輸入的帳號,就是實際使用者。比方說我登入的帳號是 allen,那麼實際使用者就是 allen.
- 當你以 sudo 執行命令時,比如 sudo rm text.txt,這時候 ,在執行這條命令的時候 ,實際使用者是 allen,有效使用者是 root。
- 假設當前實際使用者是 allen,你執行的檔案 a.out 的所有者是 david,這時候你在執行這條命令的時候 ,實際使用者是 allen,有效使用者也是 allen,如果 a.out 這個檔案設定使用者 ID 標誌開啟,那麼執行 a.out 的時候,實際使用者是 allen, 有效使用者是 david.
2 st_mode 的結構
st_mode 主要包含了 3 部分資訊:
- 15-12 位儲存檔案型別
- 11-9 位儲存執行檔案時設定的資訊
- 8-0 位儲存檔案訪問許可權
圖1 展示了 st_mode 各個位的結構。
圖1 st_mode 結構(圖中花括號裡的數字都是 2 進位制)
2.1 黏著位(sticky)
要刪除一個檔案,你不一定要有這個檔案的寫許可權,但你一定要有這個檔案的上級目錄的寫許可權。也就是說,你即使沒有一個檔案的寫許可權,但你有這個檔案的上級目錄的寫許可權,你也可以把這個檔案給刪除,而如果沒有一個目錄的寫許可權,也就不能在這個目錄下建立檔案。
如何才能使一個目錄既可以讓任何使用者寫入檔案,又不讓使用者刪除這個目錄下他人的檔案,sticky就是能起到這個作用。sticky一般只用在目錄上,用在檔案上起不到什麼作用。
在一個目錄上設了sticky位後,(如/home,許可權為1777)所有的使用者都可以在這個目錄下建立檔案,但只能刪除自己建立的檔案(root除外),這就對所有使用者能寫的目錄下的使用者檔案啟到了保護的作用。
如果使用者對目錄有寫許可權,則可以刪除其中的檔案和子目錄,即使該使用者不是這些檔案的所有者,而且也沒有讀或寫許可。黏著位出現執行許可的位置上,用t表示,設定了該位後,其它使用者就不可以刪除不屬於他的檔案和目錄。但是該目錄下的目錄不繼承該許可權,要再設定才可使用。
普通檔案的sticky位會被linux核心忽略。
目錄的sticky位表示這個目錄裡的檔案只能被owner和root刪除 。
/tmp常被我們用來存放臨時檔案,是所有使用者。但是我們不希望別的使用者隨隨便便的就刪除了自己的檔案,於是便有了黏著位,它的作用便是讓使用者只能刪除屬於自己的檔案。
$ ls -dl /tmp
drwxrwxrwt 15 root root ......... // 注意 other 使用者的許可權位,x 變成了 t
那麼原來的執行標誌x到哪裡去了呢? 系統是這樣規定的, 假如本來在該位上有x, 則這些特別標誌 (suid, sgid, sticky) 顯示為小寫字母 (s, s, t). 否則, 顯示為大寫字母 (S, S, T) 。
另外:
chmod 777 abc
chmod +t abc
等價於
chmod 1777 abc
3 例項分析
3.1 普通檔案例項
上一篇我們得到的 st_mode 10進位制值是 33204,轉換成 8 進位制後為 100664.
把它填到圖1中後,是這樣:
圖2 33204 填到 st_mode 中
根據圖2 我們可以得到如下資訊:
- 1000: 這是一個常規檔案
- 000: 執行時設定資訊為空,黏著位為 0
- 110-110-100: 使用者許可權為
RW-
,組員許可權為RW-
,其他人許可權為R--
3.2 帶設定-使用者-ID標誌的檔案分析
我們要分析的檔案是這樣的:
-rwsr-xr-x 1 root root 7612 11月 28 14:58 append
-rw-r--r-- 1 root root 6 11月 28 14:52 test.txt
append 是一個可執行檔案,它的作用是以追加的方式開啟 test.txt,程式碼如下:
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
int main() {
int fd = open("test.txt", O_WRONLY | O_APPEND);
if (fd == -1) {
perror("open");
return -1;
}
printf("uid: %d\n", getuid());
printf("euid: %d\n", geteuid());
close(fd);
return 0;
}
append 檔案生成方式如下:
$ gcc append.c -o append
$ sudo chown root:root append
$ sudo chmod u+s append
當前實際使用者並不是 root,而是 allen.
一般來說,直接執行 ./append 會出現訪問被禁止,因為 test.txt 檔案的所有者並不是 allen. 然而,這裡的會正常執行,結果顯示如下:
uid:1000
euid:0
即實際使用者 ID 是 allen, 有效使用者 id 是 root. 為什麼直接執行 ./append 後有效使用者 id 不是 allen,而變成 了 root? 這裡實際上是 append 檔案的 suid 位被置 1 了。
通過 stat 函式,我們得到的 append 的 st_mode 值為 35309. 轉換成 8 進位制後為 104755. 將其填充到圖 1 的結構中後是這樣的:
圖3 填充 st_mode
分析圖3,可以知道:
- 1000:這是一個普通檔案
- 100:suid 為 1
- 111-101-101:使用者許可權為 RWX
,組員許可權為R-X
,其他人許可權為R-X
所以這裡的 append 檔案在執行的時候,發現 suid 標誌被開啟,它會把有效使用者id (euid) 設定成 append 檔案的所有者(root)。
4 一些常用的巨集
前面我們通過手工分析 st_mode 欄位,實際上是很不方便的。實際寫程式,你可以使用 st_mode & 掩碼來得到 st_mode 中特定的部分。比如:
- st_mode & 0170000 : 得到檔案型別
- st_mode & 0007000 : 得到執行檔案時設定資訊
- st_mode & 0000777 : 得到許可權位
- st_mode & 00100: 判斷所有者是否可執行
……
如果在程式中真這麼寫,估計很少有人願意去看。實際上,可以使用 linux 預定義的一些巨集來代替這些生硬的數字。這些巨集定義在 sys/stat.h 標頭檔案中。
#define S_IFMT 00170000
#define S_IFSOCK 0140000
#define S_IFLNK 0120000
#define S_IFREG 0100000
#define S_IFBLK 0060000
#define S_IFDIR 0040000
#define S_IFCHR 0020000
#define S_IFIFO 0010000
#define S_ISUID 0004000
#define S_ISGID 0002000
#define S_ISVTX 0001000
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
#define S_IRWXU 00700
#define S_IRUSR 00400
#define S_IWUSR 00200
#define S_IXUSR 00100
#define S_IRWXG 00070
#define S_IRGRP 00040
#define S_IWGRP 00020
#define S_IXGRP 00010
#define S_IRWXO 00007
#define S_IROTH 00004
#define S_IWOTH 00002
#define S_IXOTH 00001
總結
本篇的內容有點繁而多,需要掌握的知識點如下:
- 實際使用者 ID,實際使用者組 ID
- 有效使用者 ID,有效使用者組 ID
- 設定使用者 ID,設定使用者組 ID
- st_mode 欄位結構(檔案型別-執行時檔案設定資訊-許可權位)
- 黏著位