Skip to content

Commit 6fd06e3

Browse files
committed
du: ignore space used by already counted clones
Similar to hardlinks, space used by cloned files is not included by default. This behavior can be changed by using the `-C` option. Like hardlinks, this only affects clones in the same tree(s) being evaluated.
1 parent 7bdf2a0 commit 6fd06e3

File tree

3 files changed

+164
-4
lines changed

3 files changed

+164
-4
lines changed

du/du.1

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
.Nd display disk usage statistics
3737
.Sh SYNOPSIS
3838
.Nm
39-
.Op Fl Aclnx
39+
.Op Fl ACclnx
4040
.Op Fl H | L | P
4141
.Op Fl g | h | k | m
4242
.Op Fl a | s | d Ar depth
@@ -77,6 +77,15 @@ Unless in
7777
mode,
7878
.Ar blocksize
7979
is rounded up to the next multiple of 512.
80+
.It Fl C
81+
If a file uses data that has been cloned, count the clone's size multiple
82+
times. The default behavior of
83+
.Nm
84+
is to count clones once.
85+
When the
86+
.Fl C
87+
option is specified, the clone checks are disabled, and this data is
88+
counted (and displayed) as many times as they are found.
8089
.It Fl H
8190
Symbolic links on the command line are followed, symbolic links in file
8291
hierarchies are not followed.

du/du.c

