#include "pch.h"
#pragma hdrstop

#include "ownerdlg.h"
#include "ownerlst.h"
#include "resource.h"
#include "uihelp.h"
#include "uiutils.h"

//
// Private message used to indicate that the owner list thread
// has completed it's work.  
//
const UINT PWM_OWNERLIST_COMPLETE = WM_USER + 1;
//
// Mask bits indicating what operations are allowed for a given
// file selection set in the listview.
//
const DWORD ACTION_NONE          = 0x00000000;
const DWORD ACTION_TAKEOWNERSHIP = 0x00000001;
const DWORD ACTION_MOVE          = 0x00000002;
const DWORD ACTION_DELETE        = 0x00000004;
const DWORD ACTION_ANY           = 0x00000007;


const static DWORD rgFileOwnerDialogHelpIDs[] =
{
    IDC_CMB_OWNERDLG_OWNERS,    IDH_CMB_OWNERDLG_OWNERS,
    IDC_LV_OWNERDLG,            IDH_LV_OWNERDLG,
    IDC_BTN_OWNERDLG_DELETE,    IDH_BTN_OWNERDLG_DELETE,
    IDC_BTN_OWNERDLG_MOVETO,    IDH_BTN_OWNERDLG_MOVETO,
    IDC_BTN_OWNERDLG_TAKE,      IDH_BTN_OWNERDLG_TAKE,
    IDC_BTN_OWNERDLG_BROWSE,    IDH_BTN_OWNERDLG_BROWSE,
    IDC_EDIT_OWNERDLG_MOVETO,   IDH_EDIT_OWNERDLG_MOVETO,
    0,0
};


CFileOwnerDialog::CFileOwnerDialog(HINSTANCE hInstance,
    HWND hwndParent,
    LPCTSTR pszVolumeRoot,
    const CArray<IDiskQuotaUser *>& rgpOwners
    ) : m_hInstance(hInstance),
        m_hwndParent(hwndParent),
        m_hwndDlg(NULL),
        m_hwndLV(NULL),
        m_hwndOwnerCombo(NULL),
        m_hwndEditMoveTo(NULL),
        m_iLastColSorted(-1),
        m_bSortAscending(true),
        m_bAbort(false),
        m_hOwnerListThread(NULL),
        m_rgpOwners(rgpOwners),
        m_strVolumeRoot(pszVolumeRoot)
{
}

CFileOwnerDialog::~CFileOwnerDialog(
    void
    )
{
    if (NULL != m_hOwnerListThread)
    {
        CloseHandle(m_hOwnerListThread);
    }
}


INT_PTR
CFileOwnerDialog::Run(
    void
    )
{
    DBGTRACE((DM_VIEW, DL_HIGH, TEXT("CFileOwnerDialog::Run")));
    return DialogBoxParam(m_hInstance,
                          MAKEINTRESOURCE(IDD_OWNERSANDFILES),
                          m_hwndParent,
                          DlgProc,
                          (LPARAM)this);
}


