]> git.saurik.com Git - apple/libc.git/blame - libdarwin/dirstat.c
Libc-1353.41.1.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));
222 continue;
223 }
224 }
225
226 while (1) {
227 int ret_entry_count = getattrlistbulk(fd, &attrlist, attrbuf, attrbuf_len, 0);
228 if (-1 == ret_entry_count) {
229 if (fd == parent_fd) {
230 reterror = errno;
231 }
232 DEBUGPRINT( "getattrlistbulk on in %s returned error %s\n", path, strerror(errno));
233 break;
234 } else if (0 == ret_entry_count) {
235 break;
236 } else {
237 char *cursor = NULL; //pointer into attrbuf
238 char *entry_start = attrbuf;
239
240 for (int index = 0; index < ret_entry_count; index++) {
241 max_attr_entry_t attrs = {0};
242 char *name = NULL;
243
244 cursor = entry_start;
245
246 memcpy(&attrs.length, cursor, sizeof(attrs.length));
247 cursor += sizeof(attrs.length);
248
249 /* set starting point for next entry */
250 entry_start += attrs.length;
251
252 memcpy(&attrs.returned, cursor, sizeof(attrs.returned));
253 cursor += sizeof(attrs.returned);
254
255 if (attrs.returned.commonattr & ATTR_CMN_ERROR) {
256 memcpy(&attrs.error, cursor, sizeof(attrs.error));
257 cursor += sizeof(attrs.error);
258 }
259
260 if (attrs.error) {
261 DEBUGPRINT( "Got error %s while processing in %s\n", strerror(errno), path);
262 continue;
263 }
264
265 if (attrs.returned.commonattr & ATTR_CMN_NAME) {
266 memcpy(&attrs.item_name_info, cursor, sizeof(attrs.item_name_info));
267 name = cursor + attrs.item_name_info.attr_dataoffset;
268 if (name + attrs.item_name_info.attr_length > entry_start) {
269 name = NULL;
270 }
271 cursor += sizeof(attrs.item_name_info);
272 }
273
274 if (attrs.returned.commonattr & ATTR_CMN_OBJTYPE) {
275 memcpy(&attrs.type, cursor, sizeof(attrs.type));
276 cursor += sizeof(attrs.type);
277 }
278
279 if (attrs.returned.commonattr & ATTR_CMN_FILEID) {
280 memcpy(&attrs.fileid, cursor, sizeof(attrs.fileid));
281 cursor += sizeof(attrs.fileid);
282 }
283
284 if (VDIR == attrs.type) {
285 if (attrs.returned.dirattr & ATTR_DIR_ENTRYCOUNT) {
286 memcpy(&attrs.entry_count, cursor, sizeof(attrs.entry_count));
287 cursor += sizeof(attrs.entry_count);
288 } else {
289 // Fake it so we go down the right path below
290 attrs.entry_count = -1;
291 }
292
293 // avoid descending into empty directories
294 if (attrs.entry_count && name) {
295 struct dirqueue_entry *dqe = malloc(sizeof(struct dirqueue_entry));
296 if (path == NULL) {
297 dqe->path = strdup(name);
298 } else {
299 asprintf(&dqe->path, "%s/%s", path, name);
300 }
301
302 if (dqe->path != NULL) {
303 STAILQ_INSERT_TAIL(&dirqueue_head, dqe, entries);
304 } else {
305 DEBUGPRINT( "Unable to create dqe\n");
306 free(dqe);
307 }
308 } else if (attrs.entry_count != 0) {
309 DEBUGPRINT( "Failed to get name for item in %s\n", path);
310 } else if (attrs.entry_count == 0) {
311 // Empty directory, nothing to do
312 }
313 } else {
314 off_t object_size = 0;
315
316 if (attrs.returned.fileattr & ATTR_FILE_LINKCOUNT) {
317 memcpy(&attrs.link_count, cursor, sizeof(attrs.link_count));
318 cursor += sizeof(attrs.link_count);
319 }
320
321 if (attrs.returned.fileattr & ATTR_FILE_ALLOCSIZE) {
322 memcpy(&attrs.alloc_size, cursor, sizeof(attrs.alloc_size));
323 cursor += sizeof(attrs.alloc_size);
324 object_size = attrs.alloc_size;
325 }
326
327 if (attrs.returned.fileattr & ATTR_FILE_DATAALLOCSIZE) {
328 memcpy(&attrs.data_alloc_size, cursor, sizeof(attrs.data_alloc_size));
329 cursor += sizeof(attrs.data_alloc_size);
330 if (0 == object_size) {
331 object_size = attrs.data_alloc_size;
332 }
333 }
334
335 if (1 == attrs.link_count) {
336 ds->total_size += object_size;
337 } else {
338 bool new_fileid = _dirstat_fileid_set_add(fileid_seen, attrs.fileid);
339 if (new_fileid) {
340 ds->total_size += object_size;
341 } else {
342 DEBUGPRINT( "Skipping hardlinked file at %s/%s\n", path, name);
343 }
344 }
345 }
346 ds->descendants++;
347 }
348 }
349 }
350
351 if (path) {
352 close(fd);
353 free(path);
354 }
355 } while (!STAILQ_EMPTY(&dirqueue_head));
356
357 _dirstat_fileid_set_destroy(fileid_seen);
358
359 if (reterror) {
360 errno = reterror;
361 return -1;
362 } else {
363 return 0;
364 }
365}