+typedef struct __attribute__((packed)) __attribute__((__aligned__(4))) {
+ uint32_t length;
+
+ /* common attributes */
+ attribute_set_t attrset;
+ attrreference_t name;
+ dev_t st_dev;
+ fsobj_type_t objtype;
+ struct timespec st_birthtimespec;
+ struct timespec st_mtimespec;
+ struct timespec st_ctimespec;
+ struct timespec st_atimespec;
+ uid_t st_uid;
+ gid_t st_gid;
+ uint32_t accessmask;
+ uint32_t st_flags;
+ uint64_t st_ino;
+
+ /* non-directory attributes */
+ uint32_t st_nlink;
+ off_t allocsize;
+ uint32_t st_blksize;
+ uint32_t st_rdev;
+ off_t st_size;
+} attrListAttributes;
+
+typedef struct __attribute__((packed)) {
+ uint32_t length;
+
+ /* common attributes */
+ attribute_set_t attrset;
+ attrreference_t name;
+ dev_t st_dev;
+ fsobj_type_t objtype;
+ uint64_t st_ino;
+
+ /* non-directory attributes */
+ uint32_t st_nlink;
+ uint32_t st_rdev;
+} attrListAttributes_nostat;
+
+#define ATTR_BUF_SIZE (32*1024)
+
+typedef struct {
+ DIR *dirp;
+
+ struct attrlist requested_attrs;
+ void *attrbuf;
+ union {
+ attrListAttributes *curattr;
+ attrListAttributes_nostat *curattr_nostat;
+ };
+ int dirfd;
+ bool done;
+ bool nostat;
+ bool needs_dot;
+ bool needs_dotdot;
+
+ int entry_count;
+ int cur_entry;
+} dir_handle;
+
+typedef struct {
+ char *d_name;
+ size_t d_namlen;
+ struct stat sb;
+ int d_type;
+ bool stat_valid;
+} dir_entry;
+
+static bool
+advance_directory(dir_handle *handle)
+{
+ if (handle->done) return true;
+
+ os_assert(handle->dirfd != -1);
+ handle->entry_count = getattrlistbulk(handle->dirfd, &handle->requested_attrs,
+ handle->attrbuf, ATTR_BUF_SIZE, FSOPT_PACK_INVAL_ATTRS);
+ if (handle->entry_count == -1) {
+ goto error;
+ } else if (handle->entry_count == 0) {
+ /* No more entries. */
+ handle->done = true;
+ }
+ handle->cur_entry = 0;
+ handle->curattr = handle->attrbuf;
+ return true;
+
+error: {
+ int saved_errno = errno;
+ close(handle->dirfd);
+ handle->dirfd = -1;
+ errno = saved_errno;
+ return false;
+ }
+}
+
+static bool
+open_directory(FTS *sp, dir_handle *handle, const char *path)
+{
+ memset(handle, 0, sizeof(*handle));
+
+ handle->nostat = ISSET(FTS_NOSTAT);
+ handle->needs_dot = handle->needs_dotdot = ISSET(FTS_SEEDOT);
+
+ handle->dirfd = open(path, O_RDONLY | O_NONBLOCK | O_DIRECTORY | O_CLOEXEC);
+ if (handle->dirfd == -1) goto fallback;
+
+ handle->attrbuf = malloc(ATTR_BUF_SIZE);
+ if (!handle->attrbuf) goto fallback;
+
+ handle->requested_attrs.bitmapcount = ATTR_BIT_MAP_COUNT;
+ if (!handle->nostat) {
+ handle->requested_attrs.commonattr =
+ ATTR_CMN_RETURNED_ATTRS|
+ ATTR_CMN_NAME|ATTR_CMN_DEVID|ATTR_CMN_OBJTYPE|
+ ATTR_CMN_CRTIME|ATTR_CMN_MODTIME|ATTR_CMN_CHGTIME|ATTR_CMN_ACCTIME|
+ ATTR_CMN_OWNERID|ATTR_CMN_GRPID|ATTR_CMN_ACCESSMASK|ATTR_CMN_FLAGS|
+ ATTR_CMN_FILEID;
+ handle->requested_attrs.fileattr = ATTR_FILE_LINKCOUNT|ATTR_FILE_ALLOCSIZE|
+ ATTR_FILE_IOBLOCKSIZE|ATTR_FILE_DEVTYPE|ATTR_FILE_DATALENGTH;
+ } else {
+ handle->requested_attrs.commonattr = ATTR_CMN_RETURNED_ATTRS|
+ ATTR_CMN_NAME|ATTR_CMN_DEVID|ATTR_CMN_OBJTYPE|
+ ATTR_CMN_FILEID;
+ handle->requested_attrs.fileattr = ATTR_FILE_LINKCOUNT;
+ }
+
+ if (advance_directory(handle)) {
+ /*
+ * We successfully read the first attribute buffer,
+ * so we'll use getdirentriesattr/getattrlistbulk.
+ */
+ return true;
+ }
+
+fallback:
+ if (handle->dirfd != -1) close(handle->dirfd);
+ handle->dirfd = -1;
+ free(handle->attrbuf);
+ handle->attrbuf = NULL;
+ handle->dirp = opendir(path);
+ return (handle->dirp != NULL);
+}
+
+static bool
+read_dirent(dir_handle *handle, dir_entry *entry)
+{
+ if (handle->dirp) {
+ struct dirent *de = readdir(handle->dirp);
+ if (de == NULL) return false;
+ entry->d_name = de->d_name;
+ entry->d_namlen = de->d_namlen;
+ entry->d_type = de->d_type;
+ entry->stat_valid = false;
+ return true;
+ }
+
+ /*
+ * There are three states that our dir_handle can be in:
+ * - real getattrlistbulk use (entry_count >= 0) - the rest of this function
+ * - fallback - handled above with dirp
+ * - closed fallback (dirp == NULL) - handled with this check
+ */
+ if (handle->dirfd == -1) {
+ return false;
+ }
+
+ if (handle->needs_dot) {
+ handle->needs_dot = false;
+ entry->d_name = ".";
+ entry->d_namlen = strlen(".");
+ entry->d_type = DT_DIR;
+ entry->stat_valid = false;
+ return true;
+ } else if (handle->needs_dotdot) {
+ handle->needs_dotdot = false;
+ entry->d_name = "..";
+ entry->d_namlen = strlen("..");
+ entry->d_type = DT_DIR;
+ entry->stat_valid = false;
+ return true;
+ }
+
+ if (handle->cur_entry == handle->entry_count) {
+ if (handle->done) return false; /* Already done with the directory. */
+ if (!advance_directory(handle)) return false; /* Reading the next buffer failed. */
+ if (handle->done) return false; /* We're now done with the directory. */
+ }
+
+ bzero(entry, sizeof(*entry));
+
+ attrListAttributes *curattr_stat = NULL;
+ attrListAttributes_nostat *curattr_nostat = NULL;
+ if (!handle->nostat) {
+ curattr_stat = handle->curattr;
+ handle->cur_entry++;
+ handle->curattr = (attrListAttributes*)(((char*)curattr_stat) + curattr_stat->length);
+ os_assert((handle->cur_entry == handle->entry_count) ||
+ (void*)handle->curattr + handle->curattr->length <= (void*)handle->attrbuf + ATTR_BUF_SIZE);
+
+ os_assert(curattr_stat->name.attr_length > 0);
+ entry->d_name = ((char*)&curattr_stat->name) + curattr_stat->name.attr_dataoffset;
+ /* attr_length includes the null terminator, but readdir's d_namlen doesn't. */
+ entry->d_namlen = curattr_stat->name.attr_length - 1;
+ os_assert((void*)entry->d_name + curattr_stat->name.attr_length <= (void*)handle->attrbuf + ATTR_BUF_SIZE);
+ } else {
+ curattr_nostat = handle->curattr_nostat;
+ handle->cur_entry++;
+ handle->curattr_nostat = (attrListAttributes_nostat*)(((char*)curattr_nostat) + curattr_nostat->length);
+ os_assert((handle->cur_entry == handle->entry_count) ||
+ (void*)handle->curattr + handle->curattr->length <= (void*)handle->attrbuf + ATTR_BUF_SIZE);
+
+ os_assert(curattr_nostat->name.attr_length > 0);
+ entry->d_name = ((char*)&curattr_nostat->name) + curattr_nostat->name.attr_dataoffset;
+ /* attr_length includes the null terminator, but readdir's d_namlen doesn't. */
+ entry->d_namlen = curattr_nostat->name.attr_length - 1;
+ os_assert((void*)entry->d_name + curattr_nostat->name.attr_length <= (void*)handle->attrbuf + ATTR_BUF_SIZE);
+ }
+
+ int stat_type = 0;
+
+ switch (handle->nostat ? curattr_nostat->objtype : curattr_stat->objtype) {
+ case VREG:
+ entry->d_type = DT_REG;
+ stat_type = S_IFREG;
+ break;
+ case VDIR:
+ entry->d_type = DT_DIR;
+ /* Force a stat call so we don't have to guess on st_size, st_blocks, etc. */
+ // stat_type = S_IFDIR;
+ break;
+ case VBLK:
+ entry->d_type = DT_BLK;
+ stat_type = S_IFBLK;
+ break;
+ case VCHR:
+ entry->d_type = DT_CHR;
+ stat_type = S_IFCHR;
+ break;
+ case VLNK:
+ entry->d_type = DT_LNK;
+ stat_type = S_IFLNK;
+ break;
+ case VSOCK:
+ entry->d_type = DT_SOCK;
+ stat_type = S_IFSOCK;
+ break;
+ case VFIFO:
+ entry->d_type = DT_FIFO;
+ stat_type = S_IFIFO;
+ break;
+ default:
+ entry->d_type = DT_UNKNOWN;
+ break;
+ }
+
+ if (!handle->nostat && stat_type) {
+ entry->stat_valid = true;
+
+ /*
+ * Make sure we got all the attributes we need to fill out a stat structure.
+ */
+
+ attrgroup_t requiredCommon = handle->requested_attrs.commonattr;
+ attrgroup_t requiredFile = handle->requested_attrs.fileattr;
+
+ if ((entry->d_type != DT_BLK) && (entry->d_type != DT_CHR)) {
+ /* It's okay for ATTR_FILE_DEVTYPE to be missing if the entry isn't a block or character device. */
+ curattr_stat->st_rdev = 0;
+ requiredFile &= ~ATTR_FILE_DEVTYPE;
+ }
+
+ if ((curattr_stat->attrset.commonattr & ATTR_CMN_CRTIME) == 0) {
+ /* Many (most?) file systems don't support create time (see vn_stat_noauth in xnu) */
+ curattr_stat->st_birthtimespec.tv_sec = curattr_stat->st_birthtimespec.tv_nsec = 0;
+ requiredCommon &= ~ ATTR_CMN_CRTIME;
+ }
+
+ if ((curattr_stat->attrset.commonattr & requiredCommon) != requiredCommon ||
+ (curattr_stat->attrset.fileattr & requiredFile) != requiredFile) {
+ /* Some of our required attributes are missing. */
+ entry->stat_valid = false;
+ }
+ } else {
+ entry->stat_valid = false;
+ }
+
+ if (entry->stat_valid) {
+
+#define COPY_FIELD(fld) entry->sb.fld = curattr_stat->fld
+ COPY_FIELD(st_dev);
+ /* COPY_FIELD(st_mode); Handled below. */
+ COPY_FIELD(st_nlink);
+ COPY_FIELD(st_ino);
+ COPY_FIELD(st_uid);
+ COPY_FIELD(st_gid);
+ COPY_FIELD(st_rdev);
+ COPY_FIELD(st_atimespec);
+ COPY_FIELD(st_mtimespec);
+ COPY_FIELD(st_ctimespec);
+#if __DARWIN_64_BIT_INO_T
+ COPY_FIELD(st_birthtimespec);
+#endif /* __DARWIN_64_BIT_INO_T */
+ COPY_FIELD(st_size);
+ /* COPY_FIELD(st_blocks); Handled below. */
+ COPY_FIELD(st_blksize);
+ COPY_FIELD(st_flags);
+#undef COPY_FIELD
+
+ /* We have to handle some fields specially. */
+ entry->sb.st_mode = (curattr_stat->accessmask & ~S_IFMT) | stat_type;
+ entry->sb.st_blocks = howmany(curattr_stat->allocsize, 512); /* Matches vn_stat implementation in xnu. */
+ }
+ return true;
+}
+
+static int
+dir_fd(dir_handle *handle)
+{
+ return handle->dirp ? dirfd(handle->dirp) : handle->dirfd;
+}
+
+static void
+close_directory(dir_handle *handle)
+{
+ if (handle->dirp) {
+ closedir(handle->dirp);
+ handle->dirp = NULL;
+ }
+ if (handle->dirfd != -1) {
+ close(handle->dirfd);
+ handle->dirfd = -1;
+ }
+ free(handle->attrbuf);
+ handle->attrbuf = NULL;
+}
+