master
c 202 lines 6.64 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 #include "libnetdata/libnetdata.h"
4 #include "dir_size.h"
5
6 #include <dirent.h>
7 #include <sys/stat.h>
8 #include <unistd.h>
9 #include <string.h>
10 #include <errno.h>
11
12 // Hash table to keep track of visited inodes to avoid cycles
13 typedef struct {
14 ino_t inode; // Inode number
15 dev_t device; // Device ID
16 } INODE_DEVICE_PAIR;
17
18 // Internal function to recursively calculate directory size
19 static void calc_dir_size_recursive(const char *base_path, const char *rel_path,
20 SIMPLE_PATTERN *pattern, size_t max_depth, size_t current_depth,
21 DIR_SIZE *result, DICTIONARY *visited_inodes) {
22
23 char path[FILENAME_MAX + 1];
24 struct stat statbuf;
25 struct dirent *entry;
26 DIR *dir;
27
28 // Check max depth
29 if (max_depth > 0 && current_depth > max_depth)
30 return;
31
32 // Update max depth found
33 if (current_depth > result->depth)
34 result->depth = current_depth;
35
36 // Construct full path (avoid double slashes)
37 if (rel_path && *rel_path) {
38 if (base_path[strlen(base_path) - 1] == '/')
39 snprintfz(path, FILENAME_MAX, "%s%s", base_path, rel_path);
40 else
41 snprintfz(path, FILENAME_MAX, "%s/%s", base_path, rel_path);
42 } else {
43 snprintfz(path, FILENAME_MAX, "%s", base_path);
44 }
45
46 // Get file/directory stats
47 if (lstat(path, &statbuf) != 0) {
48 result->errors++;
49 return;
50 }
51
52 // Create inode-device pair to detect loops
53 INODE_DEVICE_PAIR id_pair = {
54 .inode = statbuf.st_ino,
55 .device = statbuf.st_dev
56 };
57
58 // Use string representation as the dictionary name
59 char name[sizeof(INODE_DEVICE_PAIR) * 2 + 1];
60 snprintfz(name, sizeof(name), "%lu_%lu", (unsigned long)id_pair.inode, (unsigned long)id_pair.device);
61
62 // Check if we've seen this inode-device pair before (for symlink loop detection)
63 if (dictionary_get(visited_inodes, name))
64 return;
65
66 // Add to visited inodes
67 dictionary_set(visited_inodes, name, NULL, sizeof(void *));
68
69 // Handle different file types
70 if (S_ISDIR(statbuf.st_mode)) {
71 result->directories++;
72
73 // Open directory
74 dir = opendir(path);
75 if (!dir) {
76 result->errors++;
77 return;
78 }
79
80 // Iterate through directory entries
81 while ((entry = readdir(dir)) != NULL) {
82 // Skip "." and ".."
83 if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0)
84 continue;
85
86 // Build relative path (this is the path relative to base_path)
87 char next_rel_path[FILENAME_MAX + 1];
88 if (rel_path[0] == '\0')
89 snprintfz(next_rel_path, FILENAME_MAX, "%s", entry->d_name);
90 else
91 snprintfz(next_rel_path, FILENAME_MAX, "%s/%s", rel_path, entry->d_name);
92
93 // Build full path to check file type (avoid double slashes)
94 char full_path[FILENAME_MAX + 1];
95 if (path[strlen(path) - 1] == '/')
96 snprintfz(full_path, FILENAME_MAX, "%s%s", path, entry->d_name);
97 else
98 snprintfz(full_path, FILENAME_MAX, "%s/%s", path, entry->d_name);
99
100 struct stat entry_stat;
101 if (lstat(full_path, &entry_stat) != 0) {
102 result->errors++;
103 continue;
104 }
105
106 if (S_ISDIR(entry_stat.st_mode)) {
107 // Always recurse on directories regardless of pattern
108 calc_dir_size_recursive(base_path, next_rel_path, pattern, max_depth,
109 current_depth + 1, result, visited_inodes);
110 }
111 else if (S_ISREG(entry_stat.st_mode)) {
112 // For files, apply pattern filtering if specified
113 if (pattern && !simple_pattern_matches(pattern, next_rel_path))
114 continue;
115
116 // Count the file
117 result->files++;
118 result->bytes += entry_stat.st_size;
119 }
120 // Other file types (symlinks, etc.) are not counted
121 }
122
123 closedir(dir);
124 }
125 else if (S_ISREG(statbuf.st_mode)) {
126 // For individual files (when dir_size is called directly on a file)
127 // Apply pattern filtering if specified
128 if (pattern && !simple_pattern_matches(pattern, rel_path))
129 return;
130
131 // Count the file
132 result->files++;
133 result->bytes += statbuf.st_size;
134 }
135 // Other file types (symlinks, etc.) are not counted in size calculation
136 }
137
138 DIR_SIZE dir_size(const char *path, SIMPLE_PATTERN *pattern, size_t max_depth) {
139 DIR_SIZE result = DIR_SIZE_EMPTY;
140
141 if (!path || !*path)
142 return result;
143
144 // Create dictionary to track visited inodes
145 DICTIONARY *visited_inodes = dictionary_create(DICT_OPTION_SINGLE_THREADED);
146
147 // Check if path exists and get initial stats
148 struct stat statbuf;
149
150 if (stat(path, &statbuf) != 0) {
151 result.errors++;
152 dictionary_destroy(visited_inodes);
153 return result;
154 }
155
156 if (S_ISDIR(statbuf.st_mode)) {
157 // Start recursion from the base path for directories
158 calc_dir_size_recursive(path, "", pattern, max_depth, 0, &result, visited_inodes);
159 } else if (S_ISREG(statbuf.st_mode)) {
160 // Single file case
161 // Extract the filename for pattern matching
162 const char *filename = strrchr(path, '/');
163 filename = filename ? filename + 1 : path;
164
165 // Apply pattern filtering if specified
166 if (pattern && !simple_pattern_matches(pattern, filename))
167 return result;
168
169 result.files = 1;
170 result.bytes = statbuf.st_size;
171 }
172
173 dictionary_destroy(visited_inodes);
174 return result;
175 }
176
177 DIR_SIZE dir_size_multiple(const char **paths, int num_paths, SIMPLE_PATTERN *pattern, size_t max_depth) {
178 DIR_SIZE result = DIR_SIZE_EMPTY;
179
180 if (!paths || num_paths <= 0)
181 return result;
182
183 // Calculate size for each path and combine results
184 for (int i = 0; i < num_paths; i++) {
185 if (!paths[i] || !*paths[i])
186 continue;
187
188 DIR_SIZE path_result = dir_size(paths[i], pattern, max_depth);
189
190 // Combine results
191 result.bytes += path_result.bytes;
192 result.files += path_result.files;
193 result.directories += path_result.directories;
194 result.errors += path_result.errors;
195
196 // Take the maximum depth found
197 if (path_result.depth > result.depth)
198 result.depth = path_result.depth;
199 }
200
201 return result;
202 }