INT_PTR CALLBACK
CFileOwnerDialog::DlgProc(
    HWND hwnd,
    UINT uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
{
    //
    // Retrieve the dialog object's ptr from the window's userdata.
    // Place there in response to WM_INITDIALOG.
    //
    CFileOwnerDialog *pThis = (CFileOwnerDialog *)GetWindowLongPtr(hwnd, DWLP_USER);

    try
    {
        switch(uMsg)
        {
            case WM_INITDIALOG:
                //
                // Store "this" ptr in window's userdata.
                //
                SetWindowLongPtr(hwnd, DWLP_USER, (INT_PTR)lParam);
                pThis = (CFileOwnerDialog *)lParam;
                //
                // Save the HWND in our object.  We'll need it later.
                //
                pThis->m_hwndDlg = hwnd;
                return pThis->OnInitDialog(hwnd);

            case WM_DESTROY:
                return pThis->OnDestroy(hwnd);

            case WM_COMMAND:
                return pThis->OnCommand(hwnd, wParam, lParam);

            case WM_NOTIFY:
                return pThis->OnNotify(hwnd, wParam, lParam);

            case WM_CONTEXTMENU:
                return pThis->OnContextMenu((HWND)wParam, LOWORD(lParam), HIWORD(lParam));
                break;

            case PWM_OWNERLIST_COMPLETE:
                pThis->OnOwnerListComplete();
                break;

            case WM_SETCURSOR:
                return pThis->OnSetCursor(hwnd);
                break;

            case WM_HELP:
                WinHelp((HWND)((LPHELPINFO) lParam)->hItemHandle, STR_DSKQUOUI_HELPFILE,
                            HELP_WM_HELP, (DWORD_PTR)(LPTSTR) rgFileOwnerDialogHelpIDs);
                return TRUE;
        }
    }
    catch(CAllocException& me)
    {
        //
        // Announce any out-of-memory errors associated with running the dlg.
        //
        DiskQuotaMsgBox(GetDesktopWindow(),
                        IDS_OUTOFMEMORY,
                        IDS_TITLE_DISK_QUOTA,
                        MB_ICONERROR | MB_OK);
    }

    return FALSE;
}


INT_PTR
CFileOwnerDialog::OnInitDialog(
    HWND hwnd
    )
{
    DBGTRACE((DM_VIEW, DL_HIGH, TEXT("CFileOwnerDialog::OnInitDialog")));

    //
    // Save HWNDs of controls we'll need later.
    //
    m_hwndLV         = GetDlgItem(hwnd, IDC_LV_OWNERDLG);
    m_hwndOwnerCombo = GetDlgItem(hwnd, IDC_CMB_OWNERDLG_OWNERS);
    m_hwndEditMoveTo = GetDlgItem(hwnd, IDC_EDIT_OWNERDLG_MOVETO);
    //
    // We want these controls to be disabled until the owner list
    // has been populated.
    //
    EnableWindow(m_hwndLV, FALSE);
    EnableWindow(m_hwndOwnerCombo, FALSE);
    EnableWindow(m_hwndEditMoveTo, FALSE);
    EnableWindow(GetDlgItem(hwnd, IDC_BTN_OWNERDLG_BROWSE), FALSE);
    EnableWindow(GetDlgItem(hwnd, IDC_CBX_OWNERDLG_EXCLUDEDIRS), FALSE);
    EnableWindow(GetDlgItem(hwnd, IDC_CBX_OWNERDLG_EXCLUDEFILES), FALSE);
    //
    // Build the list of owners and filenames on the volume.
    // This can take a while depending on how many owners
    // are in m_rgpOwners, the size of the volume and how many
    // files each owner owns.  First clear the owner list in case Run()
    // is being called multiple times on the same dialog object.
    // Note that we build this list on a background thread so that
    // we don't block the UI while it's building.
    //
    m_OwnerList.Clear();
    DWORD idThread;
    m_hOwnerListThread = CreateThread(NULL,
                                      0,
                                      OwnerListThreadProc,
                                      this,
                                      0,
                                      &idThread);
    return TRUE;
}


INT_PTR
CFileOwnerDialog::OnSetCursor(
    HWND hwnd
    )
{
    if (m_hOwnerListThread && (WAIT_TIMEOUT == WaitForSingleObject(m_hOwnerListThread, 0)))
    {
        SetCursor(LoadCursor(NULL, IDC_WAIT));
        return TRUE;
    }
    return FALSE;
}


INT_PTR
CFileOwnerDialog::OnDestroy(
    HWND hwnd
    )
{
    m_bAbort = true;
    if (NULL != m_hOwnerListThread)
    {
        WaitForSingleObject(m_hOwnerListThread, INFINITE);
    }
    return TRUE;
}


DWORD
CFileOwnerDialog::OwnerListThreadProc(  // [static]
    LPVOID pvParam
    )
{
    CFileOwnerDialog *pThis = (CFileOwnerDialog *)pvParam;
    
    pThis->BuildFileOwnerList(pThis->m_strVolumeRoot,
                              pThis->m_rgpOwners,
                             &(pThis->m_OwnerList));

    PostMessage(pThis->m_hwndDlg, PWM_OWNERLIST_COMPLETE, 0, 0);
    return 0;
}

//
// Called in response to PWM_OWNERLIST_COMPLETE which is posted
// to the dialog when the OwnerListThreadProc has completed
// it's processing.
//
void
CFileOwnerDialog::OnOwnerListComplete(
    void
    )
{
    //
    // Set the message in the top of the dialog.
    //
    CString s(m_hInstance, IDS_FMT_OWNERDLG_HEADER, m_OwnerList.OwnerCount());
    SetWindowText(GetDlgItem(m_hwndDlg, IDC_TXT_OWNERDLG_HEADER), s);
    //
    // Populate the listview and owner combo.
    //
    InitializeList(m_OwnerList, m_hwndLV);
    InitializeOwnerCombo(m_OwnerList, m_hwndOwnerCombo);
    //
    // Now we can enable the controls we disabled in OnInitDialog().
    //
    EnableWindow(m_hwndLV, TRUE);
    EnableWindow(m_hwndOwnerCombo, TRUE);
    EnableWindow(m_hwndEditMoveTo, TRUE);
    EnableWindow(GetDlgItem(m_hwndDlg, IDC_BTN_OWNERDLG_BROWSE), TRUE);
    EnableWindow(GetDlgItem(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEDIRS), TRUE);
    EnableWindow(GetDlgItem(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEFILES), TRUE);

}



INT_PTR
CFileOwnerDialog::OnCommand(
    HWND hwnd,
    WPARAM wParam,
    LPARAM lParam
    )
{
    BOOL bResult     = TRUE; // Assume not handled.
    WORD wID         = LOWORD(wParam);
    WORD wNotifyCode = HIWORD(wParam);
    HWND hCtl        = (HWND)lParam;

    switch(wID)
    {
        case IDCANCEL:
            EndDialog(hwnd, 0);
            bResult = FALSE;
            break;

        case IDC_CMB_OWNERDLG_OWNERS:
            if (CBN_SELCHANGE == wNotifyCode)
            {
                int iOwner = ComboBox_GetCurSel(m_hwndOwnerCombo);
                if (1 < m_OwnerList.OwnerCount())
                {
                    //
                    // Owner list contains more than one owner.  The combo
                    // contains a leading "All owners" entry.
                    //
                    iOwner--;
                }

                DBGASSERT((-1 <= iOwner));
                CAutoSetRedraw autoredraw(m_hwndLV, false);
                //
                // Only show "owner" column if user has selected "all owners" combo item.
                //
                CreateListColumns(m_hwndLV, -1 == iOwner);
                FillListView(m_OwnerList, m_hwndLV, iOwner);
            }
            bResult = FALSE;
            break;

        case IDC_BTN_OWNERDLG_BROWSE:
        {
            CString s;
            if (BrowseForFolder(hwnd, &s))
                SetWindowText(m_hwndEditMoveTo, s);
            break;
        }

        case IDC_BTN_OWNERDLG_DELETE:
            DeleteSelectedFiles(m_hwndLV);
            bResult = FALSE;
            break;

        case IDC_BTN_OWNERDLG_MOVETO:
        {
            CPath strDest;
            CPath strRoot;
            int cchEdit = Edit_GetTextLength(m_hwndEditMoveTo);
            Edit_GetText(m_hwndEditMoveTo,
                         strDest.GetBuffer(cchEdit + 1),
                         cchEdit + 1);
            strDest.ReleaseBuffer();
            strDest.Trim();
            strDest.GetRoot(&strRoot);

            if (IsSameVolume(strRoot, m_strVolumeRoot))
            {
                //
                // Don't let operator move files to a folder
                // on the same volume.
                //
                DiskQuotaMsgBox(m_hwndDlg,
                                IDS_ERROR_MOVETO_SAMEVOL,
                                IDS_TITLE_DISK_QUOTA,
                                MB_ICONINFORMATION | MB_OK);

                SetWindowText(m_hwndEditMoveTo, strDest);
                SetFocus(m_hwndEditMoveTo);
            }
            else
            {
                //
                // Eveything looks OK.  Try to move the files.
                //
                MoveSelectedFiles(m_hwndLV, strDest);
            }
            bResult = FALSE;
            break;
        }

        case IDC_BTN_OWNERDLG_TAKE:
        {
            HRESULT hr = TakeOwnershipOfSelectedFiles(m_hwndLV);
            if (FAILED(hr))
            {
                DBGERROR((TEXT("TakeOwnershipOfSelectedFiles failed with hr = 0x%08X"), hr));
            }
            break;
        }

        case IDC_EDIT_OWNERDLG_MOVETO:
            if (EN_UPDATE == wNotifyCode)
            {
                //
                // Disable the "Move" button if the destination edit field
                // is blank.
                //
                HWND hwnd    = GetDlgItem(m_hwndDlg, IDC_BTN_OWNERDLG_MOVETO);
                bool bEnable = ShouldEnableControl(IDC_BTN_OWNERDLG_MOVETO);
                if (bEnable != boolify(IsWindowEnabled(hwnd)))
                {
                    EnableWindow(hwnd, bEnable);
                }
            }
            break;

        case IDC_CBX_OWNERDLG_EXCLUDEFILES:
        case IDC_CBX_OWNERDLG_EXCLUDEDIRS:
            if (BN_CLICKED == wNotifyCode)
            {
                //
                // The allowable states for these two checkboxes are:
                //
                //   Excl Files      Excl Dirs
                //   --------------- ----------------
                //   Checked         Unchecked
                //   Unchecked       Checked
                //   Unchecked       Unchecked
                //
                // It makes no sense to have both checkboxes checked. 
                // This would cause the list to be empty and might 
                // generate user confusion.
                //
                if (IsDlgButtonChecked(m_hwndDlg, wID))
                {
                    UINT idOther = IDC_CBX_OWNERDLG_EXCLUDEFILES;
                    if (IDC_CBX_OWNERDLG_EXCLUDEFILES == wID)
                    {
                        idOther = IDC_CBX_OWNERDLG_EXCLUDEDIRS;
                    }
                    CheckDlgButton(m_hwndDlg, idOther, BST_UNCHECKED);
                }
                FillListView(m_OwnerList, m_hwndLV, ComboBox_GetCurSel(m_hwndOwnerCombo) - 1);
            }
            break;
    }
    return bResult;
}


INT_PTR
CFileOwnerDialog::OnContextMenu(
    HWND hwndItem,
    int xPos,
    int yPos
    )
{
    int idCtl = GetDlgCtrlID(hwndItem);
    WinHelp(hwndItem,
            UseWindowsHelp(idCtl) ? NULL : STR_DSKQUOUI_HELPFILE,
            HELP_CONTEXTMENU,
            (DWORD_PTR)((LPTSTR)rgFileOwnerDialogHelpIDs));

    return FALSE;
}


//
// Determine what actions are allowed for the current selection.
//
DWORD 
CFileOwnerDialog::GetAllowedActions(
    HWND hwndLV
    )
{
    CArray<COwnerListItemHandle> rgItemHandles;

    BuildListOfSelectedFiles(hwndLV, NULL, &rgItemHandles);
    if (0 != rgItemHandles.Count())
    {
        const int cHandles = rgItemHandles.Count();
        for(int i = 0; i < cHandles; i++)
        {
            COwnerListItemHandle handle = rgItemHandles[i];
            int iOwner = handle.OwnerIndex();
            int iFile  = handle.FileIndex();
            if (m_OwnerList.IsFileDirectory(iOwner, iFile))
            {
                //
                // If any directory exists in the selection,
                // "take ownership" is the only allowed action.
                //
                return ACTION_TAKEOWNERSHIP;
            }
        }
    }
    return ACTION_ANY;
}


//
// Determine if one of the move/delete/take buttons should be enabled
// or disabled.
//
bool
CFileOwnerDialog::ShouldEnableControl(
    UINT idCtl
    )
{
    bool bEnable = true;
    int cLVSel = ListView_GetSelectedCount(m_hwndLV);
    DWORD actions = GetAllowedActions(m_hwndLV);
    
    switch(idCtl)
    {
        case IDC_BTN_OWNERDLG_DELETE:
            bEnable = (0 != (ACTION_DELETE & actions)) && (0 < cLVSel);
            break;
            
        case IDC_BTN_OWNERDLG_TAKE:
            bEnable = (0 != (ACTION_TAKEOWNERSHIP & actions)) && (0 < cLVSel);
            break;

        case IDC_BTN_OWNERDLG_MOVETO:
            bEnable = (0 != (ACTION_MOVE & actions));
            if (bEnable && 0 < cLVSel)
            {
                CPath s;
                int cch = Edit_GetTextLength(m_hwndEditMoveTo);
                Edit_GetText(m_hwndEditMoveTo, s.GetBuffer(cch + 1), cch + 1);
                s.ReleaseBuffer();
                s.Trim();
                bEnable = 0 < s.Length();
            }
            break;

        case IDC_BTN_OWNERDLG_BROWSE:
        case IDC_EDIT_OWNERDLG_MOVETO:
            bEnable = (0 != (ACTION_MOVE & actions));
            break;

        default:
            break;
    }
    return bEnable;
}


INT_PTR
CFileOwnerDialog::OnNotify(
    HWND hwnd,
    WPARAM wParam,
    LPARAM lParam
    )
{
    BOOL bResult = TRUE;
    LPNMHDR pnm  = (LPNMHDR)lParam;

    switch(pnm->code)
    {
        case LVN_GETDISPINFO:
            OnLVN_GetDispInfo((LV_DISPINFO *)lParam);
            break;

        case LVN_COLUMNCLICK:
            OnLVN_ColumnClick((NM_LISTVIEW *)lParam);
            break;

        case LVN_ITEMCHANGED:
            OnLVN_ItemChanged((NM_LISTVIEW *)lParam);
            break;

        case LVN_KEYDOWN:
            OnLVN_KeyDown((NMLVKEYDOWN *)lParam);
            break;

        default:
            break;
    }

    return bResult;
}


void
CFileOwnerDialog::OnLVN_GetDispInfo(
    LV_DISPINFO *plvdi
    )
{
    static CPath strPath;
    static CString strOwner;

    COwnerListItemHandle hItem(plvdi->item.lParam);
    int iOwner = hItem.OwnerIndex();
    int iFile  = hItem.FileIndex();

    if (LVIF_TEXT & plvdi->item.mask)
    {
        switch(plvdi->item.iSubItem)
        {
            case iLVSUBITEM_FILE:
            {
                CPath s;
                m_OwnerList.GetFileName(iOwner, iFile, &s);
                if (m_OwnerList.IsFileDirectory(iOwner, iFile))
                {
                    strPath.Format(m_hInstance, IDS_FMT_OWNERDLG_FOLDERNAME, s.Cstr());
                }
                else
                {
                    strPath = s;
                }
                plvdi->item.pszText = (LPTSTR)strPath.Cstr();
            }
            break;

            case iLVSUBITEM_FOLDER:
                m_OwnerList.GetFolderName(iOwner, iFile, &strPath);
                plvdi->item.pszText = (LPTSTR)strPath.Cstr();
                break;

            case iLVSUBITEM_OWNER:
                m_OwnerList.GetOwnerName(iOwner, &strOwner);
                plvdi->item.pszText = (LPTSTR)strOwner.Cstr();
                break;
        }
    }

    if (LVIF_IMAGE & plvdi->item.mask)
    {
        //
        // Not displaying any images.  This is just a placeholder.
        // Should be optimized out by compiler.
        //
    }
}


int CALLBACK
CFileOwnerDialog::CompareLVItems(
    LPARAM lParam1,
    LPARAM lParam2,
    LPARAM lParamSort
    )
{
    CFileOwnerDialog *pdlg = reinterpret_cast<CFileOwnerDialog *>(lParamSort);
    int diff = 0;
    try
    {
        COwnerListItemHandle h1(lParam1);
        COwnerListItemHandle h2(lParam2);
        int iOwner1 = h1.OwnerIndex();
        int iOwner2 = h2.OwnerIndex();
        int iFile1  = h1.FileIndex();
        int iFile2  = h2.FileIndex();
        static CPath s1, s2;

        //
        // This array controls the comparison column IDs used when
        // values for the selected column are equal.  These should
        // remain in order of the iLVSUBITEM_xxxxx enumeration with
        // respect to the first element in each row.
        //
        static const int rgColComp[3][3] = {
            { iLVSUBITEM_FILE,   iLVSUBITEM_FOLDER, iLVSUBITEM_OWNER  },
            { iLVSUBITEM_FOLDER, iLVSUBITEM_FILE,   iLVSUBITEM_OWNER  },
            { iLVSUBITEM_OWNER,  iLVSUBITEM_FILE,   iLVSUBITEM_FOLDER }
                                           };
        int iCompare = 0;
        while(0 == diff && iCompare < ARRAYSIZE(rgColComp))
        {
            switch(rgColComp[pdlg->m_iLastColSorted][iCompare++])
            {
                case iLVSUBITEM_FILE:
                    pdlg->m_OwnerList.GetFileName(iOwner1, iFile1, &s1);
                    pdlg->m_OwnerList.GetFileName(iOwner2, iFile2, &s2);
                    break;

                case iLVSUBITEM_FOLDER:
                    pdlg->m_OwnerList.GetFolderName(iOwner1, iFile1, &s1);
                    pdlg->m_OwnerList.GetFolderName(iOwner2, iFile2, &s2);
                    break;

                case iLVSUBITEM_OWNER:
                    //
                    // Can use CPath (s1 and s2) in place of CString arg since
                    // CPath is derived from CString.
                    //
                    pdlg->m_OwnerList.GetOwnerName(iOwner1, &s1);
                    pdlg->m_OwnerList.GetOwnerName(iOwner2, &s2);
                    break;

                default:
                    //
                    // If you hit this, you need to update this function
                    // to handle the new column you've added to the listview.
                    //
                    DBGASSERT((false));
                    break;
            }
            diff = s1.Compare(s2);
        }
        //
        // Don't need contents of static strings between function invocations.
        // The strings are static to avoid repeated construction/destruction.
        // It's only a minor optimization.
        //
        s1.Empty();
        s2.Empty();
    }
    catch(CAllocException& e)
    {
        //
        // Do nothing.  Just return diff "as is".
        // Don't want to throw an exception back into comctl32.
        //
    }
    return pdlg->m_bSortAscending ? diff : -1 * diff;
}


void
CFileOwnerDialog::OnLVN_ColumnClick(
    NM_LISTVIEW *pnmlv
    )
{
    DBGTRACE((DM_VIEW, DL_LOW, TEXT("CFileOwnerDialog::OnLVN_ColumnClick")));

    if (m_iLastColSorted != pnmlv->iSubItem)
    {
        m_bSortAscending = true;
        m_iLastColSorted = pnmlv->iSubItem;
    }
    else
    {
        m_bSortAscending = !m_bSortAscending;
    }

    ListView_SortItems(m_hwndLV, CompareLVItems, LPARAM(this));
}


//
// Called whenever a listview item has changed state.
// I'm using this to update the "enabledness" of the
// dialog buttons.  If there's nothing selected in the listview,
// the move/delete/take buttons are disabled.
//
void
CFileOwnerDialog::OnLVN_ItemChanged(
    NM_LISTVIEW *pnmlv
    )
{
    static const int rgCtls[] = { IDC_BTN_OWNERDLG_DELETE,
                                  IDC_BTN_OWNERDLG_TAKE,
                                  IDC_BTN_OWNERDLG_MOVETO,
                                  IDC_BTN_OWNERDLG_BROWSE,
                                  IDC_EDIT_OWNERDLG_MOVETO};

    //
    // LVN_ITEMCHANGED is sent multiple times when you move the
    // highlight bar in a listview.
    // Only run this code when the "focused" state bit is set
    // for the "new state".  This should be the last call in
    // the series.
    //
    if (LVIS_FOCUSED & pnmlv->uNewState)
    {
        for (int i = 0; i < ARRAYSIZE(rgCtls); i++)
        {
            HWND hwnd    = GetDlgItem(m_hwndDlg, rgCtls[i]);
            bool bEnable = ShouldEnableControl(rgCtls[i]);
            if (bEnable != boolify(IsWindowEnabled(hwnd)))
            {
                EnableWindow(hwnd, bEnable);
            }
        }
    }
}


void
CFileOwnerDialog::OnLVN_KeyDown(
    NMLVKEYDOWN *plvkd
    )
{
    if (VK_DELETE == plvkd->wVKey)
    {
        DeleteSelectedFiles(m_hwndLV);
        FocusOnSomethingInListview(m_hwndLV);
    }
}



void
CFileOwnerDialog::FocusOnSomethingInListview(
    HWND hwndLV
    )
{
    //
    // Focus on something.
    //
    int iFocus = ListView_GetNextItem(hwndLV, -1, LVNI_FOCUSED);
    if (-1 == iFocus)
        iFocus = 0;

    ListView_SetItemState(hwndLV, iFocus, LVIS_FOCUSED | LVIS_SELECTED,
                                          LVIS_FOCUSED | LVIS_SELECTED);
}


//
// Creates the listview columns and populates the listview
// with filenames.
//
void
CFileOwnerDialog::InitializeList(
    const COwnerList& fol,  // file & owner list
    HWND hwndList
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::InitializeList")));

    CreateListColumns(hwndList, 1 < m_OwnerList.OwnerCount());
    FillListView(fol, hwndList);
    ListView_SetExtendedListViewStyle(hwndList, LVS_EX_FULLROWSELECT);
}


void
CFileOwnerDialog::CreateListColumns(
    HWND hwndList,
    bool bShowOwner    // Default is true.
    )
{
    //
    // Clear out the listview and header.
    //
    ListView_DeleteAllItems(hwndList);
    HWND hwndHeader = ListView_GetHeader(hwndList);
    if (NULL != hwndHeader)
    {
        while(0 < Header_GetItemCount(hwndHeader))
            ListView_DeleteColumn(hwndList, 0);
    }

    //
    // Create the header titles.
    //
    CString strFile(m_hInstance,   IDS_OWNERDLG_HDR_FILE);
    CString strFolder(m_hInstance, IDS_OWNERDLG_HDR_FOLDER);
    CString strOwner(m_hInstance,  IDS_OWNERDLG_HDR_OWNER);

    //
    // FEATURE:  Should probably allow for vertical scroll bar also.
    //
    RECT rcList;
    GetClientRect(hwndList, &rcList);
    int cxCol = (rcList.right - rcList.left) / (bShowOwner ? 3 : 2);

#define LVCOLMASK (LVCF_FMT | LVCF_TEXT | LVCF_WIDTH | LVCF_SUBITEM)

    LV_COLUMN rgCols[] = {
         { LVCOLMASK, LVCFMT_LEFT, cxCol, strFile,   0, iLVSUBITEM_FILE   },
         { LVCOLMASK, LVCFMT_LEFT, cxCol, strFolder, 0, iLVSUBITEM_FOLDER },
         { LVCOLMASK, LVCFMT_LEFT, cxCol, strOwner,  0, iLVSUBITEM_OWNER  }
                         };
    //
    // Add the columns to the listview.
    //
    int cCols = bShowOwner ? ARRAYSIZE(rgCols) : ARRAYSIZE(rgCols) - 1;
    for (INT i = 0; i < cCols; i++)
    {
        if (-1 == ListView_InsertColumn(hwndList, i, &rgCols[i]))
        {
            DBGERROR((TEXT("CFileOwnerDialog::CreateListColumns failed to add column %d"), i));
        }
    }
}


void
CFileOwnerDialog::FillListView(
    const COwnerList& fol,  // file & owner list
    HWND hwndList,
    int iOwner              // default is -1 (all owners)
    )
{
    ListView_DeleteAllItems(hwndList);

    LV_ITEM item;
    item.iSubItem  = 0;
    item.mask      = LVIF_TEXT | LVIF_STATE | LVIF_IMAGE | LVIF_PARAM;
    item.state     = 0;
    item.stateMask = 0;
    item.pszText   = LPSTR_TEXTCALLBACK;
    item.iImage    = I_IMAGECALLBACK;

    int iFirst = iOwner;
    int iLast  = iOwner;
    if (-1 == iOwner)
    {
        iFirst = 0;
        iLast  = fol.OwnerCount() - 1;
    }
    int iItem = 0;
    const bool bExclFiles = IsDlgButtonChecked(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEFILES);
    const bool bExclDirs  = IsDlgButtonChecked(m_hwndDlg, IDC_CBX_OWNERDLG_EXCLUDEDIRS);
    //
    // WARNING:  Reusing formal arg iOwner.  It's safe to do, but you
    //           should be aware that I'm doing it.
    //
    for (iOwner = iFirst; iOwner <= iLast; iOwner++)
    {
        int cFiles = fol.FileCount(iOwner, true);
        for (int iFile = 0; iFile < cFiles; iFile++)
        {
            bool bDirectory = fol.IsFileDirectory(iOwner, iFile);
            bool bFile      = !bDirectory;

            if ((bDirectory && !bExclDirs) || (bFile && !bExclFiles))
            {
                if (!fol.IsFileDeleted(iOwner, iFile))
                {
                    item.lParam = COwnerListItemHandle(iOwner, iFile);
                    item.iItem  = iItem++;
                    if (-1 == ListView_InsertItem(hwndList, &item))
                        DBGERROR((TEXT("Error adding LV item for owner %d, file %d"), iOwner, iFile));
                }
            }
        }
    }
}


void
CFileOwnerDialog::InitializeOwnerCombo(
    const COwnerList& fol,  // file & owner list
    HWND hwndCombo
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::InitializeList")));

    int iSelected = ComboBox_GetCurSel(hwndCombo);
    ComboBox_ResetContent(hwndCombo);

    CString s, s2;
    int cOwners = fol.OwnerCount();
    if (1 < cOwners)
    {
        //
        // Add "all owners" entry.
        //
        s.Format(m_hInstance, IDS_FMT_ALLOWNERS, fol.FileCount());
        ComboBox_InsertString(hwndCombo, -1, s);
    }

    for (int iOwner = 0; iOwner < cOwners; iOwner++)
    {
        fol.GetOwnerName(iOwner, &s2);
        s.Format(m_hInstance, IDS_FMT_OWNER, s2.Cstr(), fol.FileCount(iOwner));
        ComboBox_InsertString(hwndCombo, -1, s);
    }

    ComboBox_SetCurSel(hwndCombo, CB_ERR != iSelected ? iSelected : 0);

    //
    // Set the max height of the owner combo
    //
    RECT rcCombo;
    GetClientRect(m_hwndOwnerCombo, &rcCombo);
    SetWindowPos(m_hwndOwnerCombo,
                 NULL,
                 0, 0,
                 rcCombo.right - rcCombo.left,
                 200,
                 SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE);
}


//
// Determine if two volume root strings refer to the same volume.
// With volume mount points, "C:\" and "D:\DriveC" could refer to the
// same physical volume.  To differentiate we need to examine the unique
// volume name GUID strings.
//
bool
CFileOwnerDialog::IsSameVolume(
    LPCTSTR pszRoot1,
    LPCTSTR pszRoot2
    )
{
    TCHAR szVolGUID1[MAX_PATH];
    TCHAR szTemp[MAX_PATH];
    bool bSameVolume = false;

    //
    // GetVolumeNameForVolumeMountPoint requires trailing backslash on paths.
    //
    lstrcpyn(szTemp, pszRoot1, ARRAYSIZE(szTemp));
    PathAddBackslash(szTemp);
    
    if (GetVolumeNameForVolumeMountPoint(szTemp, szVolGUID1, ARRAYSIZE(szVolGUID1)))
    {
        TCHAR szVolGUID2[MAX_PATH];
        lstrcpyn(szTemp, pszRoot2, ARRAYSIZE(szTemp));
        PathAddBackslash(szTemp);

        if (GetVolumeNameForVolumeMountPoint(szTemp, szVolGUID2, ARRAYSIZE(szVolGUID2)))
        {
            if (0 == lstrcmpi(szVolGUID1, szVolGUID2))
                bSameVolume = true;
        }
    }
    return bSameVolume;
}

//
// Let the user browse for a folder.
// The selected folder path is returned in *pstrFolder.
//
bool
CFileOwnerDialog::BrowseForFolder(
    HWND hwndParent,
    CString *pstrFolder
    )
{
    bool bResult = false;
    BROWSEINFO bi;
    ZeroMemory(&bi, sizeof(bi));

    CString strTitle(m_hInstance, IDS_BROWSEFORFOLDER);

    bi.hwndOwner      = hwndParent;
    bi.pidlRoot       = NULL;       // Start at desktop.
    bi.pszDisplayName = NULL;
    bi.lpszTitle      = strTitle.Cstr();
    //
    // FEATURE:  Setting the BIF_EDITBOX flag causes SHBrowseForFolder to invoke
    //          autocomplete through SHAutoComplete (in shlwapi).  SHAutoComplete
    //          loads browseui.dll to implement the autocomplete feature.  The bad
    //          part is that SHAutoComplete also unloads browseui.dll before it
    //          returns, resulting in calls to the unloaded WndProc.  I've notified
    //          ReinerF about this.  Turning off the BIF_EDITBOX bit prevents
    //          autocomplete from being used and thus prevents the problem.
    //          I want the edit box.  Turn it back on once they fix this bug.
    //
    //          brianau [1/30/97]
    //
    bi.ulFlags        = BIF_RETURNONLYFSDIRS; // | BIF_EDITBOX;
    bi.lpfn           = BrowseForFolderCallback;
    bi.lParam         = (LPARAM)pstrFolder;
    bi.iImage         = 0;

    bResult = boolify(SHBrowseForFolder(&bi));
    return bResult;
}


//
// Callback called by SHBrowseForFolder.  Writes selected folder path
// to CString object who's pointer is passed in lpData arg.
//
int
CFileOwnerDialog::BrowseForFolderCallback(
    HWND hwnd,
    UINT uMsg,
    LPARAM lParam,
    LPARAM lpData
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::BrowseForFolderCallback")));
    CString *pstrFolder = (CString *)lpData;

    if (BFFM_SELCHANGED == uMsg)
    {
        SHGetPathFromIDList((LPCITEMIDLIST)lParam, pstrFolder->GetBuffer(MAX_PATH));
        pstrFolder->ReleaseBuffer();
    }
    return 0;
}


//
// Builds a double-nul terminated list of file paths from the listview
// along with an array of "item handle" objects that acts as a cross-
// reference between the list items, items in the listview and items
// in the file owner list.  Each handle contains an owner index and
// file index into the file owner list.  Each handle is also the value
// stored as the lParam in the listview items.
// Both pList and prgItemHandles arguments are optional.  Although,
// calling with neither non-null is sort of useless.
//
void
CFileOwnerDialog::BuildListOfSelectedFiles(
    HWND hwndLV,
    DblNulTermList *pList,
    CArray<COwnerListItemHandle> *prgItemHandles
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::BuildListOfSelectedFiles")));
    int iItem = -1;
    CPath strPath;
    LV_ITEM item;

    if (NULL != prgItemHandles)
        prgItemHandles->Clear();
    while(-1 != (iItem = ListView_GetNextItem(hwndLV, iItem, LVNI_SELECTED)))
    {
        item.iSubItem = 0;
        item.iItem    = iItem;
        item.mask     = LVIF_PARAM;
        if (-1 != ListView_GetItem(hwndLV, &item))
        {
            COwnerListItemHandle hItem(item.lParam);
            m_OwnerList.GetFileFullPath(hItem.OwnerIndex(),
                                        hItem.FileIndex(),
                                        &strPath);
            if (pList)
                pList->AddString(strPath);
            if (prgItemHandles)
                prgItemHandles->Append(hItem);
        }
    }
}



//
// Given an item "handle", find it's entry in the listview.
//
int
CFileOwnerDialog::FindItemFromHandle(
    HWND hwndLV,
    const COwnerListItemHandle& handle
    )
{
    LV_FINDINFO lvfi;
    lvfi.flags  = LVFI_PARAM;
    lvfi.lParam = handle;
    return ListView_FindItem(hwndLV, -1, &lvfi);
}


//
// Scans an array of item handles and removes all corresponding
// items from the listview.
//
void
CFileOwnerDialog::RemoveListViewItems(
    HWND hwndLV,
    const CArray<COwnerListItemHandle>& rgItemHandles
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::RemoveListViewItems")));
    LV_ITEM item;
    CPath strPath;

    CAutoSetRedraw autoredraw(hwndLV, false);
    int cHandles = rgItemHandles.Count();
    for (int iHandle = 0; iHandle < cHandles; iHandle++)
    {
        COwnerListItemHandle handle = rgItemHandles[iHandle];
        int iItem = FindItemFromHandle(hwndLV, handle);
        if (-1 != iItem)
        {
            int iOwner = handle.OwnerIndex();
            int iFile  = handle.FileIndex();
            m_OwnerList.GetFileFullPath(iOwner, iFile, &strPath);

            if ((DWORD)-1 == GetFileAttributes(strPath))
            {
                //
                // File doesn't exist any more.
                // Delete from the listview.
                // Mark it as "deleted" in the ownerlist container.
                //
                ListView_DeleteItem(hwndLV, iItem);
                m_OwnerList.MarkFileDeleted(iOwner, iFile);
                DBGPRINT((DM_VIEW, DL_LOW, TEXT("Removed item %d \"%s\""),
                         iItem, strPath.Cstr()));
            }
        }
    }
    //
    // Refresh the owner combo to update the file counts.
    //
    InitializeOwnerCombo(m_OwnerList, m_hwndOwnerCombo);
}


//
// Delete the files selected in the listview.
// Files deleted are removed from the listview.
//
void
CFileOwnerDialog::DeleteSelectedFiles(
    HWND hwndLV
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::DeleteSelectedFiles")));
    DblNulTermList list(1024);  // 1024 is the buffer growth size in chars.
    CArray<COwnerListItemHandle> rgItemHandles;

    BuildListOfSelectedFiles(hwndLV, &list, &rgItemHandles);
    if (0 < list.Count())
    {
        SHFILEOPSTRUCT fo;
        fo.hwnd   = m_hwndDlg;
        fo.wFunc  = FO_DELETE;
        fo.pFrom  = list;
        fo.pTo    = NULL;
        fo.fFlags = 0;

        if (0 != SHFileOperation(&fo))
        {
            DBGERROR((TEXT("SHFileOperation [FO_DELETE] failed")));
        }
        //
        // Remove listview items if their files were really deleted.
        //
        RemoveListViewItems(hwndLV, rgItemHandles);
    }
}


//
// Move the selected files to a new location.
// Moved files are removed from the listview.
//
void
CFileOwnerDialog::MoveSelectedFiles(
    HWND hwndLV,
    LPCTSTR pszDest
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::DeleteSelectedFiles")));
    DblNulTermList list(1024);  // 1024 is the buffer growth size in chars.
    CArray<COwnerListItemHandle> rgItemHandles;

    BuildListOfSelectedFiles(hwndLV, &list, &rgItemHandles);
    if (0 < list.Count())
    {
        CPath strDest(pszDest);
        if (1 == list.Count())
        {
            //
            // If we have only a single file we MUST create a fully-qualified
            // path to the destination file.  Oddities in the shell's move/copy
            // engine won't let us pass merely a destination folder in the
            // case where that folder doesn't exist.  If we give the full path
            // including filename we'll get the "folder doesn't exist, create
            // now?" messagebox as we would expect.  If we're moving multiple
            // files the shell accepts a single directory path.
            //
            LPCTSTR psz;
            DblNulTermListIter iter(list);
            if (iter.Next(&psz))
            {
                CPath strSrc(psz);           // Copy the source
                CPath strFile;               
                strSrc.GetFileSpec(&strFile);// Extract the filename.
                strDest.Append(strFile);     // Append to the dest path.
            }
        }
            
        SHFILEOPSTRUCT fo;
        fo.hwnd   = m_hwndDlg;
        fo.wFunc  = FO_MOVE;
        fo.pFrom  = list;
        fo.pTo    = strDest;
        fo.fFlags = FOF_RENAMEONCOLLISION;

        if (0 != SHFileOperation(&fo))
        {
            DBGERROR((TEXT("SHFileOperation [FO_MOVE] failed")));
        }
        //
        // Remove listview items if their file was really deleted.
        //
        RemoveListViewItems(hwndLV, rgItemHandles);
    }
}


//
// Get the SID to use for taking ownership of files.
// First try to get the first group SID with the SE_GROUP_OWNER attribute.
// If none found, use the operator's account SID.  The SID is in a
// dynamic buffer attached to the ptrSid autoptr argument.
//
HRESULT
CFileOwnerDialog::GetOwnershipSid(
    array_autoptr<BYTE> *ptrSid
    )
{
    HRESULT hr  = E_FAIL;
    DWORD dwErr = 0;

    //
    // Get the token handle. First try the thread token then the process
    // token.  If these fail we return early.  No sense in continuing
    // on if we can't get a user token.
    //
    CWin32Handle hToken;
    if (!OpenThreadToken(GetCurrentThread(),
                         TOKEN_READ,
                         TRUE,
                         hToken.HandlePtr()))
    {
        if (ERROR_NO_TOKEN == GetLastError())
        {
            if (!OpenProcessToken(GetCurrentProcess(),
                                  TOKEN_READ,
                                  hToken.HandlePtr()))
            {
                dwErr = GetLastError();
                DBGERROR((TEXT("Error %d opening process token"), dwErr));
                return HRESULT_FROM_WIN32(dwErr);
            }
        }
        else
        {
            dwErr = GetLastError();
            DBGERROR((TEXT("Error %d opening thread token"), dwErr));
            return HRESULT_FROM_WIN32(dwErr);
        }
    }

    //
    // Get the required size of the group token information buffer.
    //
    array_autoptr<BYTE> ptrTokenInfo;
    DWORD cbTokenInfo = 0;

    if (!GetTokenInformation(hToken,
                             TokenGroups,
                             NULL,
                             cbTokenInfo,
                             &cbTokenInfo))
    {
        dwErr = GetLastError();
        if (ERROR_INSUFFICIENT_BUFFER == dwErr)
        {
            ptrTokenInfo = new BYTE[cbTokenInfo];
        }
        else
        {
            dwErr = GetLastError();
            DBGERROR((TEXT("Error %d getting TokenGroups info [for size]"), dwErr));
            hr = HRESULT_FROM_WIN32(dwErr);
        }
    }

    //
    // Get the group token information.
    //
    if (NULL != ptrTokenInfo.get())
    {
        if (!GetTokenInformation(hToken,
                                 TokenGroups,
                                 ptrTokenInfo.get(),
                                 cbTokenInfo,
                                 &cbTokenInfo))
        {
            dwErr = GetLastError();
            DBGERROR((TEXT("Error %d getting TokenGroups info"), dwErr));
            hr = HRESULT_FROM_WIN32(dwErr);
        }
        else
        {
            //
            // Extract the first SID with the GROUP_OWNER bit set.
            //
            TOKEN_GROUPS *ptg = (TOKEN_GROUPS *)ptrTokenInfo.get();
            DBGASSERT((NULL != ptg));
            for (DWORD i = 0; i < ptg->GroupCount; i++)
            {
                SID_AND_ATTRIBUTES *psa = (SID_AND_ATTRIBUTES *)&ptg->Groups[i];
                DBGASSERT((NULL != psa));
                if (SE_GROUP_OWNER & psa->Attributes)
                {
                    int cbSid = GetLengthSid(psa->Sid);
                    *ptrSid = new BYTE[cbSid];
                    CopySid(cbSid, ptrSid->get(), psa->Sid);
                    hr = NOERROR;
                    break;
                }
            }
        }
    }

    if (FAILED(hr))
    {
        //
        // Didn't find a SID from the group information.
        // Use the operator's SID.
        //
        cbTokenInfo = 0;
        if (!GetTokenInformation(hToken,
                                 TokenUser,
                                 NULL,
                                 cbTokenInfo,
                                 &cbTokenInfo))
        {
            dwErr = GetLastError();
            if (ERROR_INSUFFICIENT_BUFFER == dwErr)
            {
                ptrTokenInfo = new BYTE[cbTokenInfo];
            }
            else
            {
                DBGERROR((TEXT("Error %d getting TokenUser info [for size]"), dwErr));
                hr = HRESULT_FROM_WIN32(dwErr);
            }
        }

        if (SUCCEEDED(hr))
        {
            //
            // Get the user token information.
            //
            if (!GetTokenInformation(hToken,
                                     TokenUser,
                                     ptrTokenInfo.get(),
                                     cbTokenInfo,
                                     &cbTokenInfo))
            {
                dwErr = GetLastError();
                DBGERROR((TEXT("Error %d getting TokenUser info"), dwErr));
                hr = HRESULT_FROM_WIN32(dwErr);
            }
            else
            {
                SID_AND_ATTRIBUTES *psa = (SID_AND_ATTRIBUTES *)ptrTokenInfo.get();
                DBGASSERT((NULL != psa));
                int cbSid = GetLengthSid(psa->Sid);
                *ptrSid = new BYTE[cbSid];
                CopySid(cbSid, ptrSid->get(), psa->Sid);
                hr = NOERROR;
            }
        }
    }
    if (SUCCEEDED(hr) && NULL != ptrSid->get() && !IsValidSid(ptrSid->get()))
    {
        hr = HRESULT_FROM_WIN32(ERROR_INVALID_SID);
    }
    return hr;
}


//
// Transfers ownership of selected files in the listview to the
// currently logged-on user.
//
HRESULT
CFileOwnerDialog::TakeOwnershipOfSelectedFiles(
    HWND hwndLV
    )
{
    HRESULT hr = NOERROR;
    DWORD dwErr = 0;
    CArray<COwnerListItemHandle> rgItemHandles;
    
    BuildListOfSelectedFiles(hwndLV, NULL, &rgItemHandles);
    if (0 == rgItemHandles.Count())
        return S_OK;

    array_autoptr<BYTE> ptrSid;
    hr = GetOwnershipSid(&ptrSid);
    if (FAILED(hr))
        return hr;

    CPath strFile;
    int cHandles = rgItemHandles.Count();
    for (int i = 0; i < cHandles; i++)
    {
        COwnerListItemHandle handle = rgItemHandles[i];
        int iItem = FindItemFromHandle(hwndLV, handle);
        if (-1 != iItem)
        {
            SECURITY_DESCRIPTOR sd;
            if (InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION))
            {
                int iOwner = handle.OwnerIndex();
                int iFile  = handle.FileIndex();
                m_OwnerList.GetFileFullPath(iOwner, iFile, &strFile);
                if (SetSecurityDescriptorOwner(&sd, ptrSid.get(), FALSE))
                {
                    if (SetFileSecurity(strFile, OWNER_SECURITY_INFORMATION, &sd))
                    {
                        ListView_DeleteItem(hwndLV, iItem);
                        m_OwnerList.MarkFileDeleted(iOwner, iFile);
                    }
                    else
                    {
                        dwErr = GetLastError();
                        DBGERROR((TEXT("Error %d setting new owner for \"%s\""),
                                 dwErr, strFile.Cstr()));
                        hr = HRESULT_FROM_WIN32(dwErr);
                    }
                }
                else
                {
                    dwErr = GetLastError();
                    DBGERROR((TEXT("Error %d setting security descriptor owner"), dwErr));
                    hr = HRESULT_FROM_WIN32(dwErr);
                }
            }
            else
            {
                dwErr = GetLastError();
                DBGERROR((TEXT("Error %d initing security descriptor"), GetLastError()));
                hr = HRESULT_FROM_WIN32(dwErr);
            }
        }
        else
        {
            DBGERROR((TEXT("Can't find listview item for owner %d, file %d"),
                     handle.OwnerIndex(), handle.FileIndex()));
        }
    }
    //
    // Refresh the owner combo with the new file counts.
    //
    InitializeOwnerCombo(m_OwnerList, m_hwndOwnerCombo);
    return hr;
}



