/*++

Copyright (c) 1998  Microsoft Corporation

Module Name:

    curdir.c

Abstract:

    This module implements the directory commands.

Author:

    Wesley Witt (wesw) 21-Oct-1998

Revision History:

--*/

#include "cmdcons.h"
#pragma hdrstop


//
// Each entry in _CurDirs always starts and ends with a \.
//

LPWSTR _CurDirs[26];
WCHAR _CurDrive;
LPWSTR _NtDrivePrefixes[26];
BOOLEAN AllowAllPaths;


VOID
RcAddDrive(
    WCHAR DriveLetter
    )
{
    OBJECT_ATTRIBUTES Obja;
    UNICODE_STRING UnicodeString;
    WCHAR name[20];
    HANDLE Handle;
    NTSTATUS Status;

    ASSERT(_NtDrivePrefixes[(int)(DriveLetter - L'A')] == NULL);

    swprintf(name,L"\\DosDevices\\%c:", DriveLetter);

    INIT_OBJA(&Obja, &UnicodeString, name);

    Status = ZwOpenSymbolicLinkObject(&Handle, READ_CONTROL | SYMBOLIC_LINK_QUERY, &Obja);

    if (NT_SUCCESS(Status)) {
         ZwClose(Handle);
         _NtDrivePrefixes[(int)(DriveLetter - L'A')] = SpDupStringW(name);
    }
        
}



VOID
RcRemoveDrive(
    WCHAR DriveLetter
    )
{

    ASSERT(_NtDrivePrefixes[(int)(DriveLetter - L'A')] != NULL);
    
    SpMemFree(_NtDrivePrefixes[(int)(DriveLetter - L'A')]);
    _NtDrivePrefixes[(int)(DriveLetter - L'A')] = NULL;
}



VOID
RcInitializeCurrentDirectories(
    VOID
    )
{
    unsigned i;

    RtlZeroMemory( _CurDirs, sizeof(_CurDirs) );
    RtlZeroMemory( _NtDrivePrefixes, sizeof(_NtDrivePrefixes) );

    //
    // Initially, the current directory on all drives
    // is the root.
    //
    for( i=0; i<26; i++ ) {
        _CurDirs[i] = SpDupStringW(L"\\");
    }

    //
    // Now go set up the NT drive prefixes for each drive in the system.
    // For each drive letter, we see whether it exists in the \DosDevices
    // directory as a symbolic link.
    //
    for( i=0; i<26; i++ ) {    
        RcAddDrive((WCHAR)(i+L'A'));
    }

    //
    // NOTE: need to determine this by tracking the lowest
    // valid drive letter from the loop above, taking into account
    // floppy drives.
    //
    //
    _CurDrive = L'C';

    // fixed by using the drive letter for the selected install of NT
    // this is done in  in logon.c .



    return;
}


VOID
RcTerminateCurrentDirectories(
    VOID
    )
{
    unsigned i;

    for( i=0; i<26; i++ ) {
        if( _CurDirs[i] ) {
            SpMemFree(_CurDirs[i]);
            _CurDirs[i] = NULL;
        }
        if( _NtDrivePrefixes[i] ) {
            SpMemFree(_NtDrivePrefixes[i]);
            _NtDrivePrefixes[i] = NULL;
        }
    }
}


BOOLEAN
RcFormFullPath(
    IN  LPCWSTR PartialPath,
    OUT LPWSTR  FullPath,
    IN  BOOLEAN NtPath
    )

/*++

Routine Description:

    This routine is similar to the Win32 GetFullPathName() API.
    It takes an arbitrary pathspec and converts it to a full one,
    by merging in the current drive and directory if necessary.
    The output is a fully-qualified NT pathname equivalent to
    the partial spec given.

    Processing includes all your favorite Win32isms, including
    collapsing adjacent dots and slashes, stripping trailing spaces,
    handling . and .., etc.

Arguments:

    PartialPath - supplies a (dos-style) path spec of arbitrary qualification.

    FullPath - receives the equivalent fully-qualified NT path. The caller
        must ensure that this buffer is large enough.

    NtPath - if TRUE, we want a fully canonicalized NT path. Otherwise we want
        a DOS path.

Return Value:

    FALSE if failure, indicating an invalid drive spec or syntactically
    invalid path. TRUE otherwise.

--*/

