/*++

Copyright (c) 1990  Microsoft Corporation

Module Name:

    nt_obj.c

Abstract:

    1. Contains routines to access symbolic link objects

    2. Contains routines to convert between the DOS and ARC Name space

Author:

    Sunil Pai (sunilp) 20-Nov-1991

--*/


#include <nt.h>
#include <ntrtl.h>
#include <nturtl.h>
#include <windows.h>
#include <string.h>
#include "rc_ids.h"
#include "patchdll.h"

#define BUFFER_SIZE      1024
#define SYMLINKTYPE      L"SymbolicLink"
#define ARCNAMEOBJDIR    L"\\ArcName"
#define DOSDEVOBJDIR     L"\\DosDevices"

extern CHAR ReturnTextBuffer[1024];

//
// Args[0]: $(SystemPartition)
// Args[1]: $(OsLoader)
// Args[2]: $(OsLoadPartition)
// Args[3]: $(OsLoadFilename)
// Args[4]: $(VolumeList)


BOOL
GetOsLoaderDest(
    IN  DWORD cArgs,
    IN  LPSTR Args[],
    OUT LPSTR *TextOut
    )
{

#if i386
    extern  WCHAR   x86DetermineSystemPartition(VOID);
            WCHAR   UnicodeDrive[4];
            CHAR    AnsiDrive[8];

    *TextOut = ReturnTextBuffer;
    SetReturnText("C:");
    UnicodeDrive[0] = x86DetermineSystemPartition();
    UnicodeDrive[1] = L':';
    UnicodeDrive[2] = L'\\';
    UnicodeDrive[3] = L'\0';

    if (WideCharToMultiByte(
        CP_ACP,
        0,
        UnicodeDrive,
        4,
        AnsiDrive,
        8,
        NULL,
        NULL) ) {

        SetReturnText(AnsiDrive);

    }
    return( TRUE );

#else

    RGSZ  rgszSystemPartition;
    RGSZ  rgszOsLoader;
    RGSZ  rgszOsLoadPartition;
    RGSZ  rgszOsLoadFilename;
    SZ    szOsLoader, szFileName, szDir;

    CHAR  szDrive[] = "?:";
    DWORD Disk;

    DWORD dwSystemPartition = 0;
    DWORD dwOsLoader = 0;
    DWORD dwOsLoadPartition = 0;
    DWORD dwOsLoadFilename = 0;

    CHAR  WindowsDirectory[MAX_PATH]  , ArcWindowsDirectory[MAX_PATH];
    CHAR  SystemPartition[MAX_PATH]   , ProcessedArcSysPart[MAX_PATH];
    CHAR  ArcForDosDrive[MAX_PATH]    , ProcessedArcForDosDrive[MAX_PATH];
    CHAR  OsLoaderDest[MAX_PATH];

    BOOL  Status = TRUE, Done, Found;



    *TextOut = ReturnTextBuffer;
    if(cArgs != 4) {
        SetErrorText(IDS_ERROR_BADARGS);
        return(FALSE);
    }

    //
    // Find the windows directory
    //

    if( !GetWindowsDirectory( WindowsDirectory, MAX_PATH ) ) {
        SetErrorText(IDS_ERROR_GETWINDOWSDIR);
        return(FALSE);
    }

    //
    // Find the arcname for the windows dir
    //

    if (!DosPathToArcPathWorker( WindowsDirectory, ArcWindowsDirectory )) {
        return( FALSE );
    }

    //
    // Break all the lists into arrays
    //

    rgszSystemPartition = RgszFromSzListValue(Args[0]);
    rgszOsLoader        = RgszFromSzListValue(Args[1]);
    rgszOsLoadPartition = RgszFromSzListValue(Args[2]);
    rgszOsLoadFilename  = RgszFromSzListValue(Args[3]);

    if( !( rgszSystemPartition &&
           rgszOsLoader        &&
           rgszOsLoadPartition &&
           rgszOsLoadFilename
         )
      ) {

        Status = FALSE;
        SetErrorText(IDS_ERROR_DLLOOM);
        goto r0;
    }

    //
    // Go through the NVRAM Variables in tandem.  Note that the last one in
    // each list gets reused for the others.
    //

    // First ensure that atleast one component exists for each of the arc
    // boot vars

    if( rgszSystemPartition[0]  == NULL  ||
        rgszOsLoader[0]         == NULL  ||
        rgszOsLoadPartition[0]  == NULL  ||
        rgszOsLoadFilename[0]   == NULL
      ) {
        Status = FALSE;
        SetErrorText(IDS_ERROR_NONVRAMVARS);
        goto r0;
    }

    //
    // Pick up set by set till done
    //

    Found = FALSE;
    while( TRUE ) {

        CHAR ArcOsLoadWindowsDir[MAX_PATH];
        BOOL AnyAdvanced = FALSE;

        //
        // Combine the current OsLoadPartition with the OsLoadFilename
        // and compare it with the windows directory arc
        //

        strcpy( ArcOsLoadWindowsDir, rgszOsLoadPartition[dwOsLoadPartition] );
        strcat( ArcOsLoadWindowsDir, rgszOsLoadFilename[dwOsLoadFilename] );
        if( ArcOsLoadWindowsDir[lstrlen(ArcOsLoadWindowsDir) - 1] == '\\' ) {
            ArcOsLoadWindowsDir[lstrlen(ArcOsLoadWindowsDir) - 1] = '\0';
        }

        if( CompareArcNames( ArcOsLoadWindowsDir, ArcWindowsDirectory ) ) {
            Found = TRUE;
            break;
        }

        //
        // Advance to next step
        //

        if ( rgszSystemPartition[dwSystemPartition+1] ) { dwSystemPartition++ ; AnyAdvanced = TRUE ; }
        if ( rgszOsLoader[dwOsLoader+1]               ) { dwOsLoader++        ; AnyAdvanced = TRUE ; }
        if ( rgszOsLoadPartition[dwOsLoadPartition+1] ) { dwOsLoadPartition++ ; AnyAdvanced = TRUE ; }
        if ( rgszOsLoadFilename[dwOsLoadFilename+1]   ) { dwOsLoadFilename++  ; AnyAdvanced = TRUE ; }

        if ( !AnyAdvanced ) {
            break;
        }
    }

    if( !Found ) {
        Status = FALSE;
        SetErrorText(IDS_ERROR_OSLOADNOTFND);
        goto r0;
    }

    //
    // Set has been found, extract the osloader destination and the system
    // partition arc variables.
    //

    szOsLoader = rgszOsLoader[dwOsLoader];
    szFileName = strrchr( szOsLoader, '\\' );
    szDir      = strchr( szOsLoader, '\\' );
    if( !szFileName ) {
        Status = FALSE;
        SetErrorText(IDS_ERROR_BADOSLNVR);
        goto r0;
    }
    strncpy( SystemPartition, szOsLoader, (int)(szDir - szOsLoader) );
    SystemPartition[ szDir - szOsLoader ] = '\0';

    //
    // Process the names and run through the drive list to determine a
    // match
    //

    ProcessArcName( SystemPartition, ProcessedArcSysPart );
    Found = FALSE;

    for( Disk = 0; Disk < 26 ; Disk++ ) {
        *szDrive = (CHAR)(Disk) + (CHAR)'A';
        if( DosPathToArcPathWorker( szDrive, ArcForDosDrive ) ) {
            ProcessArcName( ArcForDosDrive, ProcessedArcForDosDrive );
            if( !lstrcmpi( ProcessedArcSysPart, ProcessedArcForDosDrive ) ) {
                Found = TRUE;
                strcpy( OsLoaderDest, szDrive );
                strncat( OsLoaderDest, szDir, (int)(szFileName - szDir) );
                break;
            }
        }
    }
    if( !Found ) {
        Status = FALSE;
        SetErrorText(IDS_ERROR_DOSNOTEXIST);
        goto r0;
    }

    Status = TRUE;
    SetReturnText( OsLoaderDest );

r0:
    if( rgszSystemPartition ) { RgszFree( rgszSystemPartition ); }
    if( rgszOsLoader        ) { RgszFree( rgszOsLoader        ); }
    if( rgszOsLoadPartition ) { RgszFree( rgszOsLoadPartition ); }
    if( rgszOsLoadFilename  ) { RgszFree( rgszOsLoadFilename  ); }

    return( Status );

#endif
}

