目的,计算一个目录下所有文件占用的磁盘空间,但是不计算链接文件(硬链接和软链接)。
硬连接
硬链接是通过索引节点进行的链接。在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);
}