//
// The original code for listing files owned by a user was
// contributed by MarkZ.  I made some minor modifications
// to fit it into the diskquota project and make it more
// exception safe.
//
inline VOID *
Add2Ptr(VOID *pv, ULONG cb)
{
    return((BYTE *) pv + cb);
}

inline ULONG
QuadAlign( ULONG Value )
{
    return (Value + 7) & ~7;
}


//
// Add files owned by a particular user on a particular volume.
//
HRESULT
CFileOwnerDialog::AddFilesToOwnerList(
    LPCTSTR pszVolumeRoot,
    HANDLE hVolumeRoot,
    IDiskQuotaUser *pOwner,
    COwnerList *pOwnerList
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::AddFilesToOwnerList")));
    DBGASSERT((NULL != hVolumeRoot));
    DBGASSERT((NULL != pOwner));
    DBGASSERT((NULL != pOwnerList));

    struct
    {
        ULONG Restart;
        BYTE Sid[MAX_SID_LEN];
    }FsCtlInput;

    NTSTATUS status = ERROR_SUCCESS;

    if (m_bAbort)
    {
        return S_OK;
    }

    //
    // Get owner's SID.
    //
    HRESULT hr = pOwner->GetSid(FsCtlInput.Sid, sizeof(FsCtlInput.Sid));
    if (FAILED(hr))
    {
        DBGERROR((TEXT("IDiskQuotaUser::GetSid failed with hr = 0x%08X"), hr));
        return hr;
    }

    //
    // Add the owner to the owner-file list.
    //
    int iOwner = pOwnerList->AddOwner(pOwner);

    IO_STATUS_BLOCK iosb;
    FsCtlInput.Restart = 1;
    BYTE Output[1024];
    bool bPathIsRemote = false;
    FILE_FS_DEVICE_INFORMATION DeviceInfo;

    //
    // Determine if the volume is a remote device.  This will affect
    // our handling of the paths returned by NtQueryInformationFile.
    //
    status = NtQueryVolumeInformationFile(
                    hVolumeRoot,
                    &iosb,
                    &DeviceInfo,
                    sizeof(DeviceInfo),
                    FileFsDeviceInformation);
                    
    if (NT_SUCCESS(status))
    {
        bPathIsRemote = (FILE_REMOTE_DEVICE == DeviceInfo.Characteristics);
    }

    while (!m_bAbort)
    {
        status = NtFsControlFile(hVolumeRoot,
                                 NULL,
                                 NULL,
                                 NULL,
                                 &iosb,
                                 FSCTL_FIND_FILES_BY_SID,
                                 &FsCtlInput,
                                 sizeof(FsCtlInput),
                                 Output,
                                 sizeof(Output));

        FsCtlInput.Restart = 0;

        if (!NT_SUCCESS(status) && STATUS_BUFFER_OVERFLOW != status)
        {
            DBGERROR((TEXT("NtFsControlFile failed with status 0x%08X"), status));
            return HRESULT_FROM_NT(status);
        }

        if (0 == iosb.Information)
        {
            //
            // No more data.
            //
            break;
        }

        PFILE_NAME_INFORMATION pFileNameInfo = (PFILE_NAME_INFORMATION)Output;

        while (!m_bAbort && ((PBYTE)pFileNameInfo < Output + iosb.Information))
        {
            ULONG Length = sizeof(FILE_NAME_INFORMATION) - sizeof(WCHAR) +
                           pFileNameInfo->FileNameLength;

            CNtHandle hChild;
            WCHAR szChild[MAX_PATH];

            RtlMoveMemory(szChild, pFileNameInfo->FileName, pFileNameInfo->FileNameLength);
            szChild[pFileNameInfo->FileNameLength / sizeof(WCHAR)] = L'\0';
            status = OpenNtObject(szChild,
                                  hVolumeRoot,
                                  FILE_SYNCHRONOUS_IO_NONALERT,
                                  FILE_READ_ATTRIBUTES,
                                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                                  FILE_OPEN,
                                  hChild.HandlePtr());

            if (!NT_SUCCESS(status))
            {
                DBGERROR((TEXT("Unable to open file \"%s\".  Status = 0x%08X"),
                         szChild, status));
            }
            else if (!m_bAbort)
            {
                //
                // Directory entries get a slightly different treatment so
                // we need to know if an entry is a directory or not.
                //
                bool bIsDirectory = false;
                IO_STATUS_BLOCK iosb2;
                FILE_BASIC_INFORMATION fbi;
                status = NtQueryInformationFile(hChild,
                                                &iosb2,
                                                &fbi,
                                                sizeof(fbi),
                                                FileBasicInformation);
                if (!NT_SUCCESS(status))
                {
                    DBGERROR((TEXT("NtQueryInformationFile failed with status 0x%08X for \"%s\""),
                              status, szChild));
                }
                else if (0 != (FILE_ATTRIBUTE_DIRECTORY & fbi.FileAttributes))
                {
                    bIsDirectory = true;
                }
                
                //
                // Get the file's name (full path).
                //
                WCHAR szFile[MAX_PATH + 10];
                status = NtQueryInformationFile(hChild,
                                                &iosb2,
                                                szFile,
                                                sizeof(szFile),
                                                FileNameInformation);

                if (!NT_SUCCESS(status))
                {
                    DBGERROR((TEXT("NtQueryInformation file failed with status 0x%08X for \"%s\""),
                              status, szChild));
                }
                else if (!m_bAbort)
                {
                    PFILE_NAME_INFORMATION pfn = (PFILE_NAME_INFORMATION)szFile;
                    pfn->FileName[pfn->FileNameLength / sizeof(WCHAR)] = L'\0';
                    CPath path;

                    //
                    // If the path is remote, NtQueryInformationFile returns
                    // a string like this:
                    //
                    //  \server\share\dir1\dir2\file.ext
                    //
                    // If the path is local, NtQueryInformationFile returns
                    // a string like this:
                    //
                    //  \dir1\dir2\file.ext
                    //
                    // For remote paths we merely prepend a '\' to create a
                    // valid UNC path.  For local paths we prepend the local
                    // drive specification.
                    //
                    if (bPathIsRemote)
                    {
                        path = L"\\";
                        path += CString(pfn->FileName);
                    }
                    else
                    {
                        path = pszVolumeRoot;
                        path.Append(pfn->FileName);
                    }
                    DBGPRINT((DM_VIEW, DL_LOW, TEXT("Adding \"%s\""), path.Cstr()));
                    pOwnerList->AddFile(iOwner, path, bIsDirectory);
                }
            }
            hChild.Close();

            pFileNameInfo =
                (PFILE_NAME_INFORMATION) Add2Ptr(pFileNameInfo, QuadAlign(Length));
        }
    }
    return NOERROR;
}