VOID
ProcessArcName(
   IN LPSTR NameIn,
   IN LPSTR NameOut
   )
{
   DWORD i;

   for(i = 0; (NameOut[i] = *NameIn) != '\0'; i++, NameIn++) {
       if( *NameIn == '(' && *(NameIn + 1) == ')' ) {
          NameOut[++i] = '0';
       }
   }
   return;
}



BOOL
CompareArcNames(
   IN LPSTR Name1,
   IN LPSTR Name2
   )
{
   CHAR ProcessedName1[MAX_PATH];
   CHAR ProcessedName2[MAX_PATH];

   ProcessArcName( Name1, ProcessedName1 );
   ProcessArcName( Name2, ProcessedName2 );
   return( !lstrcmpi( ProcessedName1, ProcessedName2 ) );
}




BOOL
DosPathToArcPathWorker(
    IN  LPSTR DosPath,
    OUT LPSTR ArcPath
    )
{
    CHAR  Drive[] = "\\DosDevices\\?:";
    WCHAR NtNameDrive[MAX_PATH];
    WCHAR ArcNameDrive[MAX_PATH];

    ANSI_STRING       AnsiString;

    UNICODE_STRING    Drive_U, NtNameDrive_U, ObjDir_U, ArcNameDrive_U;
    ANSI_STRING       ArcNameDrive_A;

    BOOL              bStatus;
    NTSTATUS          Status;

    //
    // Validate the DOS Path passed in
    //

    if (lstrlen(DosPath) < 2 || DosPath[1] != ':') {
        SetErrorText(IDS_ERROR_INVALIDDISK);
        return ( FALSE );
    }

    //
    // Extract the drive
    //

    Drive[12] = DosPath[0];

    //
    // Get Unicode string for the drive
    //

    RtlInitAnsiString(&AnsiString, Drive);
    Status = RtlAnsiStringToUnicodeString(
                 &Drive_U,
                 &AnsiString,
                 TRUE
                 );

    if (!NT_SUCCESS(Status)) {
        SetErrorText(IDS_ERROR_RTLOOM);
        return ( FALSE );
    }


    //
    // Initialise Unicode string to hold the Nt Name for the Drive
    //

    NtNameDrive_U.Buffer        = NtNameDrive;
    NtNameDrive_U.Length        = 0;
    NtNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);

    //
    // Initialise Unicode string to hold the ArcName for the drive
    //

    ArcNameDrive_U.Buffer        = ArcNameDrive;
    ArcNameDrive_U.Length        = 0;
    ArcNameDrive_U.MaximumLength = MAX_PATH * sizeof(WCHAR);

    //
    // Initialise Unicode string to hold the \\ArcName objdir name
    //

    RtlInitUnicodeString(&ObjDir_U, ARCNAMEOBJDIR);

    //
    // Query symbolic link of the drive
    //

    bStatus = GetSymbolicLinkTarget(&Drive_U, &NtNameDrive_U);
    RtlFreeUnicodeString(&Drive_U);
    if (!bStatus) {
        return ( FALSE );
    }

    //
    // Find the object in the arcname directory
    //

    bStatus = GetSymbolicLinkSource(&ObjDir_U, &NtNameDrive_U, &ArcNameDrive_U);
    if (!bStatus) {

        return ( FALSE );
    }

    //
    // Convert the Unicode ArcName for drive to ansi
    //

    Status = RtlUnicodeStringToAnsiString(
                 &ArcNameDrive_A,
                 &ArcNameDrive_U,
                 TRUE
                 );

    if (!NT_SUCCESS(Status)) {
        SetErrorText(IDS_ERROR_RTLOOM);
        return ( FALSE );
    }

    //
    // Copy the drive to the output variable
    //

    ArcNameDrive_A.Buffer[ArcNameDrive_A.Length] = '\0'; //Null terminate
    lstrcpy(ArcPath, ArcNameDrive_A.Buffer);
    RtlFreeAnsiString(&ArcNameDrive_A);

    //
    // concatenate the rest of the DosPath to this ArcPath
    //

    lstrcat(ArcPath, DosPath+2);

    return(TRUE);

}



