-
-
Notifications
You must be signed in to change notification settings - Fork 39
/
xdu.c
297 lines (252 loc) · 7.92 KB
/
xdu.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
/* xdu.c -- Trimmed down implementation of du(1) */
/*
* This file is part of CliFM
*
* Copyright (C) 2016-2024, L. Abramovich <leo.clifm@outlook.com>
* All rights reserved.
* CliFM is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* CliFM is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
* MA 02110-1301, USA.
*/
#include "helpers.h"
#include <errno.h>
#ifdef USE_DU1
# include <string.h> /* strchr */
# include <unistd.h> /* close, dup, dup2, unlink, unlinkat */
#endif /* USE_DU1 */
#ifdef USE_DU1
# include "aux.h" /* xnrealloc, open_fread */
# include "spawn.h" /* launch_execv */
#else
# include "mem.h" /* xnrealloc */
#endif /* USE_DU1 */
/* According to 'info du', the st_size member of a stat struct is meaningful
* only:
* 1. When computing disk usage (not apparent sizes).
* 2. If apparent sizes, only for symlinks and regular files.
* NOTE: Here we add shared memory object and typed memory object just to
* match the check made by du(1). These objects are not implemented on most
* systems, but this might change in the future. */
#define USABLE_ST_SIZE(s) (conf.apparent_size != 1 || S_ISLNK((s)->st_mode) \
|| S_ISREG((s)->st_mode) || S_TYPEISSHM((s)) || S_TYPEISTMO((s)))
struct hlink_t {
dev_t dev;
ino_t ino;
};
static struct hlink_t *xdu_hardlinks = {0};
static size_t xdu_hardlink_n = 0;
static inline int
check_xdu_hardlinks(const dev_t dev, const ino_t ino)
{
if (!xdu_hardlinks || xdu_hardlink_n == 0)
return 0;
size_t i;
for (i = 0; i < xdu_hardlink_n; i++) {
if (dev == xdu_hardlinks[i].dev && ino == xdu_hardlinks[i].ino)
return 1;
}
return 0;
}
static inline void
add_xdu_hardlink(const dev_t dev, const ino_t ino)
{
xdu_hardlinks = xnrealloc(xdu_hardlinks, xdu_hardlink_n + 1,
sizeof(struct hlink_t));
xdu_hardlinks[xdu_hardlink_n].dev = dev;
xdu_hardlinks[xdu_hardlink_n].ino = ino;
xdu_hardlink_n++;
}
static inline void
free_xdu_hardlinks(void)
{
free(xdu_hardlinks);
xdu_hardlinks = (struct hlink_t *)NULL;
xdu_hardlink_n = 0;
}
/* Trimmed down implementation of du(1) providing only those features
* required by Clifm.
*
* Recursively count files and directories in the directory DIR and store
* values in the INFO struct.
* The total size in bytes (apparent size, if conf.apparent_size is set to 1,
* or disk usage otherwise) is stored in the SIZE field of the struct.
* The amount of directories, symbolic links, and other file types is stored
* in the DIRS, LINKS, and FILES fields respectively.
* FIRST_LEVEL must be always 1 when calling this function (this value will
* be zero whenever the function calls itself recursively).
* If a directory cannot be read, or a file cannot be stat'ed, then the
* STATUS field of the INFO struct is set to the appropriate errno value.*/
void
dir_info(const char *dir, const int first_level, struct dir_info_t *info)
{
if (!dir || !*dir) {
info->status = ENOENT;
return;
}
struct stat a;
DIR *p;
if ((p = opendir(dir)) == NULL) {
info->status = errno;
return;
}
#ifdef POSIX_FADV_SEQUENTIAL
/* A hint to the kernel to optimize the current dir for reading. */
const int fd = dirfd(p);
posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL);
#endif /* POSIX_FADV_SEQUENTIAL */
/* Compute the size of the base directory itself. */
if (first_level == 1 && conf.apparent_size != 1 && stat(dir, &a) != -1)
info->size += (a.st_blocks * S_BLKSIZE);
struct dirent *ent;
char buf[PATH_MAX + 1];
while ((ent = readdir(p)) != NULL) {
if (SELFORPARENT(ent->d_name))
continue;
snprintf(buf, sizeof(buf), "%s/%s", dir, ent->d_name);
if (lstat(buf, &a) == -1) {
info->status = errno;
#ifdef _DIRENT_HAVE_D_TYPE
/* We cannot extract the file type from st_mode. Let's fallback
* to whatever d_type says. */
switch (ent->d_type) {
case DT_LNK: info->links++; break;
case DT_DIR: info->dirs++; break;
default: info->files++; break;
}
#else
info->files++;
#endif /* _DIRENT_HAVE_D_TYPE */
continue;
}
if (S_ISLNK(a.st_mode)) {
info->links++;
#ifdef __CYGWIN__
/* This is because on Cygwin systems some regular files, maybe due to
* some permissions issue, are otherwise taken as directories. */
} else if (S_ISREG(a.st_mode)) {
info->files++;
#endif /* __CYGWIN__ */
} else if (S_ISDIR(a.st_mode)) {
/* In case we want to print the currently scanned directory:
* Note: 12 is the size of "Scanning... "
printf("\r\x1b[%zuC\x1b[0K%s%.*s%s", (size_t)12,
di_c, term_cols - 12 - 2, buf, df_c); */
/* Even if a subdirectory is unreadable or we can't chdir into
* it, do let its size contribute to the total (provided we're
* not computing apparent sizes). */
if (conf.apparent_size != 1)
info->size += (a.st_blocks * S_BLKSIZE);
info->dirs++;
dir_info(buf, 0, info);
continue;
} else {
info->files++;
}
if (!USABLE_ST_SIZE(&a))
continue;
if (a.st_nlink > 1) {
if (check_xdu_hardlinks(a.st_dev, a.st_ino) == 1)
continue;
else
add_xdu_hardlink(a.st_dev, a.st_ino);
}
info->size += conf.apparent_size == 1 ? a.st_size
: (a.st_blocks * S_BLKSIZE);
}
closedir(p);
if (first_level == 1)
free_xdu_hardlinks();
}
#ifndef USE_DU1
off_t
dir_size(const char *dir, const int first_level, int *status)
{
struct dir_info_t info = {0};
dir_info(dir, first_level, &info);
*status = info.status;
return info.size;
}
#else /* USE_DU1 */
/* Return the full size of the directory DIR using du(1).
* The size is reported in bytes if SIZE_IN_BYTES is set to 1.
* Otherwise, human format is used.
* STATUS is updated to the command exit code. */
off_t
dir_size(char *dir, const int size_in_bytes, int *status)
{
if (!dir || !*dir)
return (-1);
char file[PATH_MAX + 1];
snprintf(file, sizeof(file), "%s/%s", xargs.stealth_mode == 1
? P_tmpdir : tmp_dir, TMP_FILENAME);
int fd = mkstemp(file);
if (fd == -1)
return (-1);
int stdout_bk = dup(STDOUT_FILENO); /* Save original stdout */
if (stdout_bk == -1) {
unlinkat(fd, file, 0);
close(fd);
return (-1);
}
/* Redirect stdout to the desired file */
dup2(fd, STDOUT_FILENO);
close(fd);
if (bin_flags & (GNU_DU_BIN_DU | GNU_DU_BIN_GDU)) {
char *block_size = (char *)NULL;
if (size_in_bytes == 1) {
block_size = "--block-size=1";
} else {
if (xargs.si == 1)
block_size = "--block-size=KB";
else
block_size = "--block-size=K";
}
char *bin = (bin_flags & GNU_DU_BIN_DU) ? "du" : "gdu";
if (conf.apparent_size != 1) {
char *cmd[] = {bin, "-s", block_size, "--", dir, NULL};
*status = launch_execv(cmd, FOREGROUND, E_NOSTDERR);
} else {
char *cmd[] = {bin, "-s", "--apparent-size", block_size,
"--", dir, NULL};
*status = launch_execv(cmd, FOREGROUND, E_NOSTDERR);
}
} else {
char *cmd[] = {"du", "-ks", "--", dir, NULL};
*status = launch_execv(cmd, FOREGROUND, E_NOSTDERR);
}
dup2(stdout_bk, STDOUT_FILENO); /* Restore original stdout */
close(stdout_bk);
FILE *fp = open_fread(file, &fd);
if (!fp) {
unlink(file);
return (-1);
}
off_t retval = -1;
/* We only need here the first field of the line, which is a file
* size and usually takes only a few digits: since a yottabyte takes 26
* digits, MAX_INT_STR (32) is more than enough. */
char line[MAX_INT_STR]; *line = '\0';
if (fgets(line, (int)sizeof(line), fp) == NULL)
goto END;
char *p = strchr(line, '\t');
if (p && p != line) {
*p = '\0';
retval = (off_t)atoll(line);
}
END:
unlinkat(fd, file, 0);
fclose(fp);
return retval;
}
#endif /* !USE_DU1 */