{
    unsigned len;
    unsigned len2;
    LPCWSTR Prefix;
    PDISK_REGION Region;
    WCHAR Buffer[MAX_PATH*2];

    //
    // The first thing we do is to form the fully qualified path
    // by merging in the current drive and directory, if necessary.
    //
    // Check for leading drive in the form X:.
    //
    if((wcslen(PartialPath) >= 2) && (PartialPath[1] == L':') && RcIsAlpha(PartialPath[0])) {
        //
        // Got leading drive, transfer it into the target.
        //
        FullPath[0] = PartialPath[0];
        PartialPath += 2;
    } else {
        //
        // No leading drive, use current drive.
        //
        FullPath[0] = _CurDrive;
    }

    //
    // Make sure we've got a drive we think is valid.
    //
    Prefix = _NtDrivePrefixes[RcToUpper(FullPath[0])-L'A'];
    if(!Prefix) {
        return(FALSE);
    }

    FullPath[1] = L':';
    FullPath[2] = 0;

    //
    // Now deal with the path part. If the next character in the input
    // is \ then we have a rooted path, otherwise we need to merge in
    // the current directory for the drive.
    //
    if(PartialPath[0] != L'\\') {
        wcscat(FullPath,_CurDirs[RcToUpper(FullPath[0])-L'A']);
    }

    wcscat(FullPath,PartialPath);

    //
    // Disallow ending with \ except for the root.
    //
    len = wcslen(FullPath);

    if((len > 3) && (FullPath[len-1] == L'\\')) {
        FullPath[len-1] = 0;
    }

    //
    // Now that we've done this, we need to call RtlGetFullPathName_U
    // to get full win32 naming semantics, for example, stripping
    // trailing spaces, coalescing adjacent dots, processing . and .., etc.
    // We get at that API via setupdd.sys.
    //
    if(!NT_SUCCESS(SpGetFullPathName(FullPath))) {
        return(FALSE);
    }

    len = wcslen(FullPath) * sizeof(WCHAR);
    
    //
    // check if the path is too long to be 
    // handled by our routines [MAX_PATH*2] limit
    //
    // Note : RcGetNTFileName is called irrespective of whether caller
    // requested it or not to do proper error handling at the caller.
    //
    if ((len < sizeof(Buffer)) && RcGetNTFileName(FullPath, Buffer)){       
        if (NtPath)
            wcscpy(FullPath, Buffer);
    }
    else
        return FALSE;

    return TRUE;
}


VOID
RcGetCurrentDriveAndDir(
    OUT LPWSTR Output
    )
{
    ULONG len;

    Output[0] = _CurDrive;
    Output[1] = L':';
    wcscpy(Output+2,_CurDirs[_CurDrive-L'A']);

    //
    // Strip off trailing \ except in root case.
    //
    len = wcslen(Output);
    if( (len > 3) && (Output[len-1] == L'\\') ) {
        Output[len-1] = 0;
    }
}


WCHAR
RcGetCurrentDriveLetter(
    VOID
    )
{
    return(_CurDrive);
}


BOOLEAN
RcIsDriveApparentlyValid(
    IN WCHAR DriveLetter
    )
{
    return((BOOLEAN)(_NtDrivePrefixes[RcToUpper(DriveLetter)-L'A'] != NULL));
}


ULONG
RcCmdSwitchDrives(
    IN WCHAR DriveLetter
    )
{
    //
    // If there's no NT equivalent for this drive, then we can't
    // switch to it.
    //
    if( !RcIsDriveApparentlyValid(DriveLetter) ) {
        RcMessageOut(MSG_INVALID_DRIVE);
        return 1;
    }

    //
    // NOTE should we attempt to open the root of the drive,
    // so we can mimic cmd.exe's behavior of refusing to set
    // the current drive when say there's no floppy in the drive?
    // There's really no great reason to do this except that it might
    // be a little less confusing for the user.
    //
    // No.
    //

    _CurDrive = RcToUpper(DriveLetter);

    return 1;
}


