How to return the number of bytes required for a subsequent operation


A very common pattern is to allow a caller to ask for the number bytes (or elements) required and then ask for the data, many user mode Win32 APIs (like RegQueryValueEx) and kernel mode (like IoGetDeviceProperty) implement it. You first ask for the number of bytes needed (passing NULL and a pointer to a size), allocate the memory needed, and then ask for the data using the buffer and size to get the data. It is a very useful pattern. So how do you implement it in a driver? Well, there are 2 ways I can think of implementing it, both use IOCTLs.


The first implementation is to define a structure which has a size as its first field and then return the number of bytes/elements required in the first field. For instance,

    typedef struct _MY_ELEMENT {
ULONG Id;
UCHAR Name[32];
} MY_ELEMENT, *PMY_ELEMENT;

typedef struct _MY_ELEMENT_LIST {
ULONG NumElements;
MY_ELEMENT ElementList[1]; // open ended array
} MY_ELEMENT_LIST, *PMY_ELEMENT_LIST;

// An easy way to figure out how many bytes are in the structure before the list
// of elements
#define MY_ELEMENT_LIST_HEADER_SIZE (FIELD_OFFSET(MY_ELEMENT_LIST, ElementList))

size_t length;
NTSTATUS status;
PMY_ELEMENT_LIST pList = NULL;
ULONG_PTR information = 0x0;

