`getattrlistbulk(2)` erroneously returns `ERANGE` on 10.13.

Originator:sujayakar
Number:rdar://36268180 Date Originated:1/2/2018
Status:Open Resolved:
Product:macOS + SDK Product Version:10.13
Classification: Reproducible:Yes
 
Area:
Terminal

Summary:
`getattrlistbulk(2)` mistakenly returns `ERANGE` after fully iterating through a directory.  The final call should return zero, indicating that the directory stream has terminated, but it returns an error with `ERANGE` set in `errno`.

Steps to Reproduce:
Run the attached C program with 88 as its argument. 

To summarize, create a directory with a single empty file named "a" underneath, and call `getattrlistbulk` on it with an `attrlist` containing `ATTR_BIT_MAP_COUNT`, `ATTR_CMN_NAME | ATTR_CMN_OBJTYPE | ATTR_CMN_OBJID | ATTR_CMN_MODTIME | ATTR_CMN_CHGTIME` and `ATTR_FILE_DATALENGTH`, a buffer of 88 bytes, and options set to zero.  The first call will return the metadata for "a", and the second call will return -1 with `errno` set to `ERANGE`.

This code (https://github.com/apple/darwin-xnu/blob/master/bsd/vfs/vfs_attrlist.c#L3879) looks error-prone; perhaps `eofflag` is not being set in this condition?

Expected Results:
The second call should return zero, indicating the directory stream has finished.

Actual Results:
The second call returns -1, failing with `ERANGE`.

Version/Build:
10.13 (17A365)

Configuration:
On an APFS filesystem

Attached diriterbug.c:
```
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/attr.h>
#include <fcntl.h>
#include <unistd.h>

struct _attrbufhdr {
  u_int32_t length;
  attribute_set_t returned;
};

int main(int argc, char** argv) {
  if (argc != 2) {
    fprintf(stderr, "Usage: %s <bufsize>\n", argv[0]);
    return 1;
  }
  int bufsize = (int) strtol(argv[1], NULL, 10);

  if (mkdir("testdir", 0777) != 0) {
    perror("mkdir testdir failed");
    return 1;
  }

  int dirfd = open("testdir", O_RDONLY);
  if (dirfd < 0) {
    perror("open testdir failed");
    return 1;
  }

  int fd = openat(dirfd, "a", O_CREAT);
  if (fd < 0) {
    perror("creating testdir/a failed");
    return 1;
  }
  if (close(fd) < 0) {
    perror("closing testdir/a failed");
    return 1;
  }

  struct attrlist attrs_req;
  memset(&attrs_req, 0, sizeof(attrs_req));
  attrs_req.bitmapcount = ATTR_BIT_MAP_COUNT;
  attrs_req.commonattr = (ATTR_CMN_RETURNED_ATTRS |
                          ATTR_CMN_NAME |
                          ATTR_CMN_OBJTYPE |
                          ATTR_CMN_OBJID |
                          ATTR_CMN_MODTIME |
                          ATTR_CMN_CHGTIME);
  attrs_req.fileattr = ATTR_FILE_DATALENGTH;

  void* buf = malloc(bufsize);
  if (buf == NULL) {
    perror("malloc failed");
    return 1;
  }

  // Call `getattrlistbulk` for the first time to get our single entry.
  int count = getattrlistbulk(dirfd, &attrs_req, buf, bufsize, 0);

  if (count < 0) {
    perror("first getattrlistbulk failed");
    return 1;
  } else if (count != 1) {
    fprintf(stderr, "Expected one entry from first getattrlistbulk, received %d\n", count);
    return 1;
  }

  // Parse out the returned attribute set.
  char* cursor = (char*) buf;
  struct _attrbufhdr* hdr = (struct _attrbufhdr*) cursor;
  cursor += sizeof(struct _attrbufhdr);

  // Parse out each attribute in order.
  if (hdr->returned.commonattr & ATTR_CMN_NAME) {
    attrreference_t *ref = (attrreference_t*) cursor;
    cursor += sizeof(attrreference_t);
    printf("  ATTR_CMN_NAME: %.*s\n", ref->attr_length, ((char*) ref) + ref->attr_dataoffset);
  }
  if (hdr->returned.commonattr & ATTR_CMN_OBJTYPE) {
    fsobj_type_t *ref = (fsobj_type_t*) cursor;
    cursor += sizeof(fsobj_type_t);
    printf("  ATTR_CMN_OBJTYPE: %d\n", *ref);
  }
  if (hdr->returned.commonattr & ATTR_CMN_OBJID) {
    fsobj_id_t *ref = (fsobj_id_t*) cursor;
    cursor += sizeof(fsobj_id_t);
    printf("  ATTR_CMN_OBJID: %d:%d\n", ref->fid_objno, ref->fid_generation);
  }
  if (hdr->returned.commonattr & ATTR_CMN_MODTIME) {
    struct timespec *ref = (struct timespec*) cursor;
    cursor += sizeof(struct timespec);
    printf("  ATTR_CMN_MODTIME: %0.4f\n", ref->tv_sec + ((double) ref->tv_nsec) / 1e9);
  }
  if (hdr->returned.commonattr & ATTR_CMN_CHGTIME) {
    struct timespec *ref = (struct timespec*) cursor;
    cursor += sizeof(struct timespec);
    printf("  ATTR_CMN_CHGTIME: %0.4f\n", ref->tv_sec + ((double) ref->tv_nsec) / 1e9);
  }

  // Zero out the buffer and try again.
  memset(buf, 0, bufsize);
  count = getattrlistbulk(dirfd, &attrs_req, buf, bufsize, 0);
  if (count < 0) {
    perror("second getattrlistbulk failed");
    return 1;
  } else if (count != 0) {
    fprintf(stderr, "Expected EOF from second getattrlistbulk, received %d\n", count);
    return 1;
  }
  printf("Success!\n");
  return 0;
}
```

Comments


Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!