]> git.saurik.com Git - apple/libc.git/blame - libdarwin/dirstat.c
Libc-1439.100.3.tar.gz
[apple/libc.git] / libdarwin / dirstat.c
CommitLineData
b061a43b 1/*
70ad1dc8 2 * Copyright (c) 2017 Apple Inc. All rights reserved.
b061a43b
A
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
11 * file.
12 *
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24#include <strings.h>
25#include <stddef.h>
26#include <stdlib.h>
27#include <stdio.h>
28#include <fcntl.h>
29#include <unistd.h>
30#include <sys/fsctl.h>
31#include <sys/vnode.h>
32#include <sys/errno.h>
33
34#include <os/assumes.h>
35
36#include <TargetConditionals.h>
37
38#include "dirstat.h"
39#include "dirstat_collection.h"
40
41#if !TARGET_OS_SIMULATOR
42#define HAS_APFS
43#endif
44
45#ifdef HAS_APFS
46#include <apfs/apfs_fsctl.h>
47#endif
48
49#if DEBUG
50#define DEBUGPRINT(...) fprintf(stderr, __VA_ARGS__)
51#else
52#define DEBUGPRINT(...) do { } while(0)
53#endif
54
55static int
56fdirstat_fallback(int fd, int flags, struct dirstat *ds);
57
58#ifdef HAS_APFS
59static int
60fdirstat(int fd, int flags, struct dirstat *ds)
61{
62 struct apfs_dir_stats_ext dstats = {0};
63 if (flags & DIRSTAT_FAST_ONLY) {
64 dstats.flags |= APFS_DIR_STATS_FAST_PATH;
65 }
66
67 int err = ffsctl(fd, APFSIOC_GET_DIR_STATS_EXT, &dstats, 0);
68 if (err == -1) {
69 if (errno == ENOENT) {
70 // <rdar://problem/31696225>
71 errno = ENOTSUP;
72 }
73 return -1;
74 }
75
76 ds->total_size = dstats.total_size;
77 ds->descendants = dstats.num_children;
78
79 return 0;
80}
81#endif
82
83int
84dirstatat_np(int dfd, const char *path, int flags, struct dirstat *ds_out, size_t ds_size)
85{
86#ifdef HAS_APFS
87 // <rdar://problem/32794924>
88 // Until APFS directory sizing is fixed, only the fallback path is
89 // available.
90 flags |= DIRSTAT_FORCE_FALLBACK;
91
92 // FORCE_FALLBACK trumps FAST_ONLY. Make sure to set errno accordingly in
93 // the case that a confused caller asks for both.
94 if ((flags & (DIRSTAT_FAST_ONLY)) && (flags & DIRSTAT_FORCE_FALLBACK)) {
95 errno = ENOTSUP;
96 return -1;
97 }
98#endif
99
100 int fd = openat(dfd, path, O_RDONLY | O_DIRECTORY);
101 DEBUGPRINT("Opened %d:%s as %d\n", dfd, path, fd);
102 if (fd == -1) return -1;
103
104 struct dirstat ds = {};
105 int ret = -1;
106
107#ifdef HAS_APFS
108 if (!(flags & DIRSTAT_FORCE_FALLBACK)) {
109 ret = fdirstat(fd, flags, &ds);
110 }
111
112 if (ret == -1 && ((flags & DIRSTAT_FORCE_FALLBACK) || ((errno == ENOTTY) && !(flags & DIRSTAT_FAST_ONLY)))) {
113 ret = fdirstat_fallback(fd, flags, &ds);
114 }
115#else
116 ret = fdirstat_fallback(fd, flags, &ds);
117#endif
118 int saved_errno = errno;
119
120 if (ds_size >= sizeof(ds)) {
121 memcpy(ds_out, &ds, sizeof(ds));
122 } else {
123 memcpy(ds_out, &ds, ds_size);
124 }
125
126 close(fd);
127
128 errno = saved_errno;
129 return ret;
130}
131
132int
133dirstat_np(const char *path, int flags, struct dirstat *ds, size_t ds_size)
134{
135 return dirstatat_np(AT_FDCWD, path, flags, ds, ds_size);
136}
137
138#pragma mark Fallback
139
140struct dirqueue_entry {
141 STAILQ_ENTRY(dirqueue_entry) entries;
142 char *path;
143};
144
145static int
146fdirstat_fallback(int parent_fd, int flags, struct dirstat *ds)
147{
148 int reterror = 0;
149
150 /*
151 * This method of gathering disk usage is the fastest by far over other
152 * methods using fts or opendir/readdir + getattrlist or stat to gather
153 * information about filesystem usage. That's because this method avoids
154 * creating vnodes for each item in a directory. We implement a recursive
155 * filesystem search by appending each directory child found to a
156 * processing queue, and then process each child directory in that queue on
157 * a FIFO basis resulting in a breadth-first traversal of the filesystem.
158 * This keeps our actual implementation iterative to avoid deep filesystem
159 * hierarchies overflowing our stack.
160 */
161
162 dirstat_fileid_set_t fileid_seen = _dirstat_fileid_set_create();
163 STAILQ_HEAD(, dirqueue_entry) dirqueue_head = STAILQ_HEAD_INITIALIZER(dirqueue_head);
164
165 struct attrlist attrlist = {
166 .bitmapcount = ATTR_BIT_MAP_COUNT,
167 .commonattr = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_ERROR |
168 ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_FILEID,
169 .dirattr = ATTR_DIR_ENTRYCOUNT,
170 .fileattr = ATTR_FILE_LINKCOUNT | ATTR_FILE_ALLOCSIZE |
171 ATTR_FILE_DATAALLOCSIZE,
172 };
173
174 typedef struct {
175 /*
176 * fields are in order of possible return in buffer (but note that data
177 * is packed in the actual buffer, and only relevant fields are
178 * returned)
179 */
180 uint32_t length;
181 attribute_set_t returned; //ATTR_CMN_RETURNED_ATTRS
182 uint32_t error; //ATTR_CMN_ERROR
183 attrreference_t item_name_info; //ATTR_CMN_NAME
184 fsobj_type_t type; //ATTR_CMN_OBJTYPE
185 uint64_t fileid; //ATTR_CMN_FILEID
186 union {
187 struct {
188 u_int32_t entry_count; //ATTR_DIR_ENTRYCOUNT
189 };
190 struct {
191 u_int32_t link_count; //ATTR_FILE_LINKCOUNT
192 off_t alloc_size; //ATTR_FILE_ALLOCSIZE
193 off_t data_alloc_size; //ATTR_FILE_DATAALLOCSIZE
194 };
195 };
196 } max_attr_entry_t;
197
198 size_t attrbuf_len = (32 * 1024);
199 char *attrbuf = alloca(attrbuf_len);
200
201#ifdef HAS_APFS
202 os_assert(!(flags & DIRSTAT_FAST_ONLY));
203#endif
204
205 do {
206 int fd = -1;
207 char *path;
208
209 if (STAILQ_EMPTY(&dirqueue_head)) {
210 fd = parent_fd;
211 path = NULL;
212 } else {
213 struct dirqueue_entry *dqe = STAILQ_FIRST(&dirqueue_head);
214 STAILQ_REMOVE_HEAD(&dirqueue_head, entries);
215 path = dqe->path;
216 free(dqe);
217
218 fd = openat(parent_fd, path, O_RDONLY | O_DIRECTORY);
219
220 if (fd < 0) {
221 DEBUGPRINT( "Unable to open directory %d:%s => %s\n", parent_fd, path, strerror(errno));
a9aaacca 222 free(path);
b061a43b
A
223 continue;
224 }
225 }
226
227 while (1) {
228 int ret_entry_count = getattrlistbulk(fd, &attrlist, attrbuf, attrbuf_len, 0);
229 if (-1 == ret_entry_count) {
230 if (fd == parent_fd) {
231 reterror = errno;
232 }
233 DEBUGPRINT( "getattrlistbulk on in %s returned error %s\n", path, strerror(errno));
234 break;
235 } else if (0 == ret_entry_count) {
236 break;
237 } else {
238 char *cursor = NULL; //pointer into attrbuf
239 char *entry_start = attrbuf;
240
241 for (int index = 0; index < ret_entry_count; index++) {
242 max_attr_entry_t attrs = {0};
243 char *name = NULL;
244
245 cursor = entry_start;
246
247 memcpy(&attrs.length, cursor, sizeof(attrs.length));
248 cursor += sizeof(attrs.length);
249
250 /* set starting point for next entry */
251 entry_start += attrs.length;
252
253 memcpy(&attrs.returned, cursor, sizeof(attrs.returned));
254 cursor += sizeof(attrs.returned);
255
256 if (attrs.returned.commonattr & ATTR_CMN_ERROR) {
257 memcpy(&attrs.error, cursor, sizeof(attrs.error));
258 cursor += sizeof(attrs.error);
259 }
260
261 if (attrs.error) {
262 DEBUGPRINT( "Got error %s while processing in %s\n", strerror(errno), path);
263 continue;
264 }
265
266 if (attrs.returned.commonattr & ATTR_CMN_NAME) {
267 memcpy(&attrs.item_name_info, cursor, sizeof(attrs.item_name_info));
268 name = cursor + attrs.item_name_info.attr_dataoffset;
269 if (name + attrs.item_name_info.attr_length > entry_start) {
270 name = NULL;
271 }
272 cursor += sizeof(attrs.item_name_info);
273 }
274
275 if (attrs.returned.commonattr & ATTR_CMN_OBJTYPE) {
276 memcpy(&attrs.type, cursor, sizeof(attrs.type));
277 cursor += sizeof(attrs.type);
278 }
279
280 if (attrs.returned.commonattr & ATTR_CMN_FILEID) {
281 memcpy(&attrs.fileid, cursor, sizeof(attrs.fileid));
282 cursor += sizeof(attrs.fileid);
283 }
284
285 if (VDIR == attrs.type) {
286 if (attrs.returned.dirattr & ATTR_DIR_ENTRYCOUNT) {
287 memcpy(&attrs.entry_count, cursor, sizeof(attrs.entry_count));
288 cursor += sizeof(attrs.entry_count);
289 } else {
290 // Fake it so we go down the right path below
291 attrs.entry_count = -1;
292 }
293
294 // avoid descending into empty directories
295 if (attrs.entry_count && name) {
296 struct dirqueue_entry *dqe = malloc(sizeof(struct dirqueue_entry));
297 if (path == NULL) {
298 dqe->path = strdup(name);
299 } else {
300 asprintf(&dqe->path, "%s/%s", path, name);
301 }
302
303 if (dqe->path != NULL) {
304 STAILQ_INSERT_TAIL(&dirqueue_head, dqe, entries);
305 } else {
306 DEBUGPRINT( "Unable to create dqe\n");
307 free(dqe);
308 }
309 } else if (attrs.entry_count != 0) {
310 DEBUGPRINT( "Failed to get name for item in %s\n", path);
311 } else if (attrs.entry_count == 0) {
312 // Empty directory, nothing to do
313 }
314 } else {
315 off_t object_size = 0;
316
317 if (attrs.returned.fileattr & ATTR_FILE_LINKCOUNT) {
318 memcpy(&attrs.link_count, cursor, sizeof(attrs.link_count));
319 cursor += sizeof(attrs.link_count);
320 }
321
322 if (attrs.returned.fileattr & ATTR_FILE_ALLOCSIZE) {
323 memcpy(&attrs.alloc_size, cursor, sizeof(attrs.alloc_size));
324 cursor += sizeof(attrs.alloc_size);
325 object_size = attrs.alloc_size;
326 }
327
328 if (attrs.returned.fileattr & ATTR_FILE_DATAALLOCSIZE) {
329 memcpy(&attrs.data_alloc_size, cursor, sizeof(attrs.data_alloc_size));
330 cursor += sizeof(attrs.data_alloc_size);
331 if (0 == object_size) {
332 object_size = attrs.data_alloc_size;
333 }
334 }
335
336 if (1 == attrs.link_count) {
337 ds->total_size += object_size;
338 } else {
e1ee4b85
A
339 bool seen_fileid = _dirstat_fileid_set_add(fileid_seen, attrs.fileid);
340 if (!seen_fileid) {
b061a43b
A
341 ds->total_size += object_size;
342 } else {
343 DEBUGPRINT( "Skipping hardlinked file at %s/%s\n", path, name);
344 }
345 }
346 }
347 ds->descendants++;
348 }
349 }
350 }
351
352 if (path) {
353 close(fd);
354 free(path);
355 }
356 } while (!STAILQ_EMPTY(&dirqueue_head));
357
358 _dirstat_fileid_set_destroy(fileid_seen);
359
360 if (reterror) {
361 errno = reterror;
362 return -1;
363 } else {
364 return 0;
365 }
366}