原文來自靜雅齋,轉載請註明出處。javascript
int stat(const char *restrict path, struct stat *restrict buf);
int fstat(int fildes, struct stat *buf);
int lstat(const char *restrict path, struct stat *restrict buf);
int fstatat(int fd, const char *path, struct stat *buf, int flag);
The stat() function obtains information about the file pointed to by path. Read, write or execute permission of the named file is not required, but all directories listed in the path name leading to the file must be searchable. The lstat() function is like stat() except in the case where the named file is a symbolic link; lstat() returns information about the link, while stat() returns information about the file the link references. The attributes cannot be relied on in case of symbolic links. In this case, the only attributes returned from an lstat() that refer to the symbolic link itself are the file type (S_IFLNK), size, blocks, and link count (always 1). The fstat() obtains the same information about an open file known by the file descriptor fildes. The fstatat() system call is equivalent to stat() and lstat() except in the case where the path specifies a relative path. In this case the status is retrieved from a file relative to the directory associated with the file descriptor fd instead of the current work-ing directory.複製代碼
stat()函數得到路徑所指向的文件信息,函數不須要文件的讀寫執行權限,可是路徑樹中的全部目錄都須要搜索權限(search)
lstat()函數和stat相似,除了當路徑指向爲符號連接時,lstat返回連接自己信息,而stat返回對應的文件信息
fstat()獲取已經打開的文件描述符的文件信息
fstatat()系統調用等價於stat和lstat函數,當AT_FDCWD
傳入fd參數,而且路徑參數爲相對路徑時,將會計算基於當前工做目錄的文件,若是路徑爲絕對路徑,則fd參數被忽略,這個函數其實是前面幾個函數的功能集合版本。
第二個參數buf則是一個結構體指針,原著中定義的版本java
struct stat {
mode_t st_mode;
ino_t st_ino;
dev_t st_dev;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
off_t st_size;
struct timespec st_atime;
struct timespec st_mtime;
struct timespec st_ctime;
blksize_t st_blksize;
blkcnt_t st_blocks;
}複製代碼
實際上各個Unix環境都擴充了標準外的實現,以Mac OS X爲例
當未定義_DARWIN_FEATURE_64_BIT_INODE
宏定義node
struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is NOT defined */
dev_t st_dev; /* device inode resides on */
ino_t st_ino; /* inode's number */
mode_t st_mode; /* inode protection mode */
nlink_t st_nlink; /* number of hard links to the file */
uid_t st_uid; /* user-id of owner */
gid_t st_gid; /* group-id of owner */
dev_t st_rdev; /* device type, for special file inode */
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last file status change */
off_t st_size; /* file size, in bytes */
quad_t st_blocks; /* blocks allocated for file */
u_long st_blksize;/* optimal file sys I/O ops blocksize */
u_long st_flags; /* user defined flags for file */
u_long st_gen; /* file generation number */
};複製代碼
當_DARWIN_FEATURE_64_BIT_INODE
被定義後shell
struct stat { /* when _DARWIN_FEATURE_64_BIT_INODE is defined */
dev_t st_dev; /* ID of device containing file */
mode_t st_mode; /* Mode of file (see below) */
nlink_t st_nlink; /* Number of hard links */
ino_t st_ino; /* File serial number */
uid_t st_uid; /* User ID of the file */
gid_t st_gid; /* Group ID of the file */
dev_t st_rdev; /* Device ID */
struct timespec st_atimespec; /* time of last access */
struct timespec st_mtimespec; /* time of last data modification */
struct timespec st_ctimespec; /* time of last status change */
struct timespec st_birthtimespec; /* time of file creation(birth) */
off_t st_size; /* file size, in bytes */
blkcnt_t st_blocks; /* blocks allocated for file */
blksize_t st_blksize; /* optimal blocksize for I/O */
uint32_t st_flags; /* user defined flags for file */
uint32_t st_gen; /* file generation number */
int32_t st_lspare; /* RESERVED: DO NOT USE! */
int64_t st_qspare[2]; /* RESERVED: DO NOT USE! */
};複製代碼
通常這些類型都是基本數據類型,而timespec
則是一個結構體,包含了納秒和秒服務器
_STRUCT_TIMESPEC
{
__darwin_time_t tv_sec;
long tv_nsec;
};複製代碼
Unix系統中全部東西都是文件,這就是Unix的哲學,一切皆是文件。文件類型有如下幾種網絡
普通文件包含了文本文件和二進制文件,想要讓二進制文件可執行,文件必須按照內核規定的格式防止各個段的數據。
FIFO主要用於進程間通訊,套接字則是一個很是重要的文件,用於進程間的網絡通訊,具體能夠分爲好幾種app
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK) /* block special */
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR) /* char special */
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR) /* directory */
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO) /* fifo or socket */
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG) /* regular file */
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK) /* symbolic link */
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK) /* socket */
#if !defined(_POSIX_C_SOURCE) || defined(_DARWIN_C_SOURCE)
#define S_ISWHT(m) (((m) & S_IFMT) == S_IFWHT) /* OBSOLETE: whiteout */
#endif複製代碼
在<sys/stat.h>
中定義了一堆宏來幫助肯定文件類型,這些宏用於判斷st_mode
的類型,上面是Mac OS X的頭文件,相對於POSIX規定多了一項。less
#include "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);
}複製代碼
這就是一個例程,用於判斷文件的類型,這裏使用了lstat函數而不是stat函數以便於咱們觀察到符號連接。socket
對於一個存在於磁盤上的文件來講,它只有一個擁有者uid和擁有者gid,分別被保存在stat
結構體的st_uid
和st_gid
中,可是對於進程來講關聯的ID有6個或者更多,原著中提到了6個ID
|各個ID的名稱|表明的含義|
|-----------------|------------|
|實際用戶ID和實際組ID|咱們其實是誰|
|有效用戶ID、有效組ID和附屬組ID|用於文件訪問權限檢查|
|保存的設置用戶ID和保存的設置組ID|由exec函數保存|
可能在看原著的時候,有不少人看不懂這裏到底講的是什麼。一些作過Node.JS開發的人可能知道,Node是一個單進程的模型,而且自身就包含了Web服務器的工做,Unix系統規定,1024如下的端口只有擁有root權限才能打開,並且爲了保證進程以非root權限執行。因此Node想要在80端口服務就只有三種辦法,第一種就是以root權限運行,而後使用process
更改當前進程的uid和gid,第二種是使用非root權限運行Node,再高位端口打開偵聽,而後使用Nginx反向代理80端口,第三種就是實現master和worker進程,讓Node以多進程的方式運行。
對於開發者來講,有效用戶ID和有效組ID纔是最重要的,由於它們才包含了進程可訪問的權限,包括各類文件、端口的打開,等等資源的使用都是依賴有效用戶ID和有效組ID,實際用戶ID和實際組ID則是繼承於shell的會話用戶,由於是shell啓動的進程。
至於保存的設置用戶ID和保存的設置組ID則是用於exec派生子進程的使用交付給子進程的實際用戶ID和實際組ID。
一般來講,有效用戶ID等於實際用戶ID,有效組ID等於實際組ID。
學過Unix文件權限的朋友應該知道,除了rwx權限之外,還有s權限,這就是設置用戶ID和設置組ID,它能讓進程有效用戶ID和有效組ID等於程序擁有者的uid和gid,例如sudo
和passwd
命令ide
st_mode
成員還包含了文件的訪問權限位。這個東西可能不少朋友都知道,分別是用戶rwx、組rwx和其餘rwx,可使用chmod來改變它們。
文件訪問權限有9種
|st_mode屏蔽|含義|
|----------|----|
|S_IRUSR|用戶讀|
|S_IWUSR|用戶寫|
|S_IXUSR|用戶執行|
|S_IRGRP|組讀|
|S_IWGRP|組寫|
|S_IXGRP|組執行|
|S_IROTH|其餘讀|
|S_IWOTH|其餘寫|
|S_IXOTH|其餘執行|
原著已經講得很詳細了,這裏就再也不贅述。
access, faccessat -- check access permissions of a file or pathname
int access(const char *path, int amode);
int faccessat(int fd, const char *path, int mode, int flag);
The real user ID is used in place of the effective user ID and the real group access list (including the real group ID) are used in place of the effective ID for verifying permission.複製代碼
這兩個函數測試的是實際用戶ID和實際組ID,faccessat函數更增強大,既能夠測試實際用戶ID,也能夠測試有效用戶ID,除非是少數狀況,大部分都是使用faccessat函數
#include "include/apue.h"
#include <fcntl.h>
int main(int argc, char *argv[])
{
if (argc != 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);
}複製代碼
在例程中,使用了兩個函數access
和open
分別測試實際用戶ID、有效用戶ID對於進程的影響,咱們能夠將程序的執行權限設置爲s,而後將全部者換成root,這樣就能測試到進程實際用戶ID和有效用戶ID不一樣帶來的影響了。
mode_t umask(mode_t cmask);
The default mask value is S_IWGRP | S_IWOTH (022, write access for the owner only). Child processes inherit the mask of the calling process.複製代碼
函數是爲進程設置屏蔽字,而且返回原先的值,當cmask中指定某位的bit被打開,那麼文件模式中的相應位獎盃關閉。
Unix環境中系統都提供了一個shell命令umask,用於顯示和設置文件模式建立掩碼字,其實是使用了umask函數,其中cmask參數就是前面st_mode的9個參數按位OR運算獲得。默認狀況下,掩碼字是022,也就是說去除了組寫和其餘寫權限。
#include "include/apue.h"
#include <fcntl.h>
#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
int main(int argc, char *argv[])
{
umask(0);
if (creat("foo", RWRWRW) < 0)
err_sys("creat error for foo");
umask(S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
if (creat("bar", RWRWRW) < 0)
err_sys("creat error for bar");
exit(0);
}複製代碼
而後運行
> umask
022
> ./a.out
> ls -l foo bar
-rw------- 1 uid gid 0 2 1 15:18 bar
-rw-rw-rw- 1 uid gid 0 2 1 15:18 foo
> umask
022複製代碼
這樣應該很清楚了,對於shell來講,他會讀取啓動文件的設置從而在啓動時自行設置爲默認的掩碼字,在運行過程當中,用戶可使用umask命令手動更改掩碼字,shell打開一個進程,新的進程則會繼承shell的umask值,在使用Unix的時候,常常須要使用的mkdir、touch之類的建立文件的命令,這些命令在運行的時候就是繼承shell的umask,在前面的實例中咱們看到,子進程中設置umask掩碼字徹底沒有影響到父進程。
原著中寫的一些東西很是讓人費解,其實是這樣的,原著中少講了一點東西,那就是open
、creat
等函數第三個mode
參數實際上不是所須要建立文件的權限,而是要經過運算的,最終權限是這麼得出的
mode & (~cmask)複製代碼
還記得前面講過的clr_fl
函數嗎,這裏也是一樣的,取反而後進行AND運算,當咱們使用mkdir等命令建立文件文件夾的時候,實際上第三個參數是rwxrwxrwx或者rwrwrw,而後經過umask運算獲得最終的權限。
並且還有一個很是有意思的東西,當咱們使用creat
函數建立文件,而且第三個參數不指定的時候,編譯時會報錯過少的參數,可是當咱們使用open
函數建立文件的時候,不指定第三個參數照樣是能夠建立的,只是第三個參數會被默認指定爲0,也就是沒有任何權限。
因此,當咱們在作開發的時候,若是想要保證本身能徹底指定文件的權限,那麼必須在運行時使用umask函數修改成0,不然非0得umask值可能會關閉咱們須要的權限位置,固然,若是進程不須要關心文件的權限問題,那麼徹底能夠指定rwxrwxrwx或者rwrwrw的權限,而後umask會自動根據默認值將其修改。
int chmod(const char *path, mode_t mode);
int fchmod(int fildes, mode_t mode);
int chmodat(int fd, const char *path, mode_t mode, int flag);複製代碼
第三個chmodat函數多了一個flag參數之外,其他和chmod函數都是同樣的。正如前面的一些函數,flag參數只有一個參數可用AT_SYMLINK_NOFOLLOW
,用於確認是否跟隨連接。當flag爲0的時候,和其餘兩個函數等價。
#define S_IRWXU 0000700 /* RWX mask for owner */
#define S_IRUSR 0000400 /* R for owner */
#define S_IWUSR 0000200 /* W for owner */
#define S_IXUSR 0000100 /* X for owner */
#define S_IRWXG 0000070 /* RWX mask for group */
#define S_IRGRP 0000040 /* R for group */
#define S_IWGRP 0000020 /* W for group */
#define S_IXGRP 0000010 /* X for group */
#define S_IRWXO 0000007 /* RWX mask for other */
#define S_IROTH 0000004 /* R for other */
#define S_IWOTH 0000002 /* W for other */
#define S_IXOTH 0000001 /* X for other */
#define S_ISUID 0004000 /* set user id on execution */
#define S_ISGID 0002000 /* set group id on execution */
#define S_ISVTX 0001000 /* save swapped text even after use */複製代碼
是否感受很是的眼熟,其實就是前面9個文件訪問權限加上S_ISUID
、S_ISGID
兩個設置ID常量,也就是s位權限,S_ISVTX
保存正文常量,以及三個組合常量S_IRWXU
、S_IRWXG
、S_IRWXO
。
#include "include/apue.h"
int main(int argc, char *argv[])
{
struct stat statbuf;
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");
if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
err_sys("chmod error for bar");
exit(0);
}複製代碼
前面一個例程建立了foo和bar文件,而且設置了權限位,當運行上面的程序,這兩個最後狀態以下
> ls -l foo bar
-rw-r--r-- 1 chasontang staff 0 2 1 18:18 bar
-rw-rwSrw- 1 chasontang staff 0 2 1 18:18 foo複製代碼
大寫的S表示只設置了設置組ID但未設置執行ID。
順便說一句,若是這種特殊權限位沒有設置執行權限,其實是根本沒有任何做用的。
忘記講一點了,經過查看man 2 chmod
的說明,能夠看到一段話
Writing or changing the owner of a file turns off the set-user-id and set-group-id bits unless the user is the super-user. This makes the system somewhat more secure by protecting set-user-id (set-group-id) files from remaining set-user-id (set-group-id) if they are modified, at the expense of a degree of compatibility.複製代碼
當寫入或者改變文件的擁有者會自動致使setuid和setgid位被自動清除,這是爲了防止設置用戶ID和設置組ID被濫用,若是有惡意程序修改了帶有這兩個參數的程序,就會被清除這兩個參數,防止被提權。