Operating on a directory

Our resource manager needs to be able to handle an ls of the directory.

Note: While this isn't an absolute requirement, it's a "nice-to-have." It's acceptable to simply lie and return nothing for the client's readdir(), but most people consider this tacky and lazy programming. As you'll see below, it's not rocket science.

I've presented the code for returning directory entries in the "Resource Managers" chapter of my previous book, Getting Started with QNX Neutrino. This code is a cut-and-paste from the atoz resource manager example, with some important changes.

#define ALIGN(x) (((x) + 3) & ~3)

This is an alignment macro to help align things on a 32-bit boundary within the struct dirent that we are returning.

static int
io_read_dir (resmgr_context_t *ctp, io_read_t *msg,
             RESMGR_OCB_T *ocb)
{
    int     nbytes;
    int     nleft;
    struct  dirent *dp;
    char    *reply_msg;
    char    fname [PATH_MAX];

    // 1) allocate a buffer for the reply
    reply_msg = calloc (1, msg -> i.nbytes);
    if (reply_msg == NULL) {
        return (ENOMEM);
    }

    // 2) assign output buffer
    dp = (struct dirent *) reply_msg;

    // 3) we have "nleft" bytes left
    nleft = msg -> i.nbytes;

    while (ocb -> base.offset < optN * 2) {
    
        // 4) create the filename
        if (ocb -> base.offset & 1) {
            sprintf (fname, "counter-%0*d.gif", 
                   optNsize, (int) (ocb -> base.offset / 2));
        } else {
            sprintf (fname, "counter-%0*d.txt", 
                   optNsize, (int) (ocb -> base.offset / 2));
        }
    
        // 5) see how big the result is
        nbytes = dirent_size (fname);
    
        // 6) do we have room for it?
        if (nleft - nbytes >= 0) {
    
            // 7) fill the dirent, advance the dirent pointer
            dp = dirent_fill (dp, ocb -> base.offset + 2, 
                           ocb -> base.offset, fname);
    
            // 8) move the OCB offset
            ocb -> base.offset++;
    
            // 9) account for the bytes we just used up
            nleft -= nbytes;
        } else {
    
            // 10) don't have any more room, stop
            break;
        }
    }

    // 11) return info back to the client
    MsgReply (ctp -> rcvid, (char *) dp - reply_msg, 
             reply_msg, (char *) dp - reply_msg);

    // 12) release our buffer
    free (reply_msg);

    // 13) tell resmgr library we already did the reply
    return (_RESMGR_NOREPLY);
}

Now I'll walk you through the code:

  1. We're generating data, so we must allocate a place to store our generated data. The client has told us how big their buffer is (the nbytes member of the incoming message), so we allocate a buffer of that size.
  2. For convenience, we're going to use a pointer to struct dirent to write our data into the buffer. Unfortunately, the struct dirent is a variable size (with implicit data fields following the end of the structure) so we can't just simply use an array; we'll need to do pointer arithmetic.
  3. Just like when we are returning file data to the client, we need to see how many bytes we have available to us. In this case, we don't want to overflow our allocated memory. The while loop runs until we have returned all of the data items to the client, with an early out at step 10 in case the data buffer is full.
  4. Once again we use the odd/even aspect of the inode to determine whether we are dealing with the GIF-encoded file or the text file. This time, however, we're generating the inodes ourselves (see note after step 13 below). Depending on what type of file we are returning, we call the appropriate version of sprintf(). Also note the use of the optNsize variable to generate the correct number of digits in the filename.
  5. The nbytes variable holds the size of the new, proposed struct dirent. It might not fit, so we check that in the next step. The helper routine dirent_size() is discussed below.
  6. If we have room for the new struct dirent we proceed; else we go to step 10.
  7. Now that we know that the proposed size of the struct dirent is okay, we proceed to fill the information by calling the helper routine dirent_fill() (discussed below). Notice that we add 2 to the OCB's offset member. That's because our first inode number is 2 (1 is reserved for the directory entry itself, and 0 is invalid). Also notice that dirent_fill() returns a new pointer to where the next directory entry should go; we assign this to our dp pointer.
  8. Next we increment the OCB's offset member. This is analogous to what we did when returning file data (in io_read_file()) in that we are making note of where we last were, so that we can resume on the next readdir() call.
  9. Since we wrote nbytes through the dp pointer in step 7, we need to account for these bytes by subtracting them from the number of bytes we still have available.
  10. This is the "early-out" step that just breaks out of the while loop in case we've run out of room.
  11. Just like when we handle a file, we need to return the data to the client. Unlike when we handle a file, we're returning data from our own allocated buffer, rather than the text buffer or the GIF-encoded output buffer.
  12. Clean up after ourselves.
  13. Finally, we tell the resource manager library that we did the reply, so that it doesn't need to.
Note: In step 4, we need to make note of the relationship between inode values and the offset member of the OCB. The meaning of the offset member is entirely up to us—all that QNX Neutrino demands is that it be consistent between invocations of the directory-reading function. In our case, I've decided that the offset member is going to be directly related to the array index (times 2) of the two arrays of attributes structures. The array index is directly related to the actual counter number (i.e. an array index of 7 corresponds to counter number 7).
static int
dirent_size (char *fname)
{
  return (ALIGN (sizeof (struct dirent) - 4 + strlen (fname)));
}

static struct dirent *
dirent_fill (struct dirent *dp, int inode, int offset, char *fname)
{
    dp -> d_ino = inode;
    dp -> d_offset = offset;
    strcpy (dp -> d_name, fname);
    dp -> d_namelen = strlen (dp -> d_name);
    dp -> d_reclen = ALIGN (sizeof (struct dirent)
                   - 4 + dp -> d_namelen);
    return ((struct dirent *) ((char *) dp + dp -> d_reclen));
}

These two utility functions calculate the size of the directory entry (dirent_size()) and then fill it (dirent_fill()).