目的,計算一個目錄下所有文件佔用的磁盤空間,但是不計算鏈接文件(硬鏈接和軟鏈接)。
硬連接
硬鏈接是通過索引節點進行的鏈接。在Linux中,多個文件指向同一個索引節點是允許的,像這樣的鏈接就是硬鏈接。硬鏈接只能在同一文件系統中的文件之間進行鏈接,不能對目錄進行創建。如果刪除硬鏈接對應的源文件,則硬鏈接文件仍然存在,而且保存了原有的內容,這樣可以起到防止因爲誤操作而錯誤刪除文件的作用。由於硬鏈接是有着相同 inode 號僅文件名不同的文件,因此,刪除一個硬鏈接文件並不影響其他有相同 inode 號的文件。
- 通過
ln rumenz.txt rumenz123.txt
創建 - 不能對目錄進行創建硬鏈接,只可對文件創建。
- 以文件副本的形式存在,但不佔用實際空間。
- 文件名有相同的 inode 及 data block。
- 只有在同一個文件系統中才能創建,不能交叉文件系統進行硬鏈接的創建。
- 刪除其中一個硬鏈接文件並不影響其他有相同inode號的文件。
- 只能對已存在的文件進行創建。
適用場景
用於鏡像數據文件,防止誤刪
軟連接
軟鏈接(也叫符號鏈接)與硬鏈接不同,文件用戶數據塊中存放的內容是另一文件的路徑名的指向。軟鏈接就是一個普通文件,只是數據塊內容有點特殊。軟鏈接可對文件或目錄創建。
軟鏈接主要應用於以下兩個方面:一是方便管理,例如可以把一個複雜路徑下的文件鏈接到一個簡單路徑下方便用戶訪問;另一方面就是解決文件系統磁盤空間不足的情況。例如某個文件文件系統空間已經用完了,但是現在必須在該文件系統下創建一個新的目錄並存儲大量的文件,那麼可以把另一個剩餘空間較多的文件系統中的目錄鏈接到該文件系統中,這樣就可以很好的解決空間不足問題。刪除軟鏈接並不影響被指向的文件,但若被指向的原文件被刪除,則相關軟連接就變成了死鏈接。
ln -s rumenz.txt rumenz123.txt
- 是存放另一個文件的路徑的形式存在。
- 可交叉文件系統創建 ,硬鏈接不可以。
- 可以對目錄進行鏈接。
- 有自己的文件屬性及權限等。
- 可對不存在的文件或目錄創建軟鏈接。
- 軟鏈接可對文件或目錄創建。
- 創建軟鏈接時,鏈接計數 i_nlink 不會增加。
- 刪除軟鏈接並不影響被指向的文件,但若被指向的原文件被刪除,則相關軟連接被稱爲死鏈接(即 dangling link,若被指向路徑文件被重新創建,死鏈接可恢復爲正常的軟鏈接)。
適用場景
- 便於文件管理,將複雜路徑下的文件鏈接到簡單路徑下訪問。
- 解決某個目錄空間不足問題。
如何知道一個文件是不是一個鏈接文件呢?首先可以獲取一個文件的stat,其定義如下:
struct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
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; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */
struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */
#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
要想獲取stat,我們可以使用stat或lstat函數得到,注意這裏需要使用lstat,表示獲取鏈接文件本身的狀態信息,而不是獲取其指向的文件stat,具體區別如下:
一旦獲取了stat ,就可以通過S_ISLNK宏定義進行判斷,注意S_ISLNK表示一個文件是否是軟(符號)鏈接。
#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)
上面已經解決了如何識別一個軟鏈接,那麼硬鏈接怎麼識別呢?仔細看struct stat中的st_nlink字段,它表示當前這個文件有幾個硬鏈接。如果st_nlink大於1,說明目前有多個文件都硬鏈接到了同一個inode上。那如何做過濾呢?我們不能簡單的以”st_nlink > 1“ 爲條件,因爲這樣會把源文件也過濾掉。幸好,du命令裏有一個選項-l,表示是否重複計算hardlink,我們可以先看他怎麼實現的,具體在coreutils裏的實現是這樣。
可以看到,du是使用st_ino和st_dev兩個信息作爲去重條件,也就是說,只要是指向同一個inode的硬鏈接,他們的st_ino和st_dev一定是相同的,因此我們的實現如下:
off_t GetDirAllFilesSize(const std::string &dname, bool count_link) {
std::set<std::string> fileset;
std::function<off_t(const std::string &, bool)> _getDirSize = [&](const std::string &dname, bool count_link) -> off_t {
struct stat statbuf;
off_t total_size;
if (::lstat(dname.c_str(), &statbuf) != 0) {
return 0;
}
std::string file_info = std::string((char *)&statbuf.st_ino, sizeof(statbuf.st_ino)) +
std::string((char *)&statbuf.st_dev, sizeof(statbuf.st_dev));;
if (fileset.find(file_info) != fileset.end() && !count_link) {
return 0;
} else {
fileset.insert(file_info);
}
if (S_ISLNK(statbuf.st_mode) && (::stat(dname.c_str(), &statbuf) != 0 || !count_link)) {
return 0;
}
total_size = statbuf.st_size;
if (S_ISDIR(statbuf.st_mode)) {
DIR *dir;
struct dirent *entry;
if (!(dir = ::opendir(dname.c_str()))) {
return total_size;
}
while ((entry = ::readdir(dir))) {
if (!::strcmp(entry->d_name, ".") || !::strcmp(entry->d_name, "..")) {
continue;
}
std::string sub_filename = dname + "/" + entry->d_name;
total_size += _getDirSize(sub_filename, count_link);
}
::closedir(dir);
}
return total_size;
};
return _getDirSize(dname, count_link);
}