1. 程式人生 > >13-stat 結構體 st_mode 欄位

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。

  1. 你在登入 linux 時輸入的帳號,就是實際使用者。比方說我登入的帳號是 allen,那麼實際使用者就是 allen.
  2. 當你以 sudo 執行命令時,比如 sudo rm text.txt,這時候 ,在執行這條命令的時候 ,實際使用者是 allen,有效使用者是 root。
  3. 假設當前實際使用者是 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 1128 14:58 append
-rw-r--r--  1 root  root     6 1128 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 欄位結構(檔案型別-執行時檔案設定資訊-許可權位)
  • 黏著位