Lines changed: 120 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,9 @@ struct ignentry {
125125

126126
static int linkchk(FTSENT *);
127127
static int dirlinkchk(FTSENT *);
128+
#ifdef __APPLE__
129+
static int clonechk(FTSENT *);
130+
#endif
128131
static void usage(void);
129132
static void prthumanval(int64_t);
130133
static void ignoreadd(const char *);
@@ -152,14 +155,14 @@ main(int argc, char *argv[])
152155
uint64_t threshold, threshold_sign;
153156
int ftsoptions;
154157
int depth;
155-
int Hflag, Lflag, aflag, sflag, dflag, cflag;
158+
int Cflag, Hflag, Lflag, aflag, sflag, dflag, cflag;
156159
int lflag, ch, notused, rval;
157160
char **save;
158161
static char dot[] = ".";
159162

160163
setlocale(LC_ALL, "");
161164

162-
Hflag = Lflag = aflag = sflag = dflag = cflag = lflag = hflag = Aflag = 0;
165+
Cflag = Hflag = Lflag = aflag = sflag = dflag = cflag = lflag = hflag = Aflag = 0;
163166

164167
save = argv;
165168
ftsoptions = FTS_PHYSICAL;
@@ -175,12 +178,15 @@ main(int argc, char *argv[])
175178
depth = INT_MAX;
176179
SLIST_INIT(&ignores);
177180

178-
while ((ch = getopt_long(argc, argv, "+AB:HI:LPasd:cghklmnrt:x",
181+
while ((ch = getopt_long(argc, argv, "+ACB:HI:LPasd:cghklmnrt:x",
179182
long_options, NULL)) != -1)
180183
switch (ch) {
181184
case 'A':
182185
Aflag = 1;
183186
break;
187+
case 'C':
188+
Cflag = 1;
189+
break;
184190
case 'B':
185191
errno = 0;
186192
cblocksize = atoi(optarg);
@@ -390,6 +396,11 @@ main(int argc, char *argv[])
390396
linkchk(p))
391397
break;
392398

399+
#ifdef __APPLE__
400+
if (Cflag == 0 && clonechk(p))
401+
break;
402+
#endif
403+
393404
curblocks = Aflag ?
394405
howmany(p->fts_statp->st_size, cblocksize) :
395406
howmany(p->fts_statp->st_blocks, cblocksize);
@@ -706,6 +717,112 @@ dirlinkchk(FTSENT *p)
706717
return (0);
707718
}
708719

720+
#if __APPLE__
721+
static int
722+
clonechk(FTSENT *p)
723+
{
724+
struct links_entry {
725+
struct links_entry *next;
726+
struct links_entry *previous;
727+
uint64_t cloneid;
728+
dev_t dev;
729+
};
730+
static const size_t links_hash_initial_size = 8192;
731+
static struct links_entry **buckets;
732+
static size_t number_buckets;
733+
static unsigned long number_entries;
734+
static char stop_allocating;
735+
struct links_entry *le, **new_buckets;
736+
struct stat *st;
737+
uint64_t cloneid;
738+
size_t i, new_size;
739+
int hash;
740+
struct attrbuf {
741+
int size;
742+
uint64_t value;
743+
} __attribute((aligned(4), packed)) buf;
744+
struct attrlist attrList;
745+
746+
memset(&attrList, 0, sizeof(attrList));
747+
attrList.bitmapcount = ATTR_BIT_MAP_COUNT;
748+
attrList.forkattr = ATTR_CMNEXT_CLONEID;
749+
if (-1 == getattrlist(p->fts_path, &attrList, &buf, sizeof(buf), FSOPT_ATTR_CMN_EXTENDED))
750+
return 0;
751+
cloneid = buf.value;
752+
st = p->fts_statp;
753+
754+
/* If necessary, initialize the hash table. */
755+
if (buckets == NULL) {
756+
number_buckets = links_hash_initial_size;
757+
buckets = malloc(number_buckets * sizeof(buckets[0]));
758+
if (buckets == NULL)
759+
errx(1, "No memory for clone detection");
760+
for (i = 0; i < number_buckets; i++)
761+
buckets[i] = NULL;
762+
}
763+
764+
/* If the hash table is getting too full, enlarge it. */
765+
if (number_entries > number_buckets * 10 && !stop_allocating) {
766+
new_size = number_buckets * 2;
767+
new_buckets = calloc(new_size, sizeof(struct links_entry *));
768+
769+
if (new_buckets == NULL) {
770+
stop_allocating = 1;
771+
warnx("No more memory for tracking clones");
772+
} else {
773+
for (i = 0; i < number_buckets; i++) {
774+
while (buckets[i] != NULL) {
775+
/* Remove entry from old bucket. */
776+
le = buckets[i];
777+
buckets[i] = le->next;
778+
779+
/* Add entry to new bucket. */
780+
hash = (le->dev ^ le->cloneid) % new_size;
781+
782+
if (new_buckets[hash] != NULL)
783+
new_buckets[hash]->previous =
784+
le;
785+
le->next = new_buckets[hash];
786+
le->previous = NULL;
787+
new_buckets[hash] = le;
788+
}
789+
}
790+
free(buckets);
791+
buckets = new_buckets;
792+
number_buckets = new_size;
793+
}
794+
}
795+
796+
/* Try to locate this entry in the hash table. */
797+
hash = ( st->st_dev ^ cloneid ) % number_buckets;
798+
for (le = buckets[hash]; le != NULL; le = le->next) {
799+
if (le->dev == st->st_dev && le->cloneid == cloneid) {
800+
return (1);
801+
}
802+
}
803+
804+
if (stop_allocating)
805+
return (0);
806+
807+
/* Add this entry to the clone ids cache. */
808+
le = malloc(sizeof(struct links_entry));
809+
if (le == NULL) {
810+
stop_allocating = 1;
811+
warnx("No more memory for tracking clones");
812+
return (0);
813+
}
814+
le->dev = st->st_dev;
815+
le->cloneid = cloneid;
816+
number_entries++;
817+
le->next = buckets[hash];
818+
le->previous = NULL;
819+
if (buckets[hash] != NULL)
820+
buckets[hash]->previous = le;
821+
buckets[hash] = le;
822+
return (0);
823+
}
824+
#endif
825+
709826
static void
710827
prthumanval(int64_t bytes)
711828
{

du/tests/du_test.sh

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,22 @@ require_sparse_file_support()
3535
fi
3636
}
3737

38+
#ifdef __APPLE__
39+
require_clonefile_support()
40+
{
41+
local mountpoint="$(df -P "$(pwd)" | awk 'NR==2 {print $NF}')"
42+
local personality="$(diskutil info "$mountpoint" | \
43+
grep "File System Personality:" | \
44+
awk '{print $NF}')"
45+
46+
if [ "$personality" != "APFS" ] ; then
47+
mount
48+
atf_skip "Test's work directory does not support clonefile(2);" \
49+
"try with a different TMPDIR?"
50+
fi
51+
}
52+
#endif
53+
3854
atf_test_case A_flag
3955
A_flag_head()
4056
{
@@ -53,6 +69,23 @@ A_flag_body()
5369
atf_check -o inline:'10\tsparse.file\n' du -A -g sparse.file
5470
}
5571

72+
#ifdef __APPLE__
73+
atf_test_case C_flag
74+
C_flag_head()
75+
{
76+
atf_set "descr" "Verify -C behavior"
77+
}
78+
C_flag_body()
79+
{
80+
require_clonefile_support
81+
atf_check truncate -s 1500000 A
82+
atf_check cp -c A B
83+
84+
atf_check -o inline:'1.4M\tA\n1.4M\tB\n' du -C A B
85+
atf_check -o inline:'1.4M\tA\n' du A B
86+
}
87+
#endif
88+
5689
atf_test_case H_flag
5790
H_flag_head()
5891
{
@@ -184,6 +217,7 @@ si_flag_body()
184217
atf_init_test_cases()
185218
{
186219
atf_add_test_case A_flag
220+
atf_add_test_case C_flag
187221
atf_add_test_case H_flag
188222
atf_add_test_case I_flag
189223
atf_add_test_case g_flag

0 commit comments

Comments
 (0)