//
// Build a list of files owned by a set of users on a particular volume.
// pszVolumeRoot is the volume root directory (i.e. "C:\").
// rgpOwners is an array of user object pointers, one for each owner.
// pOwnerList is the container where the resulting filenames are placed.
// Calls AddFilesToOwnerList() for each owner in rgpOwners.
//
HRESULT
CFileOwnerDialog::BuildFileOwnerList(
    LPCTSTR pszVolumeRoot,
    const CArray<IDiskQuotaUser *>& rgpOwners,
    COwnerList *pOwnerList
    )
{
    DBGTRACE((DM_VIEW, DL_MID, TEXT("CFileOwnerDialog::BuildFileOwnerList")));
    HRESULT hr = NOERROR;
    CNtHandle hVolumeRoot;
    NTSTATUS status = OpenNtObject(pszVolumeRoot,
                                   NULL,
                                   FILE_SYNCHRONOUS_IO_NONALERT,
                                   FILE_READ_ATTRIBUTES,
                                   FILE_SHARE_READ | FILE_SHARE_WRITE,
                                   FILE_OPEN,
                                   hVolumeRoot.HandlePtr());

    if (!NT_SUCCESS(status))
        return HRESULT_FROM_NT(status);

    int cOwners = rgpOwners.Count();
    for (int i = 0; i < cOwners && !m_bAbort; i++)
    {
        hr = AddFilesToOwnerList(pszVolumeRoot, hVolumeRoot, rgpOwners[i], pOwnerList);
    }
    return hr;
}


