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:
- 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.
- 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.
- 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.
- 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.
- 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.
- If we have room for the new struct dirent we proceed; else we go to step 10.
- 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.
- 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.
- 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.
- This is the early-out step that just breaks out of the while
loop in case we've run out of room.
- 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.
- Clean up after ourselves.
- 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()).