So now all that's left is to "simply" fill in the struct dirent with the "contents" of our directory. Here's what the struct dirent looks like (from <dirent.h>):

struct dirent {
    ino_t      d_ino;
    off_t      d_offset;
    uint16_t   d_reclen;
    uint16_t   d_namelen;
    char       d_name [1];

Here's a quick explanation of the various members:

The "inode"—a mountpoint-unique serial number that cannot be zero (zero traditionally indicates that the entry corresponding to this inode is free/empty).
The offset into the directory we just talked about above. In our example, this will be a simple number like "0," "1," "2," etc. In some filesystems, this is the offset of the next directory.
The size of the entire struct dirent field and any extensions that may be placed within it. The size includes any alignment filler required.
The number of characters in the d_name field, not including the NUL terminator.
The name of this directory entry, which must be NUL terminated.

When returning the struct dirent entries, the return code passed back to the client is the number of bytes returned.