思考一個問題(如果你很清楚的知道答案,那麼下面的內容你就不必看了):
假設有一個用戶A,他創建了一個可執行文件a.out(rwxrwx–x)和一個文本文件a.txt(rwxrwx—),在a.out中,會對文件進行讀寫。假設還有另一個用戶B,和A不是一個屬組的,他現在執行a.out,假設叫進程B,這時候
- 請問可以成功對文件a.txt進行讀寫嗎?什麼情況下可以?
- 進程B的實際用戶ID是?有效用戶ID是?進程B的設置用戶ID有什麼用?
- 說明:
- B當然不是root用戶
- a.out的文件模式字(st_mode)沒有說明因爲這是題目討論的一部分
- 想確切的知道答案的話最好看一下《unix環境下高級編程》的第四章
1、stat()、fstat()、fstatat()、lstat()
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
int lstat(const char *path, struct stat *buf);
int fstatat(int dirfd, const char *pathname, struct stat *buf,
int flags);
-
stat函數返回與命名文件有關的信息結構體
-
lstat類似,但當文件是一個符號鏈接時,lstat返回符號鏈接的有關信息,而非它鏈接的文件的信息
-
fstatat爲一個相對於當前打開目錄(fd)的路徑名返回文件統計信息
- flags爲AT_SYMLINK_NOFOLLOW時不會跟隨符號鏈接
- fd爲AT_FDCWD時爲當前目錄(path不是絕對路徑)
-
stat結構如下:
struct stat { dev_t st_dev; /* ID of device containing file */ ino_t st_ino; /* inode number */ mode_t st_mode; /* protection */ nlink_t st_nlink; /* number of hard links */ uid_t st_uid; /* user ID of owner */ gid_t st_gid; /* group ID of owner */ dev_t st_rdev; /* device ID (if special file) */ off_t st_size; /* total size, in bytes */ blksize_t st_blksize; /* blocksize for file system I/O */ blkcnt_t st_blocks; /* number of 512B blocks allocated */ time_t st_atime; /* time of last access */ time_t st_mtime; /* time of last modification */ time_t st_ctime; /* time of last status change */ };
2、文件類型
- -dlbcps 分別表示普通文件、目錄文件、鏈接文件、塊設備文件,比如磁盤、串行端口設備文件(字符設備文件)、管道文件、套接字
3、設置用戶ID和設置組ID
- 與一個進程相關的有7個ID
- 1.實際用戶ID
- 2.實際組ID
- 3.有效用戶ID
- 4.有效組ID
- 5.附屬組ID
- 6.保存的設置用戶ID
- 7.保存的設置組ID
- 可通過sysconf(_SC_SAVED_IDS)檢查是否支持
- 345決定了文件訪問權限
- 67在執行程序時包含了12的副本
- 通常3=1,4=2
- 通過設置文件模式字67可以:執行此文件時,將進程的3設置文件所有者的用戶ID,將4設置爲文件的組所有者ID
- 67可用S_ISUID(st_mode)、S_ISGID(st_mode)測試
4、文件訪問權限
- 所有文件都有文件訪問權限
- st_mode值包含了文件的訪問權限位,即
S_I[RWX]USR|S_I[RWX]GRP|S_I[RWX]OTH
- 思考:目錄的讀權限和可執行權限有什麼區別:
- 打開任意文件時,對該文件包含的每一個目錄都應具有執行權限
- 讀權限決定能否打開進行讀
- 寫權限決定了能否打開進行寫
- O_TRUNK標誌必須具有寫權限
- 要在目錄中創建文件必須具有寫權限和執行權限
- rm必須具有WX權限
- exec執行的文件必須具有R權限,且必須是普通文件
- 每次打開、創建或刪除一個文件時,內核進行下面的測試:
- 若進程的有效用戶ID是0,那麼允許訪問
- 若有效用戶ID等於文件所有者ID(進程擁有此文件),適當權限位被設置時允許訪問
- 有效組ID或附屬組ID等於文件組ID時,同上
- 若其他用戶適當權限被設置,同上
5、新文件和目錄的所有權
- 新文件的用戶ID設置爲進程的有效用戶ID
- 組ID可以是下面的(linux下先2再考慮1):
- 1.進程的有效組ID
- 2.所在目錄的組ID
6、access()和faccessat()
int access(const char *pathname, int mode);
int faccessat(int dirfd, const char *pathname, int mode, int flags);
- open()打開一個文件時,內核用有效用戶ID和有效組ID爲基礎執行訪問權限測試。
- 有時用戶希望按照實際用戶ID和實際組ID來測試訪問權限(即用戶可能已經通過設置用戶以root權限運行,但還想驗證實際用戶能否訪問一個給定的文件)
- mode可以下面常數的按位與
- R_OK、W_OK、X_OK
- 當pathname爲絕對路徑或fd取值爲AT_FDCWD時,faccessat()與access()相同
- flag爲AT_EACCESS時,檢查的就是有效用戶ID而非實際用戶ID
7、umask()
mode_t umask(mode_t mask);
- 爲進程設置文件模式創建屏蔽字,並返回之前的值–不影響父進程
- 想確保任何用戶都能讀文件,就應該設置umask爲0
umask -S
查看許可權限而非拒絕權限
8、chmod()、fchmod()、fchmodat()
int chmod(const char *path, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodat(int dirfd, const char *pathname, mode_t mode, int flags);
- 上述函數改變現有文件的讀寫權限
- 在pathname爲絕對路徑或dirfd爲AT_FDCWD時fchmod和fchmodat相同
- 當flags爲AT_SYMLINK_NOFOLLOW標誌時,fchmodat不會跟隨符號鏈接
- mode參數除了前面提到的
S_I[RWX]USR|S_I[RWX]GRP|S_I[RWX]OTH
:- S_ISUID:執行時設置用戶ID
- S_ISGID:執行時設置組ID
- S_ISVTX:保存正文(沾着位)
9、沾着位
- UNIX版本稱它爲保存正文位,配置了虛擬存儲系統以及快速文件系統後不再需要這種技術了
- 現在擴展了沾着位的用法,如果一個目錄設置了沾着位,對該目錄具有寫權限的用戶在下列條件滿足時才能刪除或重命名目錄下的文件(/tmp、/var/tmp):
- 擁有此文件
- 擁有此目錄
- 是超級用戶
10、chown()、fchown()、fchownat()、lchown()
int chown(const char *path, uid_t owner, gid_t group);
int fchown(int fd, uid_t owner, gid_t group);
int lchown(const char *path, uid_t owner, gid_t group);
int fchownat(int dirfd, const char *pathname,
uid_t owner, gid_t group, int flags);
- lchown和fchownat(設置了AT_SYMLINK_NOFOLLOW)更改符號鏈接本身的所有者而非指向的文件
- 可通過_POSIX_CHOWN_RESTRICTED常量、pathconf()或fpathconf()常量查詢是否支持任意用戶更改文件的所有者,生效時:
- 超級用戶可以改變文件的用戶ID
- 進程擁有此文件,owner=-1或文件用戶ID,且group等於進程的有效組ID或附屬ID,那麼非root用戶可以更改文件的組ID
11、文件長度
- stat結構的st_size表示字節爲單位的文件長度(對普通文件、目錄文件和符號鏈接有意義)
- 對目錄,長度通常是一個數(16/512)的整數倍
- 對於符號鏈接,長度是文件名的實際字節數(不包含null)
- st_blksize和st_blocks分別表示對文件IO較適合的塊長度和分配的實際512字節塊數
11.1 文件空洞
- 有空洞的文件使用ls -l與du命令查看可以得到不同的結果
- cat命令複製後會填滿所有空洞
12、文件截斷
int truncate(const char *path, off_t length);
int ftruncate(int fd, off_t length);
13、文件系統
本節討論UFS:以berkeley快速文件系統爲基礎的
- 我們可以把一個磁盤分成一個或多個區,每個分區可以包含一個文件系統。i節點是固定長度的記錄項
[外鏈圖片轉存失敗(img-ezwsY9xz-1567246141238)(./images/文件系統.jpg)] - 刪除一個目錄項被稱爲unlink而非delete?
- st_nlink(硬鏈接數):每個i節點有一個鏈接計數,減少到0時纔可刪除。
- 符號鏈接:文件內容(數據塊中)包含了該符號鏈接指向的文件的名字,i節點中存儲爲S_IFLNK
- i節點包含了文件的所有信息:類型權限長度數據塊指針等,除了目錄項中的文件名和i節點編號
- ln爲什麼不能跨文件系統
- 目錄項中的i節點編號指向同一文件系統中相應的i節點
- 重命名文件時,只需構造一個指向現有i節點的新目錄項並刪除老的目錄項
- 對於目錄,葉鏈接計數總是2,父目錄是3
14、link()、linkat()、unlink()、unlinkat()、remove()
int link(const char *oldpath, const char *newpath);
int linkat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath, int flags);
- 函數創建新的目錄項,通過引用現有文件(最好在一個文件系統下)
- 就算允許對目錄創建硬鏈接,也僅限於root用戶(因爲可能造成循環)
int unlink(const char *pathname);
int unlinkat(int dirfd, const char *pathname, int flags);
- 函數刪除目錄項,鏈接數減1
int remove(const char *pathname);
- 函數解除對一個文件或目錄的鏈接
15、rename()或renameat()
int rename(const char *oldpath, const char *newpath);
int renameat(int olddirfd, const char *oldpath,
int newdirfd, const char *newpath);
- 對文件或目錄重命名
16、符號鏈接
- 符號鏈接一般用於將一個文件或目錄結構移動到系統中的另一個位置
17、創建和讀取符號鏈接
int symlink(const char *oldpath, const char *newpath);
int symlinkat(const char *oldpath, int newdirfd, const char *newpath);
- 因爲open函數跟隨符號鏈接,所有有下面的函數:
ssize_t readlink(const char *path, char *buf, size_t bufsiz);
int readlinkat(int dirfd, const char *pathname,
char *buf, size_t bufsiz);
18、文件時間
- 對每個文件系統維持三個時間段
- st_atim:文件數據最後訪問時間 ls -u
- st_mtim:最後修改時間 ls
- st_ctim:i節點的最後更改時間 ls -c
19、futimens()、utimensat()、utimes()
#include <sys/stst.h>
int futimes(int fd, const struct timespec times[2]);
int utimesat(int fd, const char *path, const struct timespec times[2], int flag);
- times[0]爲訪問時間,times[1]爲修改時間,值是日曆時間
- 時間戳可以按下列4種方式之一進行綁定:
- times爲空時,訪問時間和修改時間都設置爲當前時間
- times指向兩個timespec結構數組,且任一元素爲UTIME_NOW,則相應的時間設置爲當前時間,忽略tv_sec字段
- times指向兩個timespec結構數組,且任一元素爲UTIME_OMIT,相應時間戳保持不變
- times指向兩個timespec結構數組,且不是UTIME_NOW|UTIME_OMIT,設置爲指定的時間戳
#include <sys/types.h>
#include <utime.h>
int utime(const char *filename, const struct utimbuf *times);
#include <sys/time.h>
int utimes(const char *filename, const struct timeval times[2]);
- 該函數對路徑名盡心操作
20、mkdir()、mkdirat()、rmdir()
int mkdir(const char *pathname, mode_t mode);
int mkdirat(int dirfd, const char *pathname, mode_t mode);
int rmdir(const char *pathname);
- 函數創建一個新的空目錄
- 注意至少要設置一個執行權限位,以允許訪問目錄中的文件名
21、讀目錄
- 有權限的任一用戶可以讀目錄,但只有內核能寫目錄
DIR *opendir(const char *name);
DIR *fdopendir(int fd);
int readdir(unsigned int fd, struct old_linux_dirent *dirp,
unsigned int count);
void rewinddir(DIR *dirp);
int closedir(DIR *dirp);
long telldir(DIR *dirp);
void seekdir(DIR *dirp, long offset);
22、chdir()、fchdir()、getcwd()
int chdir(const char *path);
int fchdir(int fd);
- 函數更改當前的工作目錄
#include <unistd.h>
char *getcwd(char *buf, size_t size);
char *getwd(char *buf);
char *get_current_dir_name(void);
- 函數獲取當前路徑名
23、設備特殊文件
- 每個文件系統所在的存儲設備都由其主、次設備號表示(dev_t)。
- 主設備號標識設備驅動程序,有時編碼爲與其通信的外設備板
- 次設備號標識特定的子設備
- (同一磁盤驅動器上的文件系統通常具有相同的主設備號,但次設備號卻不同)
- 通常可以使用兩個宏:major、minor來訪問主、次設備號
- 系統中與每個文件名關聯的st_dev值是文件系統的設備號
- 只有特殊文件和塊特殊文件纔有st_dev值(實際設備的設備號)