Created
May 27, 2015 08:58
-
-
Save anonymous/8f92e5c5b67133cbcc86 to your computer and use it in GitHub Desktop.
A sample on how to properly use getattrlistbulk(2)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// A multithreaded directory size measurer with various bugs, flaws and defects | |
// based on `man getattrlistbulk` | |
// All modifications against the original example code must be distributed under GPLv3 license. | |
// | |
#include <stdlib.h> | |
#include <sys/syscall.h> | |
#include <sys/attr.h> | |
#include <sys/errno.h> | |
#include <sys/vnode.h> | |
#include <unistd.h> | |
#include <fcntl.h> | |
#include <stdio.h> | |
#include <assert.h> | |
#include <stddef.h> | |
#include <string.h> | |
#include <pthread.h> | |
#include <libkern/OSAtomic.h> | |
#include <string> | |
#include <vector> | |
#include <algorithm> | |
const int kAttrBufSize = 25600; | |
typedef struct val_attrs { | |
uint32_t length; | |
attribute_set_t returned; | |
attrreference_t name_info; | |
char *name; | |
fsobj_type_t obj_type; | |
uint64_t size; | |
uint32_t entries; | |
} val_attrs_t; | |
struct val_entry { | |
std::string name; | |
int64_t size; | |
std::vector<val_entry*> children; | |
val_entry* parent; | |
val_entry() : size(0), parent(NULL) {} | |
}; | |
int64_t totalSize = 0; | |
int activeThreads = 0; | |
int totalFiles = 0; | |
void* traverse(void *p) { | |
char *attrBuf = (char*) malloc(kAttrBufSize); | |
val_entry* parent = (val_entry*) p; | |
struct attrlist attrList = { | |
.bitmapcount = ATTR_BIT_MAP_COUNT, | |
.commonattr = ATTR_CMN_RETURNED_ATTRS | ATTR_CMN_NAME | ATTR_CMN_OBJTYPE, | |
.fileattr = ATTR_FILE_TOTALSIZE, | |
.dirattr = ATTR_DIR_ENTRYCOUNT | |
}; | |
int dirfd = open(parent->name.c_str(), O_RDONLY, 0); | |
if (dirfd < 0) { | |
fprintf(stderr, "Could not open directory \"%s\". ", parent->name.c_str()); | |
perror("Error was "); | |
} else { | |
for (;;) { | |
int retcount = getattrlistbulk(dirfd, &attrList, attrBuf, kAttrBufSize, 1); | |
if (retcount == -1) { | |
perror("Error returned: "); | |
break; | |
} else if (retcount == 0) { | |
break; | |
} else { | |
char *entry_start = attrBuf; | |
for (int index = 0; index < retcount; index++) { | |
val_attrs_t attrs = {0}; | |
char *field = entry_start; | |
attrs.length = *(uint32_t *)field; | |
field += sizeof(uint32_t); | |
entry_start += attrs.length; | |
attrs.returned = *(attribute_set_t *)field; | |
field += sizeof(attribute_set_t); | |
if (attrs.returned.commonattr & ATTR_CMN_NAME) { | |
attrs.name = field; | |
attrs.name_info = *(attrreference_t *)field; | |
field += sizeof(attrreference_t); | |
} | |
if (attrs.returned.commonattr & ATTR_CMN_OBJTYPE) { | |
attrs.obj_type = *(fsobj_type_t *)field; | |
field += sizeof(fsobj_type_t); | |
} | |
if (attrs.returned.fileattr & ATTR_FILE_TOTALSIZE) { | |
attrs.size = *(uint64_t *)field; | |
field += sizeof(uint64_t); | |
OSAtomicAdd64(attrs.size, &totalSize); | |
for (val_entry* e = parent; e; e = e->parent) { | |
OSAtomicAdd64(attrs.size, &e->size); | |
} | |
} | |
if (attrs.returned.dirattr & ATTR_DIR_ENTRYCOUNT) { | |
attrs.entries = *(uint32_t *)field; | |
field += sizeof(uint32_t); | |
} | |
OSAtomicIncrement32(&totalFiles); | |
val_entry* e = new val_entry(); | |
e->name = parent->name + '/' + (attrs.name + attrs.name_info.attr_dataoffset); | |
e->size = attrs.size; | |
e->parent = parent; | |
parent->children.push_back(e); | |
switch (attrs.obj_type) { | |
case VREG: | |
break; | |
case VDIR: | |
OSAtomicIncrement32(&activeThreads); | |
if (attrs.entries > 32 && activeThreads < 128) { | |
pthread_t tid; | |
pthread_create(&tid, NULL, traverse, e); | |
} else { | |
traverse(e); | |
} | |
break; | |
default: | |
break; | |
} | |
} | |
} | |
} | |
close(dirfd); | |
} | |
free(attrBuf); | |
OSAtomicDecrement32(&activeThreads); | |
return NULL; | |
} | |
int clean_entries(val_entry* b) { | |
int r = 0; | |
for (const auto& c : b->children) { | |
if (c->children.size()) r += clean_entries(c); | |
delete c; | |
++r; | |
} | |
return r; | |
} | |
int main(int argc, char** argv) { | |
activeThreads = 1; | |
val_entry root; | |
root.name = argc < 2 ? "." : argv[1]; | |
struct timeval tbegin, tend; | |
gettimeofday(&tbegin, NULL); | |
traverse(&root); | |
while (activeThreads > 0) usleep(1000); | |
std::stable_sort(root.children.begin(), root.children.end(), | |
[](val_entry* a, val_entry* b) { | |
return a->size > b->size; | |
} | |
); | |
for (const auto& e : root.children) { printf("%*lld\t%s\n", 20, e->size, e->name.c_str()); } | |
int rc = clean_entries(&root); | |
gettimeofday(&tend, NULL); | |
int64_t tms = ((tend.tv_sec * 1000000 + tend.tv_usec) - (tbegin.tv_sec * 1000000 + tbegin.tv_usec)) / 1000; | |
int64_t tsec = tms / 1000; | |
int64_t tmsec = tms % 1000; | |
printf("\nfiles = %d checked = %d\n", totalFiles, rc); | |
printf("grand total = %lld checked = %lld\n", root.size, totalSize); | |
printf("seconds elapsed = %lld.%03lld\n", tsec, tmsec); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment