/**
 * Normalizes the give mode mask.
 *
 * It will create the missing unix or dos mask from the other (one
 * of them is required by all APIs), and guess the file type if that's
 * missing.
 *
 * @returns Normalized file mode.
 * @param   fMode       The mode mask that may contain a partial/incomplete mask.
 * @param   pszName     The filename which this applies to (exe check).
 * @param   cbName      The length of that filename. (optional, set 0)
 */
RTFMODE rtFsModeNormalize(RTFMODE fMode, const char *pszName, size_t cbName)
{
    if (!(fMode & RTFS_UNIX_MASK))
        fMode = rtFsModeFromDos(fMode, pszName, cbName);
    else if (!(fMode & RTFS_DOS_MASK))
        fMode = rtFsModeFromUnix(fMode, pszName, cbName);
    else if (!(fMode & RTFS_TYPE_MASK))
        fMode |= fMode & RTFS_DOS_DIRECTORY ? RTFS_TYPE_DIRECTORY : RTFS_TYPE_FILE;
    else if (RTFS_IS_DIRECTORY(fMode))
        fMode |= RTFS_DOS_DIRECTORY;
    return fMode;
}
/**
 * Queries information from a file or directory handle.
 *
 * This is shared between the RTPathQueryInfo, RTFileQueryInfo and
 * RTDirQueryInfo code.
 *
 * @returns IPRT status code.
 * @param   hFile               The handle to query information from.  Must have
 *                              the necessary privileges.
 * @param   pvBuf               Pointer to a scratch buffer.
 * @param   cbBuf               The size of the buffer.  This must be large
 *                              enough to hold a FILE_ALL_INFORMATION struct.
 * @param   pObjInfo            Where to return information about the handle.
 * @param   enmAddAttr          What extra info to return.
 * @param   pszPath             The path if this is a file (for exe detect).
 * @param   uReparseTag         The reparse tag number (0 if not applicable) for
 *                              symlink detection/whatnot.
 */
DECLHIDDEN(int) rtPathNtQueryInfoFromHandle(HANDLE hFile, void *pvBuf, size_t cbBuf, PRTFSOBJINFO pObjInfo,
                                            RTFSOBJATTRADD enmAddAttr, const char *pszPath, ULONG uReparseTag)
{
    Assert(cbBuf >= sizeof(FILE_ALL_INFORMATION));

    /** @todo Try optimize this for when RTFSOBJATTRADD_UNIX isn't set? */
    IO_STATUS_BLOCK  Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
    NTSTATUS rcNt = NtQueryInformationFile(hFile, &Ios, pvBuf, sizeof(FILE_ALL_INFORMATION), FileAllInformation);
    if (   NT_SUCCESS(rcNt)
        || rcNt == STATUS_BUFFER_OVERFLOW)
    {
        FILE_ALL_INFORMATION *pAllInfo = (FILE_ALL_INFORMATION *)pvBuf;
        pObjInfo->cbObject    = pAllInfo->StandardInformation.EndOfFile.QuadPart;
        pObjInfo->cbAllocated = pAllInfo->StandardInformation.AllocationSize.QuadPart;
        RTTimeSpecSetNtTime(&pObjInfo->BirthTime,         pAllInfo->BasicInformation.CreationTime.QuadPart);
        RTTimeSpecSetNtTime(&pObjInfo->AccessTime,        pAllInfo->BasicInformation.LastAccessTime.QuadPart);
        RTTimeSpecSetNtTime(&pObjInfo->ModificationTime,  pAllInfo->BasicInformation.LastWriteTime.QuadPart);
        RTTimeSpecSetNtTime(&pObjInfo->ChangeTime,        pAllInfo->BasicInformation.ChangeTime.QuadPart);
        pObjInfo->Attr.fMode = rtFsModeFromDos(  (pAllInfo->BasicInformation.FileAttributes << RTFS_DOS_SHIFT)
                                               & RTFS_DOS_MASK_NT,
                                               pszPath, pszPath ? strlen(pszPath) : 0, uReparseTag);
        pObjInfo->Attr.enmAdditional = enmAddAttr;
        if (enmAddAttr == RTFSOBJATTRADD_UNIX)
        {
            pObjInfo->Attr.u.Unix.uid             = ~0U;
            pObjInfo->Attr.u.Unix.gid             = ~0U;
            pObjInfo->Attr.u.Unix.cHardlinks      = RT_MAX(1, pAllInfo->StandardInformation.NumberOfLinks);
            pObjInfo->Attr.u.Unix.INodeIdDevice   = 0; /* below */
            pObjInfo->Attr.u.Unix.INodeId         = pAllInfo->InternalInformation.IndexNumber.QuadPart;
            pObjInfo->Attr.u.Unix.fFlags          = 0;
            pObjInfo->Attr.u.Unix.GenerationId    = 0;
            pObjInfo->Attr.u.Unix.Device          = 0;

            /* Get the serial number. */
            rcNt = NtQueryVolumeInformationFile(hFile, &Ios, pvBuf, (ULONG)RT_MIN(cbBuf, _2K), FileFsVolumeInformation);
            if (NT_SUCCESS(rcNt) || rcNt == STATUS_BUFFER_OVERFLOW)
            {
                FILE_FS_VOLUME_INFORMATION *pVolInfo = (FILE_FS_VOLUME_INFORMATION *)pvBuf;
                pObjInfo->Attr.u.Unix.INodeIdDevice = pVolInfo->VolumeSerialNumber;
            }
        }

        return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr);
    }
    return RTErrConvertFromNtStatus(rcNt);
}
예제 #3
0
RTDECL(int) RTDirReadEx(PRTDIR pDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry,
                        RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags)
{
    int rc;

    /*
     * Validate input.
     */
    AssertPtrReturn(pDir, VERR_INVALID_POINTER);
    AssertReturn(pDir->u32Magic == RTDIR_MAGIC, VERR_INVALID_HANDLE);
    AssertPtrReturn(pDirEntry, VERR_INVALID_POINTER);

    AssertReturn(enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING && enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
                 VERR_INVALID_PARAMETER);
    AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);

    size_t cbDirEntry = sizeof(*pDirEntry);
    if (pcbDirEntry)
    {
        cbDirEntry = *pcbDirEntry;
        AssertMsgReturn(cbDirEntry >= RT_UOFFSETOF(RTDIRENTRYEX, szName[2]),
                        ("Invalid *pcbDirEntry=%d (min %d)\n", *pcbDirEntry, RT_OFFSETOF(RTDIRENTRYEX, szName[2])),
                        VERR_INVALID_PARAMETER);
    }

    /*
     * Fetch data?
     */
    if (!pDir->fDataUnread)
    {
        rc = rtDirNtFetchMore(pDir);
        if (RT_FAILURE(rc))
            return rc;
    }

    /*
     * Convert the filename to UTF-8.
     */
    rc = rtDirNtConvertCurName(pDir);
    if (RT_FAILURE(rc))
        return rc;

    /*
     * Check if we've got enough space to return the data.
     */
    const char  *pszName    = pDir->pszName;
    const size_t cchName    = pDir->cchName;
    const size_t cbRequired = RT_OFFSETOF(RTDIRENTRYEX, szName[1]) + cchName;
    if (pcbDirEntry)
        *pcbDirEntry = cbRequired;
    if (cbRequired > cbDirEntry)
        return VERR_BUFFER_OVERFLOW;

    /*
     * Setup the returned data.
     */
    PFILE_BOTH_DIR_INFORMATION pBoth = pDir->uCurData.pBoth;

    pDirEntry->cbName  = (uint16_t)cchName;  Assert(pDirEntry->cbName == cchName);
    memcpy(pDirEntry->szName, pszName, cchName + 1);
    memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName));
#ifdef IPRT_WITH_NT_PATH_PASSTHRU
    if (pDir->enmInfoClass != FileMaximumInformation)
#endif
    {
        uint8_t cbShort = pBoth->ShortNameLength;
        if (cbShort > 0)
        {
            AssertStmt(cbShort < sizeof(pDirEntry->wszShortName), cbShort = sizeof(pDirEntry->wszShortName) - 2);
            memcpy(pDirEntry->wszShortName, pBoth->ShortName, cbShort);
            pDirEntry->cwcShortName = cbShort / 2;
        }
        else
            pDirEntry->cwcShortName = 0;

        pDirEntry->Info.cbObject    = pBoth->EndOfFile.QuadPart;
        pDirEntry->Info.cbAllocated = pBoth->AllocationSize.QuadPart;

        Assert(sizeof(uint64_t) == sizeof(pBoth->CreationTime));
        RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime,         pBoth->CreationTime.QuadPart);
        RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime,        pBoth->LastAccessTime.QuadPart);
        RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime,  pBoth->LastWriteTime.QuadPart);
        RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime,        pBoth->ChangeTime.QuadPart);

        pDirEntry->Info.Attr.fMode  = rtFsModeFromDos((pBoth->FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
                                                       pszName, cchName);
    }
#ifdef IPRT_WITH_NT_PATH_PASSTHRU
    else
    {
        pDirEntry->cwcShortName = 0;
        pDirEntry->Info.cbObject    = 0;
        pDirEntry->Info.cbAllocated = 0;
        RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime,         0);
        RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime,        0);
        RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime,  0);
        RTTimeSpecSetNtTime(&pDirEntry->Info.ChangeTime,        0);

        if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
                                    RT_STR_TUPLE("Directory")))
            pDirEntry->Info.Attr.fMode = RTFS_DOS_DIRECTORY | RTFS_TYPE_DIRECTORY | 0777;
        else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
                                         RT_STR_TUPLE("SymbolicLink")))
            pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_REPARSE_POINT | RTFS_TYPE_SYMLINK | 0777;
        else if (rtNtCompWideStrAndAscii(pDir->uCurData.pObjDir->TypeName.Buffer, pDir->uCurData.pObjDir->TypeName.Length,
                                         RT_STR_TUPLE("Device")))
            pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_DEVICE | RTFS_TYPE_DEV_CHAR | 0666;
        else
            pDirEntry->Info.Attr.fMode = RTFS_DOS_NT_NORMAL | RTFS_TYPE_FILE | 0666;
    }
#endif

    /*
     * Requested attributes (we cannot provide anything actually).
     */
    switch (enmAdditionalAttribs)
    {
        case RTFSOBJATTRADD_EASIZE:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_EASIZE;
#ifdef IPRT_WITH_NT_PATH_PASSTHRU
            if (pDir->enmInfoClass == FileMaximumInformation)
                pDirEntry->Info.Attr.u.EASize.cb        = 0;
            else
#endif
                pDirEntry->Info.Attr.u.EASize.cb        = pBoth->EaSize;
            break;

        case RTFSOBJATTRADD_UNIX:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_UNIX;
            pDirEntry->Info.Attr.u.Unix.uid             = ~0U;
            pDirEntry->Info.Attr.u.Unix.gid             = ~0U;
            pDirEntry->Info.Attr.u.Unix.cHardlinks      = 1;
            pDirEntry->Info.Attr.u.Unix.INodeIdDevice   = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */
            pDirEntry->Info.Attr.u.Unix.INodeId         = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */
            pDirEntry->Info.Attr.u.Unix.fFlags          = 0;
            pDirEntry->Info.Attr.u.Unix.GenerationId    = 0;
            pDirEntry->Info.Attr.u.Unix.Device          = 0;
            break;

        case RTFSOBJATTRADD_NOTHING:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_NOTHING;
            break;

        case RTFSOBJATTRADD_UNIX_OWNER:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_OWNER;
            pDirEntry->Info.Attr.u.UnixOwner.uid        = ~0U;
            pDirEntry->Info.Attr.u.UnixOwner.szName[0]  = '\0'; /** @todo return something sensible here. */
            break;

        case RTFSOBJATTRADD_UNIX_GROUP:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_GROUP;
            pDirEntry->Info.Attr.u.UnixGroup.gid        = ~0U;
            pDirEntry->Info.Attr.u.UnixGroup.szName[0]  = '\0';
            break;

        default:
            AssertMsgFailed(("Impossible!\n"));
            return VERR_INTERNAL_ERROR;
    }

    /*
     * Follow links if requested.
     */
    if (   (fFlags & RTPATH_F_FOLLOW_LINK)
        && RTFS_IS_SYMLINK(fFlags))
    {
        /** @todo Symlinks: Find[First|Next]FileW will return info about
            the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */
    }

    /*
     * Finally advance the buffer.
     */
    return rtDirNtAdvanceBuffer(pDir);
}
예제 #4
0
RTDECL(int) RTDirReadEx(PRTDIR pDir, PRTDIRENTRYEX pDirEntry, size_t *pcbDirEntry, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags)
{
    /** @todo Symlinks: Find[First|Next]FileW will return info about
        the link, so RTPATH_F_FOLLOW_LINK is not handled correctly. */
    /*
     * Validate input.
     */
    if (!pDir || pDir->u32Magic != RTDIR_MAGIC)
    {
        AssertMsgFailed(("Invalid pDir=%p\n", pDir));
        return VERR_INVALID_PARAMETER;
    }
    if (!pDirEntry)
    {
        AssertMsgFailed(("Invalid pDirEntry=%p\n", pDirEntry));
        return VERR_INVALID_PARAMETER;
    }
    if (    enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING
        ||  enmAdditionalAttribs > RTFSOBJATTRADD_LAST)
    {
        AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs));
        return VERR_INVALID_PARAMETER;
    }
    AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);
    size_t cbDirEntry = sizeof(*pDirEntry);
    if (pcbDirEntry)
    {
        cbDirEntry = *pcbDirEntry;
        if (cbDirEntry < RT_UOFFSETOF(RTDIRENTRYEX, szName[2]))
        {
            AssertMsgFailed(("Invalid *pcbDirEntry=%d (min %d)\n", *pcbDirEntry, RT_OFFSETOF(RTDIRENTRYEX, szName[2])));
            return VERR_INVALID_PARAMETER;
        }
    }

    /*
     * Fetch data?
     */
    if (!pDir->fDataUnread)
    {
        RTStrFree(pDir->pszName);
        pDir->pszName = NULL;

        BOOL fRc = FindNextFileW(pDir->hDir, &pDir->Data);
        if (!fRc)
        {
            int iErr = GetLastError();
            if (pDir->hDir == INVALID_HANDLE_VALUE || iErr == ERROR_NO_MORE_FILES)
                return VERR_NO_MORE_FILES;
            return RTErrConvertFromWin32(iErr);
        }
    }

    /*
     * Convert the filename to UTF-8.
     */
    if (!pDir->pszName)
    {
        int rc = RTUtf16ToUtf8((PCRTUTF16)pDir->Data.cFileName, &pDir->pszName);
        if (RT_FAILURE(rc))
        {
            pDir->pszName = NULL;
            return rc;
        }
        pDir->cchName = strlen(pDir->pszName);
    }

    /*
     * Check if we've got enough space to return the data.
     */
    const char  *pszName    = pDir->pszName;
    const size_t cchName    = pDir->cchName;
    const size_t cbRequired = RT_OFFSETOF(RTDIRENTRYEX, szName[1]) + cchName;
    if (pcbDirEntry)
        *pcbDirEntry = cbRequired;
    if (cbRequired > cbDirEntry)
        return VERR_BUFFER_OVERFLOW;

    /*
     * Setup the returned data.
     */
    pDir->fDataUnread  = false;
    pDirEntry->cbName  = (uint16_t)cchName;
    Assert(pDirEntry->cbName == cchName);
    memcpy(pDirEntry->szName, pszName, cchName + 1);
    if (pDir->Data.cAlternateFileName[0])
    {
        /* copy and calc length */
        PCRTUTF16 pwszSrc = (PCRTUTF16)pDir->Data.cAlternateFileName;
        PRTUTF16  pwszDst = pDirEntry->wszShortName;
        uint32_t  off = 0;
        while (off < RT_ELEMENTS(pDirEntry->wszShortName) - 1U && pwszSrc[off])
        {
            pwszDst[off] = pwszSrc[off];
            off++;
        }
        pDirEntry->cwcShortName = (uint16_t)off;

        /* zero the rest */
        do
            pwszDst[off++] = '\0';
        while (off < RT_ELEMENTS(pDirEntry->wszShortName));
    }
    else
    {
        memset(pDirEntry->wszShortName, 0, sizeof(pDirEntry->wszShortName));
        pDirEntry->cwcShortName = 0;
    }

    pDirEntry->Info.cbObject    = ((uint64_t)pDir->Data.nFileSizeHigh << 32)
                                |  (uint64_t)pDir->Data.nFileSizeLow;
    pDirEntry->Info.cbAllocated = pDirEntry->Info.cbObject;

    Assert(sizeof(uint64_t) == sizeof(pDir->Data.ftCreationTime));
    RTTimeSpecSetNtTime(&pDirEntry->Info.BirthTime,         *(uint64_t *)&pDir->Data.ftCreationTime);
    RTTimeSpecSetNtTime(&pDirEntry->Info.AccessTime,        *(uint64_t *)&pDir->Data.ftLastAccessTime);
    RTTimeSpecSetNtTime(&pDirEntry->Info.ModificationTime,  *(uint64_t *)&pDir->Data.ftLastWriteTime);
    pDirEntry->Info.ChangeTime  = pDirEntry->Info.ModificationTime;

    pDirEntry->Info.Attr.fMode  = rtFsModeFromDos((pDir->Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
                                                   pszName, cchName);

    /*
     * Requested attributes (we cannot provide anything actually).
     */
    switch (enmAdditionalAttribs)
    {
        case RTFSOBJATTRADD_EASIZE:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_EASIZE;
            pDirEntry->Info.Attr.u.EASize.cb            = 0;
            break;

        case RTFSOBJATTRADD_UNIX:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_UNIX;
            pDirEntry->Info.Attr.u.Unix.uid             = ~0U;
            pDirEntry->Info.Attr.u.Unix.gid             = ~0U;
            pDirEntry->Info.Attr.u.Unix.cHardlinks      = 1;
            pDirEntry->Info.Attr.u.Unix.INodeIdDevice   = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */
            pDirEntry->Info.Attr.u.Unix.INodeId         = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */
            pDirEntry->Info.Attr.u.Unix.fFlags          = 0;
            pDirEntry->Info.Attr.u.Unix.GenerationId    = 0;
            pDirEntry->Info.Attr.u.Unix.Device          = 0;
            break;

        case RTFSOBJATTRADD_NOTHING:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_NOTHING;
            break;

        case RTFSOBJATTRADD_UNIX_OWNER:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_OWNER;
            pDirEntry->Info.Attr.u.UnixOwner.uid        = ~0U;
            pDirEntry->Info.Attr.u.UnixOwner.szName[0]  = '\0'; /** @todo return something sensible here. */
            break;

        case RTFSOBJATTRADD_UNIX_GROUP:
            pDirEntry->Info.Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_GROUP;
            pDirEntry->Info.Attr.u.UnixGroup.gid        = ~0U;
            pDirEntry->Info.Attr.u.UnixGroup.szName[0]  = '\0';
            break;

        default:
            AssertMsgFailed(("Impossible!\n"));
            return VERR_INTERNAL_ERROR;
    }

    return VINF_SUCCESS;
}
예제 #5
0
RTR3DECL(int) RTFileQueryInfo(RTFILE hFile, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs)
{
    /*
     * Validate input.
     */
    if (hFile == NIL_RTFILE)
    {
        AssertMsgFailed(("Invalid hFile=%RTfile\n", hFile));
        return VERR_INVALID_PARAMETER;
    }
    if (!pObjInfo)
    {
        AssertMsgFailed(("Invalid pObjInfo=%p\n", pObjInfo));
        return VERR_INVALID_PARAMETER;
    }
    if (    enmAdditionalAttribs < RTFSOBJATTRADD_NOTHING
        ||  enmAdditionalAttribs > RTFSOBJATTRADD_LAST)
    {
        AssertMsgFailed(("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs));
        return VERR_INVALID_PARAMETER;
    }

    /*
     * Query file info.
     */
    BY_HANDLE_FILE_INFORMATION Data;
    if (!GetFileInformationByHandle((HANDLE)RTFileToNative(hFile), &Data))
    {
        DWORD dwErr = GetLastError();
        /* Only return if we *really* don't have a valid handle value,
         * everything else is fine here ... */
        if (dwErr == ERROR_INVALID_HANDLE)
            return RTErrConvertFromWin32(dwErr);
        RT_ZERO(Data);
        Data.dwFileAttributes = RTFS_DOS_NT_DEVICE;
    }

    /*
     * Setup the returned data.
     */
    pObjInfo->cbObject    = ((uint64_t)Data.nFileSizeHigh << 32)
                          |  (uint64_t)Data.nFileSizeLow;
    pObjInfo->cbAllocated = pObjInfo->cbObject;

    Assert(sizeof(uint64_t) == sizeof(Data.ftCreationTime));
    RTTimeSpecSetNtTime(&pObjInfo->BirthTime,         *(uint64_t *)&Data.ftCreationTime);
    RTTimeSpecSetNtTime(&pObjInfo->AccessTime,        *(uint64_t *)&Data.ftLastAccessTime);
    RTTimeSpecSetNtTime(&pObjInfo->ModificationTime,  *(uint64_t *)&Data.ftLastWriteTime);
    pObjInfo->ChangeTime  = pObjInfo->ModificationTime;

    pObjInfo->Attr.fMode  = rtFsModeFromDos((Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT, "", 0);

    /*
     * Requested attributes (we cannot provide anything actually).
     */
    switch (enmAdditionalAttribs)
    {
        case RTFSOBJATTRADD_NOTHING:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_NOTHING;
            break;

        case RTFSOBJATTRADD_UNIX:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_UNIX;
            pObjInfo->Attr.u.Unix.uid             = ~0U;
            pObjInfo->Attr.u.Unix.gid             = ~0U;
            pObjInfo->Attr.u.Unix.cHardlinks      = Data.nNumberOfLinks ? Data.nNumberOfLinks : 1;
            pObjInfo->Attr.u.Unix.INodeIdDevice   = 0; /** @todo Use the volume serial number (see GetFileInformationByHandle). */
            pObjInfo->Attr.u.Unix.INodeId         = 0; /** @todo Use the fileid (see GetFileInformationByHandle). */
            pObjInfo->Attr.u.Unix.fFlags          = 0;
            pObjInfo->Attr.u.Unix.GenerationId    = 0;
            pObjInfo->Attr.u.Unix.Device          = 0;
            break;

        case RTFSOBJATTRADD_UNIX_OWNER:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_OWNER;
            pObjInfo->Attr.u.UnixOwner.uid        = ~0U;
            pObjInfo->Attr.u.UnixOwner.szName[0]  = '\0'; /** @todo return something sensible here. */
            break;

        case RTFSOBJATTRADD_UNIX_GROUP:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_GROUP;
            pObjInfo->Attr.u.UnixGroup.gid        = ~0U;
            pObjInfo->Attr.u.UnixGroup.szName[0]  = '\0';
            break;

        case RTFSOBJATTRADD_EASIZE:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_EASIZE;
            pObjInfo->Attr.u.EASize.cb            = 0;
            break;

        default:
            AssertMsgFailed(("Impossible!\n"));
            return VERR_INTERNAL_ERROR;
    }

    return VINF_SUCCESS;
}
/**
 * Worker for RTPathQueryInfoEx and RTDirRelPathQueryInfo.
 *
 * @returns IPRT status code.
 * @param   hRootDir            The root directory that pNtName is relative to.
 * @param   pNtName             The NT path which we want to query info for.
 * @param   pObjInfo            Where to return the info.
 * @param   enmAddAttr          What additional info to get/fake.
 * @param   fFlags              Query flags (RTPATH_F_XXX).
 * @param   pszPath             The path for detecting executables and such.
 *                              Pass empty string if not applicable/available.
 */
DECLHIDDEN(int) rtPathNtQueryInfoWorker(HANDLE hRootDir, UNICODE_STRING *pNtName, PRTFSOBJINFO pObjInfo,
                                        RTFSOBJATTRADD enmAddAttr, uint32_t fFlags, const char *pszPath)
{
    /*
     * There are a three different ways of doing this:
     *   1. Use NtQueryFullAttributesFile to the get basic file info.
     *   2. Open whatever the path points to and use NtQueryInformationFile.
     *   3. Open the parent directory and use NtQueryDirectoryFile like RTDirReadEx.
     *
     * The first two options may fail with sharing violations or access denied,
     * in which case we must use the last one as fallback.
     */
    HANDLE              hFile = RTNT_INVALID_HANDLE_VALUE;
    IO_STATUS_BLOCK     Ios = RTNT_IO_STATUS_BLOCK_INITIALIZER;
    NTSTATUS            rcNt;
    OBJECT_ATTRIBUTES   ObjAttr;
    union
    {
        FILE_NETWORK_OPEN_INFORMATION   NetOpenInfo;
        FILE_ALL_INFORMATION            AllInfo;
        FILE_FS_VOLUME_INFORMATION      VolInfo;
        FILE_BOTH_DIR_INFORMATION       Both;
        FILE_ID_BOTH_DIR_INFORMATION    BothId;
        uint8_t                         abPadding[sizeof(FILE_ID_BOTH_DIR_INFORMATION) + RTPATH_MAX * sizeof(wchar_t)];
    } uBuf;

    /*
     * We can only use the first option if no additional UNIX attribs are
     * requested and it isn't a symbolic link.  NT directory object
     */
    int rc = VINF_TRY_AGAIN;
    if (   enmAddAttr != RTFSOBJATTRADD_UNIX
        && g_pfnNtQueryFullAttributesFile)
    {
        InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL);
        rcNt = g_pfnNtQueryFullAttributesFile(&ObjAttr, &uBuf.NetOpenInfo);
        if (NT_SUCCESS(rcNt))
        {
            if (!(uBuf.NetOpenInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
            {
                pObjInfo->cbObject    = uBuf.NetOpenInfo.EndOfFile.QuadPart;
                pObjInfo->cbAllocated = uBuf.NetOpenInfo.AllocationSize.QuadPart;
                RTTimeSpecSetNtTime(&pObjInfo->BirthTime,         uBuf.NetOpenInfo.CreationTime.QuadPart);
                RTTimeSpecSetNtTime(&pObjInfo->AccessTime,        uBuf.NetOpenInfo.LastAccessTime.QuadPart);
                RTTimeSpecSetNtTime(&pObjInfo->ModificationTime,  uBuf.NetOpenInfo.LastWriteTime.QuadPart);
                RTTimeSpecSetNtTime(&pObjInfo->ChangeTime,        uBuf.NetOpenInfo.ChangeTime.QuadPart);
                pObjInfo->Attr.fMode = rtFsModeFromDos((uBuf.NetOpenInfo.FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
                                                       pszPath, strlen(pszPath), 0 /*uReparseTag*/);
                pObjInfo->Attr.enmAdditional = enmAddAttr;

                return rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr);
            }
        }
        else if (   rcNt == STATUS_OBJECT_TYPE_MISMATCH
                 || rcNt == STATUS_OBJECT_NAME_INVALID
                 || rcNt == STATUS_INVALID_PARAMETER)
        {
            rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt);
            if (RT_SUCCESS(rc))
                return rc;
        }
        else if (   rcNt != STATUS_ACCESS_DENIED
                 && rcNt != STATUS_SHARING_VIOLATION)
            rc = RTErrConvertFromNtStatus(rcNt);
        else
            RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
    }

    /*
     * Try the 2nd option.  We might have to redo this if not following symbolic
     * links and the reparse point isn't a symbolic link but a mount point or similar.
     * We want to return information about the mounted root directory if we can, not
     * the directory in which it was mounted.
     */
    if (rc == VINF_TRY_AGAIN)
    {
        static int volatile g_fReparsePoints = -1;
        uint32_t            fOptions         = FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT;
        int fReparsePoints = g_fReparsePoints;
        if (fReparsePoints != 0 && !(fFlags & RTPATH_F_FOLLOW_LINK))
            fOptions |= FILE_OPEN_REPARSE_POINT;

        InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL);
        rcNt = NtCreateFile(&hFile,
                            FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                            &ObjAttr,
                            &Ios,
                            NULL /*pcbFile*/,
                            FILE_ATTRIBUTE_NORMAL,
                            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                            FILE_OPEN,
                            fOptions,
                            NULL /*pvEaBuffer*/,
                            0 /*cbEa*/);
        if (   (   rcNt == STATUS_INVALID_PARAMETER
                || rcNt == STATUS_INVALID_PARAMETER_9)
            && fReparsePoints == -1
            && (fOptions & FILE_OPEN_REPARSE_POINT))
        {
            fOptions &= ~FILE_OPEN_REPARSE_POINT;
            rcNt = NtCreateFile(&hFile,
                                FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                                &ObjAttr,
                                &Ios,
                                NULL /*pcbFile*/,
                                FILE_ATTRIBUTE_NORMAL,
                                FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                FILE_OPEN,
                                fOptions,
                                NULL /*pvEaBuffer*/,
                                0 /*cbEa*/);
            if (rcNt != STATUS_INVALID_PARAMETER)
                g_fReparsePoints = fReparsePoints = 0;
        }
        if (NT_SUCCESS(rcNt))
        {
            /* Query tag information first in order to try re-open non-symlink reparse points. */
            FILE_ATTRIBUTE_TAG_INFORMATION TagInfo;
            rcNt = NtQueryInformationFile(hFile, &Ios, &TagInfo, sizeof(TagInfo), FileAttributeTagInformation);
            if (!NT_SUCCESS(rcNt))
                TagInfo.FileAttributes = TagInfo.ReparseTag = 0;
            if (   !(TagInfo.FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
                || TagInfo.ReparseTag == IO_REPARSE_TAG_SYMLINK
                || (fFlags & RTPATH_F_FOLLOW_LINK))
            { /* likely */ }
            else
            {
                /* Reparse point that isn't a symbolic link, try follow the reparsing. */
                HANDLE hFile2;
                RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
                rcNt = NtCreateFile(&hFile2,
                                    FILE_READ_ATTRIBUTES | SYNCHRONIZE,
                                    &ObjAttr,
                                    &Ios,
                                    NULL /*pcbFile*/,
                                    FILE_ATTRIBUTE_NORMAL,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                    FILE_OPEN,
                                    FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
                                    NULL /*pvEaBuffer*/,
                                    0 /*cbEa*/);
                if (NT_SUCCESS(rcNt))
                {
                    NtClose(hFile);
                    hFile = hFile2;
                    TagInfo.FileAttributes = TagInfo.ReparseTag = 0;
                }
            }

            /*
             * Get the information we need and convert it.
             */
            rc = rtPathNtQueryInfoFromHandle(hFile, &uBuf, sizeof(uBuf), pObjInfo, enmAddAttr, pszPath, TagInfo.ReparseTag);
            NtClose(hFile);
            if (RT_SUCCESS(rc))
                return rc;

            if (RT_FAILURE(rc))
                rc = VINF_TRY_AGAIN;
        }
        else if (   rcNt == STATUS_OBJECT_TYPE_MISMATCH
                 || rcNt == STATUS_OBJECT_NAME_INVALID
                 /*|| rcNt == STATUS_INVALID_PARAMETER*/)
        {
            rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt);
            if (RT_SUCCESS(rc))
                return rc;
        }
        else if (   rcNt != STATUS_ACCESS_DENIED
                 && rcNt != STATUS_SHARING_VIOLATION)
            rc = RTErrConvertFromNtStatus(rcNt);
        else
            RTNT_IO_STATUS_BLOCK_REINIT(&Ios);
    }

    /*
     * Try the 3rd option if none of the other worked.
     * If none of the above worked, try open the directory and enumerate
     * the file we're after.  This
     */
    if (rc == VINF_TRY_AGAIN)
    {
        /* Split up the name into parent directory path and filename. */
        UNICODE_STRING NtDirName;
        UNICODE_STRING NtFilter;
        ntPathNtSplitName(pNtName, &NtDirName, &NtFilter, false /*fNoParentDirSlash*/);

        /* Try open the directory. */
        InitializeObjectAttributes(&ObjAttr, &NtDirName, OBJ_CASE_INSENSITIVE, hRootDir, NULL);
        rcNt = NtCreateFile(&hFile,
                            FILE_LIST_DIRECTORY | SYNCHRONIZE,
                            &ObjAttr,
                            &Ios,
                            NULL /*pcbFile*/,
                            FILE_ATTRIBUTE_NORMAL,
                            FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                            FILE_OPEN,
                            FILE_DIRECTORY_FILE | FILE_OPEN_FOR_BACKUP_INTENT | FILE_SYNCHRONOUS_IO_NONALERT,
                            NULL /*pvEaBuffer*/,
                            0 /*cbEa*/);
        if (NT_SUCCESS(rcNt))
        {
            FILE_INFORMATION_CLASS enmInfoClass;
            if (RT_MAKE_U64(RTNtCurrentPeb()->OSMinorVersion, RTNtCurrentPeb()->OSMajorVersion) > RT_MAKE_U64(0,5) /* > W2K */)
                enmInfoClass = FileIdBothDirectoryInformation; /* Introduced in XP, from I can tell. */
            else
                enmInfoClass = FileBothDirectoryInformation;
            rcNt = NtQueryDirectoryFile(hFile,
                                        NULL /* Event */,
                                        NULL /* ApcRoutine */,
                                        NULL /* ApcContext */,
                                        &Ios,
                                        &uBuf,
                                        RT_MIN(sizeof(uBuf), 0xfff0),
                                        enmInfoClass,
                                        TRUE /*ReturnSingleEntry */,
                                        &NtFilter,
                                        FALSE /*RestartScan */);
            if (NT_SUCCESS(rcNt))
            {
                pObjInfo->cbObject    = uBuf.Both.EndOfFile.QuadPart;
                pObjInfo->cbAllocated = uBuf.Both.AllocationSize.QuadPart;

                RTTimeSpecSetNtTime(&pObjInfo->BirthTime,         uBuf.Both.CreationTime.QuadPart);
                RTTimeSpecSetNtTime(&pObjInfo->AccessTime,        uBuf.Both.LastAccessTime.QuadPart);
                RTTimeSpecSetNtTime(&pObjInfo->ModificationTime,  uBuf.Both.LastWriteTime.QuadPart);
                RTTimeSpecSetNtTime(&pObjInfo->ChangeTime,        uBuf.Both.ChangeTime.QuadPart);

                pObjInfo->Attr.fMode  = rtFsModeFromDos((uBuf.Both.FileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
                                                        pszPath, strlen(pszPath), uBuf.Both.EaSize);

                pObjInfo->Attr.enmAdditional = enmAddAttr;
                if (enmAddAttr == RTFSOBJATTRADD_UNIX)
                {
                    pObjInfo->Attr.u.Unix.uid             = ~0U;
                    pObjInfo->Attr.u.Unix.gid             = ~0U;
                    pObjInfo->Attr.u.Unix.cHardlinks      = 1;
                    pObjInfo->Attr.u.Unix.INodeIdDevice   = 0; /* below */
                    pObjInfo->Attr.u.Unix.INodeId         = enmInfoClass == FileIdBothDirectoryInformation
                                                          ? uBuf.BothId.FileId.QuadPart : 0;
                    pObjInfo->Attr.u.Unix.fFlags          = 0;
                    pObjInfo->Attr.u.Unix.GenerationId    = 0;
                    pObjInfo->Attr.u.Unix.Device          = 0;

                    /* Get the serial number. */
                    rcNt = NtQueryVolumeInformationFile(hFile, &Ios, &uBuf, RT_MIN(sizeof(uBuf), _2K),
                                                        FileFsVolumeInformation);
                    if (NT_SUCCESS(rcNt))
                        pObjInfo->Attr.u.Unix.INodeIdDevice = uBuf.VolInfo.VolumeSerialNumber;
                }

                rc = rtPathNtQueryInfoFillInDummyData(VINF_SUCCESS, pObjInfo, enmAddAttr);
            }
            else
                rc = RTErrConvertFromNtStatus(rcNt);

            NtClose(hFile);
        }
        /*
         * Quite possibly a object directory.
         */
        else if (   rcNt == STATUS_OBJECT_NAME_INVALID  /* with trailing slash */
                 || rcNt == STATUS_OBJECT_TYPE_MISMATCH /* without trailing slash */ )
        {
            InitializeObjectAttributes(&ObjAttr, pNtName, OBJ_CASE_INSENSITIVE, hRootDir, NULL);
            rc = rtPathNtQueryInfoInDirectoryObject(&ObjAttr, pObjInfo, enmAddAttr, fFlags, &uBuf, sizeof(uBuf), rcNt);
            if (RT_FAILURE(rc))
                rc = RTErrConvertFromNtStatus(rcNt);
        }
        else
            rc = RTErrConvertFromNtStatus(rcNt);
    }

    return rc;
}
예제 #7
-1
RTR3DECL(int) RTPathQueryInfoEx(const char *pszPath, PRTFSOBJINFO pObjInfo, RTFSOBJATTRADD enmAdditionalAttribs, uint32_t fFlags)
{
    /*
     * Validate input.
     */
    AssertPtrReturn(pszPath, VERR_INVALID_POINTER);
    AssertReturn(*pszPath, VERR_INVALID_PARAMETER);
    AssertPtrReturn(pObjInfo, VERR_INVALID_POINTER);
    AssertMsgReturn(    enmAdditionalAttribs >= RTFSOBJATTRADD_NOTHING
                    &&  enmAdditionalAttribs <= RTFSOBJATTRADD_LAST,
                    ("Invalid enmAdditionalAttribs=%p\n", enmAdditionalAttribs),
                    VERR_INVALID_PARAMETER);
    AssertMsgReturn(RTPATH_F_IS_VALID(fFlags, 0), ("%#x\n", fFlags), VERR_INVALID_PARAMETER);

    /*
     * Query file info.
     */
    WIN32_FILE_ATTRIBUTE_DATA Data;
    PRTUTF16 pwszPath;
    int rc = RTStrToUtf16(pszPath, &pwszPath);
    if (RT_FAILURE(rc))
        return rc;
    if (!GetFileAttributesExW(pwszPath, GetFileExInfoStandard, &Data))
    {
        /* Fallback to FindFileFirst in case of sharing violation. */
        if (GetLastError() == ERROR_SHARING_VIOLATION)
        {
            WIN32_FIND_DATAW FindData;
            HANDLE hDir = FindFirstFileW(pwszPath, &FindData);
            if (hDir == INVALID_HANDLE_VALUE)
            {
                rc = RTErrConvertFromWin32(GetLastError());
                RTUtf16Free(pwszPath);
                return rc;
            }
            FindClose(hDir);

            Data.dwFileAttributes   = FindData.dwFileAttributes;
            Data.ftCreationTime     = FindData.ftCreationTime;
            Data.ftLastAccessTime   = FindData.ftLastAccessTime;
            Data.ftLastWriteTime    = FindData.ftLastWriteTime;
            Data.nFileSizeHigh      = FindData.nFileSizeHigh;
            Data.nFileSizeLow       = FindData.nFileSizeLow;
        }
        else
        {
            rc = RTErrConvertFromWin32(GetLastError());
            RTUtf16Free(pwszPath);
            return rc;
        }
    }

    /*
     * Getting the information for the link target is a bit annoying and
     * subject to the same access violation mess as above.. :/
     */
    /** @todo we're too lazy wrt to error paths here... */
    if (   (fFlags & RTPATH_F_FOLLOW_LINK)
        && (Data.dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT))
    {
        HANDLE hFinal = CreateFileW(pwszPath,
                                    GENERIC_READ,
                                    FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE,
                                    NULL,
                                    OPEN_EXISTING,
                                    FILE_FLAG_BACKUP_SEMANTICS,
                                    NULL);
        if (hFinal != INVALID_HANDLE_VALUE)
        {
            BY_HANDLE_FILE_INFORMATION FileData;
            if (GetFileInformationByHandle(hFinal, &FileData))
            {
                Data.dwFileAttributes   = FileData.dwFileAttributes;
                Data.ftCreationTime     = FileData.ftCreationTime;
                Data.ftLastAccessTime   = FileData.ftLastAccessTime;
                Data.ftLastWriteTime    = FileData.ftLastWriteTime;
                Data.nFileSizeHigh      = FileData.nFileSizeHigh;
                Data.nFileSizeLow       = FileData.nFileSizeLow;
            }
            CloseHandle(hFinal);
        }
        else if (GetLastError() != ERROR_SHARING_VIOLATION)
        {
            rc = RTErrConvertFromWin32(GetLastError());
            RTUtf16Free(pwszPath);
            return rc;
        }
    }

    RTUtf16Free(pwszPath);

    /*
     * Setup the returned data.
     */
    pObjInfo->cbObject    = ((uint64_t)Data.nFileSizeHigh << 32)
                          |  (uint64_t)Data.nFileSizeLow;
    pObjInfo->cbAllocated = pObjInfo->cbObject;

    Assert(sizeof(uint64_t) == sizeof(Data.ftCreationTime));
    RTTimeSpecSetNtTime(&pObjInfo->BirthTime,         *(uint64_t *)&Data.ftCreationTime);
    RTTimeSpecSetNtTime(&pObjInfo->AccessTime,        *(uint64_t *)&Data.ftLastAccessTime);
    RTTimeSpecSetNtTime(&pObjInfo->ModificationTime,  *(uint64_t *)&Data.ftLastWriteTime);
    pObjInfo->ChangeTime  = pObjInfo->ModificationTime;

    pObjInfo->Attr.fMode  = rtFsModeFromDos((Data.dwFileAttributes << RTFS_DOS_SHIFT) & RTFS_DOS_MASK_NT,
                                            pszPath, strlen(pszPath));

    /*
     * Requested attributes (we cannot provide anything actually).
     */
    switch (enmAdditionalAttribs)
    {
        case RTFSOBJATTRADD_NOTHING:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_NOTHING;
            break;

        case RTFSOBJATTRADD_UNIX:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_UNIX;
            pObjInfo->Attr.u.Unix.uid             = ~0U;
            pObjInfo->Attr.u.Unix.gid             = ~0U;
            pObjInfo->Attr.u.Unix.cHardlinks      = 1;
            pObjInfo->Attr.u.Unix.INodeIdDevice   = 0; /** @todo use volume serial number */
            pObjInfo->Attr.u.Unix.INodeId         = 0; /** @todo use fileid (see GetFileInformationByHandle). */
            pObjInfo->Attr.u.Unix.fFlags          = 0;
            pObjInfo->Attr.u.Unix.GenerationId    = 0;
            pObjInfo->Attr.u.Unix.Device          = 0;
            break;

        case RTFSOBJATTRADD_UNIX_OWNER:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_OWNER;
            pObjInfo->Attr.u.UnixOwner.uid        = ~0U;
            pObjInfo->Attr.u.UnixOwner.szName[0]  = '\0'; /** @todo return something sensible here. */
            break;

        case RTFSOBJATTRADD_UNIX_GROUP:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_UNIX_GROUP;
            pObjInfo->Attr.u.UnixGroup.gid        = ~0U;
            pObjInfo->Attr.u.UnixGroup.szName[0]  = '\0';
            break;

        case RTFSOBJATTRADD_EASIZE:
            pObjInfo->Attr.enmAdditional          = RTFSOBJATTRADD_EASIZE;
            pObjInfo->Attr.u.EASize.cb            = 0;
            break;

        default:
            AssertMsgFailed(("Impossible!\n"));
            return VERR_INTERNAL_ERROR;
    }

    return VINF_SUCCESS;
}