/*
 *  OBJECT MANAGEMENT ROUTINES
 */

BOOL
GetSymbolicLinkSource(
    IN  PUNICODE_STRING pObjDir_U,
    IN  PUNICODE_STRING pTarget_U,
    OUT PUNICODE_STRING pSource_U
    )
{
    PCHAR             Buffer, FullNameObject, ObjectLink, SavedMatch;
    UNICODE_STRING    ObjName_U, ObjLink_U;

    UNICODE_STRING    SavedMatch_U;
    BOOLEAN           IsMatchSaved = FALSE;

    OBJECT_ATTRIBUTES Attributes;

    HANDLE            DirectoryHandle;
    ULONG             Context = 0;
    ULONG             ReturnedLength;
    BOOLEAN           RestartScan = TRUE;
    BOOLEAN           ReturnSingleEntry = TRUE;  //LATER change this to FALSE

    POBJECT_DIRECTORY_INFORMATION DirInfo;

    NTSTATUS          Status;
    BOOL              bStatus;

    //
    // Open the object directory.
    //

    InitializeObjectAttributes(
        &Attributes,
        pObjDir_U,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    Status = NtOpenDirectoryObject(
                 &DirectoryHandle,
                 STANDARD_RIGHTS_READ | DIRECTORY_QUERY | DIRECTORY_TRAVERSE,
                 &Attributes
                 );

    if (!NT_SUCCESS( Status ) ) {
        SetErrorText(IDS_ERROR_OBJDIROPEN);
        return ( FALSE );
    }


    //
    //  Find the symbolic link objects in the directory and query them for
    //  a match with the string passed in
    //

    //
    //  Allocate a buffer to query the directory objects
    //
    if ((Buffer = LocalAlloc(0, BUFFER_SIZE)) == NULL) {
        SetErrorText(IDS_ERROR_DLLOOM);
        NtClose(DirectoryHandle);
        return ( FALSE );
    }

    //
    //  Form a Unicode string object to hold the symbolic link objects found
    //  in the object directory
    //

    if ((FullNameObject = LocalAlloc(0, MAX_PATH * sizeof(WCHAR))) == NULL) {
        SetErrorText(IDS_ERROR_DLLOOM);
        LocalFree (Buffer);
        NtClose (DirectoryHandle);
        return ( FALSE );
    }

    ObjName_U.Buffer        = (PWSTR)FullNameObject;
    ObjName_U.Length        = 0;
    ObjName_U.MaximumLength = MAX_PATH * sizeof(WCHAR);

    //
    //  Form a Unicode string object to hold the symbolic link objects found
    //  in the object directory
    //

    if ((ObjectLink = LocalAlloc(0, MAX_PATH * sizeof(WCHAR))) == NULL) {
        SetErrorText(IDS_ERROR_DLLOOM);
        LocalFree (Buffer);
        LocalFree (FullNameObject);
        NtClose (DirectoryHandle);
        return ( FALSE );
    }

    ObjLink_U.Buffer        = (PWSTR)ObjectLink;
    ObjLink_U.Length        = 0;
    ObjLink_U.MaximumLength = MAX_PATH * sizeof(WCHAR);


    if ((SavedMatch = LocalAlloc(0, MAX_PATH * sizeof(WCHAR))) == NULL) {
        SetErrorText(IDS_ERROR_DLLOOM);
        LocalFree (Buffer);
        LocalFree (FullNameObject);
        LocalFree (ObjectLink);
        NtClose (DirectoryHandle);
        return ( FALSE );
    }

    SavedMatch_U.Buffer        = (PWSTR)SavedMatch;
    SavedMatch_U.Length        = 0;
    SavedMatch_U.MaximumLength = MAX_PATH * sizeof(WCHAR);

    while (TRUE) {
        //
        //  Clear the buffer
        //
        RtlZeroMemory( Buffer, BUFFER_SIZE);

        //
        // repeatedly Query the directory objects till done
        //

        Status = NtQueryDirectoryObject(
                     DirectoryHandle,
                     Buffer,
                     BUFFER_SIZE,
                     ReturnSingleEntry,  // fetch more than one entry
                     RestartScan,        // start rescan true on first go
                     &Context,
                     &ReturnedLength
                     );


        //
        //  Check the Status of the operation.
        //

        if (!NT_SUCCESS(Status) && (Status != STATUS_MORE_ENTRIES)) {
            if (Status == STATUS_NO_MORE_FILES || Status == STATUS_NO_MORE_ENTRIES) {
                SetErrorText(IDS_ERROR_INVALIDDISK);
            }
            else {
                SetErrorText(IDS_ERROR_OBJDIRREAD);
            }
            LocalFree(Buffer);
            LocalFree(FullNameObject);
            LocalFree(ObjectLink);
            if(IsMatchSaved) {
                RtlCopyUnicodeString (pSource_U, &SavedMatch_U);
                LocalFree(SavedMatch);
                return(TRUE);
            }
            LocalFree(SavedMatch);
            return(FALSE);
        }

        //
        // Make sure that restart scan is false for next go
        //
        RestartScan = FALSE;


        //
        //  For every record in the buffer, see if the type of the object
        //  is a symbolic link
        //

        //
        //  Point to the first record in the buffer, we are guaranteed to have
        //  one otherwise Status would have been No More Files
        //

        DirInfo = (POBJECT_DIRECTORY_INFORMATION) Buffer;

        while (TRUE) {

            //
            //  Check if there is another record.  If there isn't, break out of
            //  the loop now
            //

            if (DirInfo->Name.Length == 0) {
                break;
            }

            //
            //  See if the object type is a symbolic link
            //
            if (IsSymbolicLinkType(&(DirInfo->TypeName))) {

                //
                // get full pathname of object
                //

                //
                // Check if we will overflow our buffer
                //
                if ((
                     pObjDir_U->Length      +
                     sizeof(WCHAR)         +
                     DirInfo->Name.Length  +
                     sizeof(WCHAR)
                    ) > ObjName_U.MaximumLength )  {

                    SetErrorText(IDS_ERROR_OBJNAMOVF);
                    LocalFree(Buffer);
                    LocalFree(FullNameObject);
                    LocalFree(ObjectLink);

                    if(IsMatchSaved) {
                        RtlCopyUnicodeString (pSource_U, &SavedMatch_U);
                        LocalFree(SavedMatch);
                        return(TRUE);
                    }
                    LocalFree(SavedMatch);
                    return( FALSE );

                }

                //
                // Copy the current object name over the the buffer prefixing
                // it with the \ArcName\ object directory name
                //

                RtlCopyUnicodeString ( &ObjName_U, pObjDir_U );
                RtlAppendUnicodeToString ( &ObjName_U, L"\\" );
                RtlAppendUnicodeStringToString ( &ObjName_U, &(DirInfo->Name));

                //
                // query the symbolic link target
                //

                ObjLink_U.Buffer        = (PWSTR)ObjectLink;
                ObjLink_U.Length        = 0;
                ObjLink_U.MaximumLength = MAX_PATH * sizeof(WCHAR);

                bStatus = GetSymbolicLinkTarget (&ObjName_U, &ObjLink_U);
                if (bStatus != TRUE) {
                    LocalFree(Buffer);
                    LocalFree(FullNameObject);
                    LocalFree(ObjectLink);

                    if(IsMatchSaved) {
                        RtlCopyUnicodeString (pSource_U, &SavedMatch_U);
                        LocalFree(SavedMatch);
                        return(TRUE);
                    }
                    LocalFree(SavedMatch);
                    return FALSE;
                }

                //
                // see if it compares to the name we are looking for
                //

                if (RtlEqualUnicodeString (pTarget_U, &ObjLink_U, TRUE)) {

#if i386
                    UNICODE_STRING Multi_U;

                    RtlInitUnicodeString(&Multi_U,L"multi(");
                    if(RtlPrefixUnicodeString(&Multi_U,&DirInfo->Name,TRUE)) {

                        RtlCopyUnicodeString(&SavedMatch_U,&DirInfo->Name);
                        IsMatchSaved = TRUE;

                    } else  // scsi, just return it.  Favor scsi over multi.
#endif
                    {
                        RtlCopyUnicodeString (pSource_U, &(DirInfo->Name));
                        LocalFree(Buffer);
                        LocalFree(FullNameObject);
                        LocalFree(ObjectLink);
                        LocalFree(SavedMatch);
                        return TRUE;
                    }
                }

            }

            //
            //  There is another record so advance DirInfo to the next entry
            //

            DirInfo = (POBJECT_DIRECTORY_INFORMATION) (((PCHAR) DirInfo) +
                          sizeof( OBJECT_DIRECTORY_INFORMATION ) );

        }  // while

    } // while

    return ( FALSE );

} // getarcname



/* Checks to see if a given object type is a symbolic link type */

BOOL
IsSymbolicLinkType(
    IN PUNICODE_STRING Type
    )
{
    UNICODE_STRING  TypeName;

    //
    // Check the length of the string
    //
    if (Type->Length == 0) {
        return FALSE;
    }

    //
    // compare it to the symbolic link type name
    //
    RtlInitUnicodeString(&TypeName, SYMLINKTYPE);
    return (RtlEqualUnicodeString(Type, &TypeName, TRUE));
}





BOOL
GetSymbolicLinkTarget(
    IN     PUNICODE_STRING pSourceString_U,
    IN OUT PUNICODE_STRING pDestString_U
    )
{

    NTSTATUS          Status;

    OBJECT_ATTRIBUTES Attributes;
    HANDLE            ObjectHandle;

    //
    // Initialise the object attributes structure for the symbolic
    // link object

    InitializeObjectAttributes(
        &Attributes,
        pSourceString_U,
        OBJ_CASE_INSENSITIVE,
        NULL,
        NULL
        );

    //
    // Open the symbolic link for link_query access
    //

    Status = NtOpenSymbolicLinkObject(
                 &ObjectHandle,
                 READ_CONTROL | SYMBOLIC_LINK_QUERY,
                 &Attributes
                 );

    if (!NT_SUCCESS(Status)) {
        SetErrorText(IDS_ERROR_SYMLNKOPEN);
        return ( FALSE );
    }


    //
    // query the symbolic link target
    //

    Status = NtQuerySymbolicLinkObject(
                 ObjectHandle,
                 pDestString_U,
                 NULL
                 );

    if (!NT_SUCCESS(Status)) {
        SetErrorText(IDS_ERROR_SYMLNKREAD);
        NtClose(ObjectHandle);
        return(FALSE);
    }

    //
    // close the link object
    //

    NtClose(ObjectHandle);

    //
    // return the link target
    //
    return ( TRUE );
}