ULONG
RcCmdChdir(
    IN PTOKENIZED_LINE TokenizedLine
    )
{
    unsigned u;
    WCHAR *p,*Arg;
    HANDLE Handle;
    IO_STATUS_BLOCK IoStatusBlock;
    UNICODE_STRING UnicodeString;
    OBJECT_ATTRIBUTES Obja;
    NTSTATUS Status;


    if (RcCmdParseHelp( TokenizedLine, MSG_CHDIR_HELP )) {
        return 1;
    }

    if (TokenizedLine->TokenCount == 1) {
        RcGetCurrentDriveAndDir(_CmdConsBlock->TemporaryBuffer);        
        RcRawTextOut(_CmdConsBlock->TemporaryBuffer,-1);
        return 1;
    }

    p = _CmdConsBlock->TemporaryBuffer;

    //
    // Get the argument. Special case x:, to print out the
    // current directory on that drive.
    //
    Arg = TokenizedLine->Tokens->Next->String;
    if(RcIsAlpha(Arg[0]) && (Arg[1] == L':') && (Arg[2] == 0)) {

        Arg[0] = RcToUpper(Arg[0]);
        u = Arg[0] - L'A';

        if(_NtDrivePrefixes[u] && _CurDirs[u]) {
            RcTextOut(Arg);

            //
            // Strip off the terminating \ except in root case.
            //
            wcscpy(p,_CurDirs[u]);
            u = wcslen(p);
            if((u > 1) && (p[u-1] == L'\\')) {
                p[u-1] = 0;
            }
            RcTextOut(p);
            RcTextOut(L"\r\n");

        } else {
            RcMessageOut(MSG_INVALID_DRIVE);
        }

        return 1;
    }

    //
    // Got a new directory spec. Canonicalize it to a fully qualified
    // DOS-style path. Check the drive to make sure it's legal.
    //
    if(!RcFormFullPath(Arg,p,FALSE)) {
        RcMessageOut(MSG_INVALID_PATH);
        return 1;
    }

    if(!_NtDrivePrefixes[RcToUpper(p[0])-L'A']) {
        RcMessageOut(MSG_INVALID_DRIVE);
        return 1;
    }

    //
    // Check the directory to make sure it exists.
    //
    if(!RcFormFullPath(Arg,p,TRUE)) {
        RcMessageOut(MSG_INVALID_PATH);
        return 1;
    }

    INIT_OBJA(&Obja,&UnicodeString,p);

    Status = ZwOpenFile(
                &Handle,
                FILE_READ_ATTRIBUTES,
                &Obja,
                &IoStatusBlock,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                FILE_DIRECTORY_FILE
                );

    if(!NT_SUCCESS(Status)) {
        RcNtError(Status,MSG_INVALID_PATH);
        return 1;
    }

    ZwClose(Handle);

    //
    // OK, it's a valid directory on a valid drive.
    // Form a path that starts and ends with \.
    //
    if(!RcFormFullPath(Arg,p,FALSE)) {
        RcMessageOut(MSG_INVALID_PATH);
        return 1;
    }

    if (!RcIsPathNameAllowed(p,TRUE,FALSE)) {
        RcMessageOut(MSG_ACCESS_DENIED);
        return 1;
    }

    p += 2;  // skip x:
    u = wcslen(p);

    if(!u || (p[u-1] != L'\\')) {
        p[u] = L'\\';
        p[u+1] = 0;
    }

    u = RcToUpper(p[-2]) - L'A';
    if(_CurDirs[u]) {
        SpMemFree(_CurDirs[u]);
    }
    _CurDirs[u] = SpDupStringW(p);

    return 1;
}

ULONG
RcCmdSystemRoot(
    IN PTOKENIZED_LINE TokenizedLine
    )
{
    ULONG u;
    WCHAR buf[MAX_PATH];


    if (RcCmdParseHelp( TokenizedLine, MSG_SYSTEMROOT_HELP )) {
        return 1;
    }

    //
    // set the current drive to the correct one.
    //

    if (SelectedInstall == NULL) {
        return 1;
    }

    _CurDrive = SelectedInstall->DriveLetter;

    //
    // set the current dir to the correct one.
    //
    RtlZeroMemory( buf, sizeof(buf) );

    wcscat( buf, L"\\" );
    wcscat( buf, SelectedInstall->Path );
    wcscat( buf, L"\\" );

    u = RcToUpper(SelectedInstall->DriveLetter) - L'A';
    if( _CurDirs[u] ) {
        SpMemFree(_CurDirs[u]);
    }
    _CurDirs[u] = SpDupStringW( buf );

    return 1;
}