//
// MarkZ had this function in his original implementation so I just
// kept it.  I did need to fix a bug in the original code.  He was
// calling RtlFreeHeap() on str.Buffer for all cases.  This is was
// not applicable in the RtlInitUnicodeString() case where the
// unicode string is merely bound to the pszFile argument.
//
NTSTATUS
CFileOwnerDialog::OpenNtObject (
    LPCWSTR pszFile,
    HANDLE RelatedObject,
    ULONG CreateOptions,
    ULONG DesiredAccess,
    ULONG ShareAccess,
    ULONG CreateDisposition,
    HANDLE *ph)
{
    NTSTATUS status;
    OBJECT_ATTRIBUTES oa;
    UNICODE_STRING str;
    IO_STATUS_BLOCK isb;
    bool bFreeString = false;

    if (NULL == RelatedObject)
    {
        RtlDosPathNameToNtPathName_U(pszFile, &str, NULL, NULL);
        bFreeString = true;
    }
    else
    {
        //
        // This just attaches pszFile to the rtl string.
        // We don't free it.
        //
        RtlInitUnicodeString(&str, pszFile);
    }

    InitializeObjectAttributes(&oa,
                               &str,
                               OBJ_CASE_INSENSITIVE,
                               RelatedObject,
                               NULL);

    status = NtCreateFile(ph,
                          DesiredAccess | SYNCHRONIZE,
                          &oa,
                          &isb,
                          NULL,                   // pallocationsize (none!)
                          FILE_ATTRIBUTE_NORMAL,
                          ShareAccess,
                          CreateDisposition,
                          CreateOptions,
                          NULL,                   // EA buffer (none!)
                          0);

    if (bFreeString)
        RtlFreeHeap(RtlProcessHeap(), 0, str.Buffer);
    return(status);
}