WAFFLE: Local groups bug

The other day I was trying to create a new release build for Waffle, but one of the unit tests was failing. The test was testing the .Net version of the WindowsComputerImpl.Groups which returns the list of local groups. The test was failing because this Groups property was returning zero groups on my machine even though my computer has several local groups. I took a look at how Groups property was implemented and I found this.

int size = 1024;
IntPtr bufptr = new IntPtr(size);
int totalentries = 0;
do
{
    int rc = 0;
    int entriesread = 0;
    int resume_handle = 0;
    rc = Netapi32.NetLocalGroupEnum(
        _computerName,
        0,
        out bufptr,
        size - 1,
        out entriesread,
        out totalentries,
        ref resume_handle
    );

    switch (rc)
    {
        //If there is more data, double the size of the buffer...
        case Netapi32.NERR_BufTooSmall:
        case Windows.ERROR_MORE_DATA:
            size *= 2;
            bufptr = new IntPtr(size);
            resume_handle = 0;
            break;
        case Netapi32.NERR_Success:
            break;
        case Netapi32.NERR_InvalidComputer:
        default:
            throw new Win32Exception(rc);
    }
}
while (rc == Windows.ERROR_MORE_DATA);

When I ran this code, I kept getting 0 for the totalentries. I took a look at MSDN and discovered that the totalentries parameter only returns the approximate number of entries from the current resume position. It is not an accurate count of the entries.

I committed a change that uses the entriesread parameter as an accurate count of the number of entries instead of the totalentries parameter. This works, but there is an even better solution. Db looked at how the Java version of this code works. The Java version passes the MAX_PREFERRED_LENGTH as the size parameter. This causes NetLocalGroupEnum to allocate the required amount of memory. When the function is called the first time, it returns all groups and the totalentries and entriesread parameters are both set to the total number of local groups. The new code looks like this.

IntPtr bufptr = IntPtr.Zero;
try
{
    int rc = 0;
    int entriesread = 0;
    int totalentries = 0;
    int resume_handle = 0;
    rc = Netapi32.NetLocalGroupEnum(
        _computerName,
        0,
        out bufptr,
        LMCons.MAX_PREFERRED_LENGTH,
        out entriesread,
        out totalentries,
        ref resume_handle
    );

    if (rc != Netapi32.NERR_Success || bufptr == IntPtr.Zero)
    {
        throw new Win32Exception(rc);
    }

    string[] groups = new string[totalentries];
    IntPtr iter = bufptr;
    for (int i = 0; i < totalentries; i++)
    {
        Netapi32.LOCALGROUP_USERS_INFO_0 group = new Netapi32.LOCALGROUP_USERS_INFO_0();
        group = (Netapi32.LOCALGROUP_USERS_INFO_0)Marshal.PtrToStructure(iter, typeof(Netapi32.LOCALGROUP_USERS_INFO_0));
        groups[i] = string.Format(@"{0}\{1}", _computerName, group.name);
        iter = (IntPtr)((int)iter + Marshal.SizeOf(typeof(Netapi32.LOCALGROUP_USERS_INFO_0)));
    }
    return groups;
}
finally
{
    if (bufptr != IntPtr.Zero)
    {
        Netapi32.NetApiBufferFree(bufptr);
    }
}

Leave a Reply

Your email address will not be published. Required fields are marked *