本篇主要介紹文件和文件系統中常用的一些函數,文件系統的組織結構和硬鏈接、符號鏈接。
通過對這些知識的了解,可以對linux文件系統有更為全面的了解。
?
1 umask函數之前我們已經了解了每個文件與權限相關的9個位(bit),我們現在來了解一下當每個進程創建文件時默認會設置該文件的文件權限(the file mode creation mask)。
umask函數設置該進程默認創建文件的權限掩碼(the file mode creation mask),并且返回之前的權限掩碼值。
#include <sys/stat.h>
mode_t umask(mode_t cmask);
the file mode creation mask的作用:當進程創建新文件時,會根據這個掩碼值創建文件,在掩碼值中打開的位,對應的新文件的權限位會被關閉。
umask的功能簡單地說就是創建新文件時屏蔽掉用戶不希望生效的權限位。
Example:
#include "apue.h"
#include <fcntl.h>
?
#define RWRWRW (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH)
?
int
main(void)
{
? ? 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);
}
運行截圖:
結果說明:
shell的umask命令顯示當前文件創建權限掩碼。
0022表示創建出來的新文件,組用戶和other用戶沒有寫該文件的權限。
在程序中,首先用默認的權限設置創建了文件foo,它的權限位666,至于為什么當前用戶也沒有執行權限,我還沒搞清楚,留一個坑 @suzhou。
然后修改umask值為0077,這樣就屏蔽掉了組用戶和其他用戶的讀寫權限位,因此再創建新文件bar,它的權限位是600。
?
2 chmod、fchmod和fchmodat函數函數作用:修改已有文件的權限位。
函數聲明:
#include <sys/stat.h>
int chmod(const char *pathname, mode_t mode);
int fchmod(int fd, mode_t mode);
int fchmodeat(int fd, const char *pathname, mode_t mode, int flag);
?區別:
權限要求:要修改一個文件的權限,需要當前進程的effective user ID和文件的所有者ID相同,或者進程有超級用戶權限。
代表各個權限位的常量如下表所示:
Example:
修改在上例中創建的兩個文件foo和bar的權限。
#include "apue.h"
?
int
main(void)
{
? ? struct stat ? ? statbuf;
?
? ? /* turn on set-group-ID and turn off group-execute */
?
? ? 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");
?
? ? /* set absolute mode to "rw-r--r--" */
?
? ? if (chmod("bar", S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) < 0)
? ? ? ? err_sys("chmod error for bar");
?
? ? exit(0);
}
運行結果:
結果說明:
高級權限(SUID/SGID/Sticky Bit )檔案特殊權限 說明:
?
3 Sticky Bit(S_ISVTX)Sticky bit有一個冗長的歷史,這里并不贅述。
作用:如果文件夾的sticky bit被打開,則文件夾中的文件被刪除或者重命名需要滿足一下的條件之一:當前用戶對該文件夾有寫權限;當前用戶是該文件的所有者;當前用戶是該文件夾的所有者;當前用戶是超級用戶。
典型的打開了sticky bit的兩個文件夾是/tmp 和/var/tmp,這兩個文件夾對所有用戶都有讀寫和執行權限,這樣所有用戶都可以在該文件夾下創建文件,但是并不能刪除由其他用戶所有的文件。
?
4 chown、fchown、fchownat和lchown函數函數作用:修改文件所屬的用戶ID和組ID,如果參數owner和group有一個的值為-1,對應的ID值不變。
#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 grou, int flag);
int lchown(const char* pathname, uid_t owner, gid_t group);
區別:
當目標文件不是一個符號鏈接時,上面的四個函數的作用基本相同。
當目標文件是一個符號鏈接文件時,并且函數lchown和fchownat的flag設置為AT_SYMLINK_NOFOLLOW時,這兩個函數修改符號鏈接文件本身的所有者,而不是符號鏈接指向的文件。
fchown函數修改fd代表的已打開文件的所有者。因為fchown操作的是已經打開的文件,所以它不可以用來修改符號鏈接的所有者。
fchownat函數的行為:當參數pathname是絕對路徑,或者參數fd的值為AT_FDCWD并且pathname為相對路徑時,fchownat函數的行為和chown和lchown類似;具體來說,flag取值AT_SYMLINK_NOFOLLOW時,行為和lchown類似;否則行為和chown類似。當fd代表已打開的文件夾,pathname為一個相對路徑時,目標文件為以fd為父目錄,pathname為相對的子目錄所指的文件。
?
5 文件大小(File Size)stat結構體中的成員st_size表示文件大小字節數。
字段st_size只對常規文件(regular files)、目錄文件(directories)和符號鏈接文件(symbolic links)。
更多的細節:
?
6 文件截斷(File Truncation)有時候我們需要通過丟棄文件結尾一部分來截短文件。
函數聲明:
#include <unistd.h>
int truncate(const char* pathname, off_t length);
itn ftruncate(int fd, off_t length);
函數作用:把目標文件截斷到參數length指定的長度。
參數:如果參數length的值比指定文件的長度小,則文件超過length長度的部分不可讀;如果length的值比指定文件的長度大,文件長度擴大到length,擴充部分填充0(可能是一個hole)
?
7 文件系統(File Systems)為了理解文件鏈接,我們需要對Unix文件系統的結構有一個概念性地認識。
一個磁盤(disk drive)可以被分為多個分區(partition),每個分區都可以包含一個文件系統(file system)。
inode是一個定長的索引,包含了一個文件的大部分信息。
關于 i-nodes和data blocks的內存布局如下圖所示:
說明:
我們再了解一下目錄文件的link count字段。
當我們執行下面的命令:
mkdir testdir
?內存中數據組織結構如下所示:
從上圖可以看到,目錄下每多加一個文件或文件夾,link count都會加1。
圖中顯示的都很清楚,不做過多贅述(其實是我懶了,哈哈...)
?
8 link、linkat、unlink、unlinkat和remove函數從前面可以知道,多個目錄索引可以指向同一個文件,即多個目錄可以包含同一個文件。
link和linkat的作用是創建一個已存在文件的鏈接。
函數聲明:
#include <unistd.h>
int link(const char* existingpath, const char* newpath);
int linkat(int efd, const char* existingpath, int nfd, const char* newpath, int flag);
函數細節:
函數聲明:
#include <unistd.h>
int unlink(const char* pathname);
int unlinkat(int fd, const char* pathname, int flag);
函數細節:
和unlink類似功能的函數remove:
#include <stdio.h>
int remove(const char* pathname);
函數細節:
?
9 rename和renameat函數函數聲明:
#include <stdio.h>
int rename(const char* oldname, const char* newname);
int renameat(int oldfd, const char* oldname, int newfd, const char* newname);
函數細節:
對renameat函數的參數含義不再贅述。
?
10 符號鏈接(Symbolic Links)符號鏈接可以認為是文件的非直接指針,而硬鏈接(hard link)可以看做是文件的直接指針,因為它直接指向文件的inode。
符號鏈接并沒有文件系統間的限制。
由于符號鏈接的存在,我們在操作文件或目錄時,總是需要知道該文件是否是一個符號鏈接,然后判斷該函數的操作對象是符號鏈接指向的文件還是符號鏈接本身。
下圖總結了一些常用函數是否追蹤鏈接,僅供參考:
Example:
鏈接情況如下圖所示:
可以發現,由于符號鏈接而出現了一個環。
再來試驗一下:
我們發現,這樣會無限循環下去。
解除循環的方法就是使用unlink刪除該符號鏈接。需要注意的一點是,如果這里出現的鏈接是硬鏈接,則刪除這個循環鏈接會困難得多。因此只有管理員權限才可以用link函數對一個文件建立硬鏈接。
有時候符號鏈接會造成一些迷惑的事情,如果使用者對符號鏈接不熟悉的話。
如下所示:
符號鏈接可以鏈接一個不存在的文件,如果使用者對符號鏈接不熟悉,則很容易被這種情況迷惑。
這時,可以使用ls的”-l"命令看到有 “->"符號,或者前面符號位第一位的”l”表示符號鏈接,或者用ls得“-F”命令,符號鏈接文件后面會跟一個@符。
?
11 創建和讀取符號鏈接(Symbolic Links)函數作用:創建符號鏈接
函數聲明:
#include <unistd.h>
int symlink(const char *actualpath, const char* sympath);
int symlinkat(const char* actualpath, int fd, const char* sympath);
參數說明:
因為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);
如果調用成功,函數從符號鏈接中讀取buf長的內容。
?
?
參考資料:
《Advanced PRogramming in the UNIX Envinronment 3rd》
?
新聞熱點
疑難解答