switch (IoctlCode) {
case IOCTL_GET_ELEMENTS:
// This will make sure that buffer length is at least sizeof(ULONG)
status = WdfRequestRetrieveOutputBuffer(Request, sizeof(ULONG), (PVOID*) &pList, &length);
if (!NT_SUCCESS(status)) {
// do nothing since buffer length is < sizeof(ULONG), we will break out and return error after the if() clause
}
else if (length == sizeof(ULONG) {
pList->NumElements = [get number of elements];
information = 0x0;
}
else {
// compute how many elements we can put into the buffer
length -= MY_ELEMENT_LIST_HEADER_SIZE;

// check to make sure the buffer is an integral number of MY_ELEMENTs
if ((length % sizeof(MY_ELEMENT)) != 0) {
status = STATUS_INVALID_PARAMETER;
}
else {
pList->NumElements = [get number of elements]
[copy elements into pList->ElementList]

// compute how many bytes we copied over
information = MY_ELEMENT_LIST_HEADER_SIZE +
[num elements copied]*sizeof(MY_ELEMENT);
status = STATUS_SUCCESS;
}
}
break;
}
...
WdfRequestCompleteWithInformation(Request, status, information);


In this example, we return the number of elements required in the list. We first check for the size of the output buffer; WdfRequestRetrieveOutputBuffer will help with the inital check. If it is a ULONG then we know that the caller is asking for the number of elements, so we return this information to the caller in the supplied output buffer. If the size of the output buffer is less than a ULONG, it is an error. If the size of the output buffer is greater than a ULONG, we first make sure that the buffer size is an integral number of elements (what good is a partial element?) and then copy over as many elements as possible. The caller would have the following code to get the data.

    PMY_ELEMENT_LIST pList;
ULONG numElements, listSize; size;

DeviceIoControl(hDevice, IOCTL_GET_ELEMENTS, NULL, 0, &numElements, sizeof(numElements), ...);

listSize = MY_ELEMENT_LIST_HEADER_SIZE + numElements * sizeof(MY_ELEMENT);
pList = (PMY_ELEMENT_LIST) LocalAlloc(LPTR, listSize);

DeviceIoControl(hDevice, IOCTL_GET_ELEMENTS, NULL, 0, pList, listSize, ...);


In the second implementation we do not use a size field at all. Instead we let the I/O manager return the data for us to the caller. In this implementation we return STATUS_BUFFER_OVERFLOW and the number of required bytes (vs. the required number of elements in the previous example, a very important distinction!). The value of STATUS_BUFFER_OVERFLOW, 0x80000005, is very important in this implementation. This value will not return TRUE for NT_SUCCESS() or NT_ERROR(), rather it will return TRUE for NT_WARNING(). When an NT_WARNING() value is returned in response to an IOCTL IRP, the I/O manager will copy the value of Irp->IoStatus.Information to the lpBytesReturned parameter of DeviceIoControl (and does not copy anything to the caller's output buffer). This allows you to just have an array of elements as your output buffer.


NOTE:  if Irp->IoStatus.Status is an NT_WARNING() and the output buffer is buffered (e.g. METHOD_BUFFERED), the I/O manager will also try to use Irp->IoStatus.Information as the number of bytes to copy to the output buffer! This means that you should only return the number of bytes required if the output buffer is not present, otherwise the I/O manager can overwrite your process's address space very easily.  Checking for the presence of the output buffer is problematic.  The easiest solution is to avoid using METHOD_BUFFERED in the IOCTL value and use METHOD_IN/OUT_DIRECT instead.  Otherwise, you can check for IRP_INPUT_OPERATION in the IRP's Flags field, see MSDN for details on how to do this.  Thank you to Ishai Ben Aroya for pointing out this subtle problem!


Here is an example implementation (with differences betweent the first and second implementations highlighted in blue), where I am assuming METHOD_BUFFERED is not being used

    PMY_ELEMENT pList = NULL;

switch (IoctlCode) {
case IOCTL_GET_ELEMENTS:
// This will make sure that buffer length is at least sizeof(MY_ELEMENT).
// Anything less and we will return a warning and indicate the number
// of required bytes.
status = WdfRequestRetrieveOutputBuffer(Request, NULL, (PVOID*) &pList, &length);
if (!NT_SUCCESS(status)) {
// do nothing, error will propagate after the switch
}
else if (length == 0x0) {
status = STATUS_BUFFER_OVERFLOW;
information = [get number of elements]

}
else {
// check to make sure the buffer is an integral number of MY_ELEMENTs
// this will also handle the case of length < sizeof(MY_ELEMENT)
if ((length % sizeof(MY_ELEMENT)) != 0) {
status = STATUS_INVALID_PARAMETER;
}
else {
[copy elements into pList]

// compute how many bytes we copied over
information = [num elements copied]*sizeof(MY_ELEMENT);
status = STATUS_SUCCESS;
}
}
break;
}
...
WdfRequestCompleteWithInformation(Request, status, information);


And the calling code would look like

    DWORD bytesReturned;
PMY_ELEMENT pList;
BOOL result;

// This will return FALSE since the driver will return an NT_WARNING value!
result = DeviceIoControl(hDevice, IOCTL_GET_ELEMENTS, NULL, 0, NULL, 0, &bytesReturned, ...);
ASSERT(result == FALSE);

listSize = bytesReturned;
pList = (PMY_ELEMENT) LocalAlloc(LPTR, listSize);

DeviceIoControl(hDevice, IOCTL_GET_ELEMENTS, NULL, 0, pList, listSize, ...);

Comments (7)

  1. Note that the original code for the 2nd implementation changed to account for the I/O manager using IoStatus.Information as the number of bytes to copy if NT_WARNING is returned and there is an output buffer.

  2. TimJohns says:

    Hello Doron,

    I’ve been out of the Windows kernel device driver arena for a few years now, but old habits die hard.  I ran across your blog today, and I’d first like to comment that I think this is good information and thank you for providing it to the Windows device driver development community.

    Another old habit of mine that dies hard is being a bit of a troublemaker, so after I found it, I naturally had to analyze what you have here and throw in my own two cents (I used to poke hornets’ nests with a stick when I was a kid, too.  Never learned my lesson.  I still think that FOX wrote the Gregory House character in the series ‘House’ based on me, in a medical setting vs. developing software, but they won’t admit it).  Anyway, enough about me and on to the fun stuff.)

    I’d like to debate the premise of this blog entry a bit, and much more constructively, also propose a third alternative.  I’d personally argue that an API that behaves differently based on the format of the parameters (as opposed to the values) is probably (not every case) inappropriately overloaded.

    I assert this is the case to a small degree for RegQueryValueEx and IoGetDeviceProperty.  Among the virtues you’ve already mentioned, your second example doesn’t suffer from this same concern of mine – it’s good-to-go in that regard.  This particular debate probably belongs on a different blog (perhaps one dedicated to whining about subtle engineering design style differences), but at least it’s a good lead-in for the third approach I’d like to mention.

    The third alternative I’d like to propose (that eliminates the need for the above debate) is to use a separate DeviceIoControl dwIoControlCode value for each of the two distinct functions of query for length and query for list.

    This might actually be the ‘zeroeth alternative’ that is assumed to be an obvious pattern, but just in case it’s not, I’ll mention some of the merits and then describe it (and hopefully get the code close to correct typing it into this submission form…).

    This third (or zeroeth) approach has the added value of decreasing the number of possible branches in each of the two separate code paths by one, removes the need to add or subract the header size from the parameter, and most importantly, further decouples the driver implementation from that of the calling application.  Imagine what would happen in the second example at [copy elements into pList] if the application and driver were compiled with different struct member alignment options (compiler command line option /Zp[]).  This is a fairly common issue, particularly when the application is developed in a language other than Microsoft Visual C++, where the header file defining MY_ELEMENT_LIST could otherwise easily be surrounded by a set of #pragma pack() directives and shared between the driver and application.  Admittedly in my example below, the same issue applies to MY_ELEMENT, but the further decoupled, the better (although I just know some other lurking troublemaker could now propose a forth alternative whereby the application calls DeviceIoControl for each element of the list…)  But hey, whatever, more examples and the detailed background behind them are a GOOD THING.

    In any case, the calling code I have in mind becomes something like this (which looks very much like the first option you described, above):

    DeviceIoControl(hDevice, IOCTL_GET_NUM_ELEMENTS, NULL, 0, &numElements, sizeof(numElements), …);

       listSize = MY_ELEMENT_LIST_HEADER_SIZE + numElements * sizeof(MY_ELEMENT);

       pList = (PMY_ELEMENT_LIST) LocalAlloc(LPTR, listSize);

       DeviceIoControl(hDevice, IOCTL_GET_ELEMENT_LIST, NULL, 0, &pList->ElementList[0], numElements * sizeof(MY_ELEMENT), …);

    And the implementation in the driver becomes something like this (which differs somewhat from both of the above options):

    switch (IoctlCode) {

       case IOCTL_GET_NUM_ELEMENTS:

           //  This will make sure that buffer length is exactly sizeof(ULONG)

           status = WdfRequestRetrieveOutputBuffer(Request, sizeof(ULONG), (PVOID*) &pNumElements, &length);

           if (!NT_SUCCESS(status)) {

               // do nothing since buffer length is != sizeof(ULONG), we will break out and return error after the if() clause

           }

           else if (length != sizeof(ULONG) {

               *pNumElements = [get number of elements];

               information = 0x0;

           }

           break;

       case IOCTL_GET_ELEMENT_LIST:

           //  This will make sure that buffer length is an integer multiple of the element size

           status = WdfRequestRetrieveOutputBuffer(Request, sizeof(ULONG), (PVOID*) &pElementList, &length);

           if (!NT_SUCCESS(status)) {

               // do nothing since if buffer length is not an integral multiple of the element size, we will break out and return error after the if() clause

           }

               // check to make sure the buffer is an integral number of MY_ELEMENTs

               // this will also handle the case of length < sizeof(MY_ELEMENT)

               if ((length % sizeof(MY_ELEMENT)) != 0) {

                   status = STATUS_INVALID_PARAMETER;

               }

               else {

                   [copy elements into pList]

                   // compute how many bytes we copied over

                   information = [num elements copied]*sizeof(MY_ELEMENT);

                   status = STATUS_SUCCESS;

               }

           break;

       }

       …

       WdfRequestCompleteWithInformation(Request, status, information);

    One additional item I did notice that I wanted to question was this line in your second example:

    information = [num elements copied]*sizeof(MY_ELEMENT);

    Shouldn’t that be:

    information = length;

    or

    information = [num elements copied]*sizeof(MY_ELEMENT) + MY_ELEMENT_LIST_HEADER_SIZE;

    -Tim Johns

  3. TimJohns says:

    …hey I just thought of something else:

    Just a suggestion, but you could follow this blog entry with another describing how to call any of the three methods’ DeviceIoControl calls in a loop, to handle the case where the number of elements in the list changed between the first call and the second.  (sort of combines elements of this entry with your InterlockedXxxxxx entry).

  4. TimJohns says:

    …I gotta start THINKING before I press submit (either that or STOP thinking after I press it)….

    That’d also all be a good lead-in to another blog entry on a low-impact method (or a comparison of various methods) for synchronizing the contents of the list between the DeviceIoControl calls and whatever’s updating the list elsewhere in the driver.

    Also, I DID mess up the code above (quite badly) when I typed it into the form:

    else if (length != sizeof(ULONG) {

              *pNumElements = [get number of elements];

              information = 0x0;

          }

    should be more like:

    else if (length == sizeof(ULONG) {

              *pNumElements = [get number of elements];

              information = sizeof(ULONG);

              status = STATUS_SUCCESS;

          }

    … and I left out the else after:

    if (!NT_SUCCESS(status))

    {

    }

    Geez, OK, now I’m going home.  I promise not to enter another comment tonight.

  5. Thanks for reading.  Yes, the split IOCTLs (one for query for size, one to get thelist) with a single purpose is another alternative.  I considered adding it to the entry, but I thought naively that it was obvious.  My point here was actually to show how to return the number of required bytes in IoStatus.Information, the rest is just structure around that point.  

    The suggestions for the other entries are good ones.  I already had the synchronization topic in the queue, but having one on how to loop to make sure you get the correct value is a good idea.  You shall see it soon 😉

    d

  6. TimJohns says:

    I think it was obvious, and your point about using IoStatus.Information is indeed an informative and interesting one.  I just couldn’t help myself commenting on a blog entry (goes back to the whole poking hornets nests for fun thing).

    Couple more thoughts I had for which the answers might make interesting blog entries (or might be dead-ends/uninteresting):

    What else does the I/O manager do with IoStatus.Information?

    What other applications are there for NT_WARNING()?

    What applications and tools are there for using and manipulating the other bits in NTSTATUS values?

    Are there any interesting kernel-level things I can do with the message compiler in addition to what’s demonstrated in the DDK samples and documented in "Defining Custom Error Types"?

    -Tim

  7. most of these are one liners, so not much to write about

    > What else does the I/O manager do with IoStatus.Information

    I don’t know about FS land, but for i/o manager IRPs it will be the number of bytes to copy outside of NT_WARNING being returned.  for other irps, like some sent by the PnP manager, it contains a pointer

    > What applications and tools are there for using and manipulating the other bits in NTSTATUS values?

    Not much else is there, esp no tools.  NTSTATUS values typically fall into success of failure, warning and information (which is considered NT_SUCCESS()) are rarely used (again, the case may be different in FS land).

    > Are there any interesting kernel-level things I can do with the message compiler in addition to what’s demonstrated in the DDK samples and documented in "Defining Custom Error Types"?

    Nope.  You use it to embedd event log error messages and you can use it to define your own NTSTATUS values (KMDF does the latter, see wdfstatus.h)

    d

Skip to main content