/*
*  DEL.C - del internal command.
*
*
*  History:
*
*    06/29/98 (Rob Lake rlake@cs.mun.ca)
*        rewrote del to support wildcards
*        added my name to the contributors
*
*    07/13/98 (Rob Lake)
*        fixed bug that caused del not to delete file with out
*        attribute. moved set, del, ren, and ver to there own files
*
*    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
*        added config.h include
*
*    09-Dec-1998 (Eric Kohl)
*        Fixed command line parsing bugs.
*
*    21-Jan-1999 (Eric Kohl)
*        Started major rewrite using a new structure.
*
*    03-Feb-1999 (Eric Kohl)
*        First working version.
*
*    30-Mar-1999 (Eric Kohl)
*        Added quiet ("/Q"), wipe ("/W") and zap ("/Z") option.
*
*    06-Nov-1999 (Eric Kohl)
*        Little fix to keep DEL quiet inside batch files.
*
*    28-Jan-2004 (Michael Fritscher <michael@fritscher.net>)
*        Added prompt ("/P"), yes ("/Y") and wipe("/W") option.
*
*    22-Jun-2005 (Brandon Turner <turnerb7@msu.edu>)
*        Added exclusive deletion "del * -abc.txt -text*.txt"
*
*    22-Jun-2005 (Brandon Turner <turnerb7@msu.edu>)
*        Implemented /A   example "del /A:H /A:-R *.exe -ping.exe"
*
*    07-Aug-2005
*        Removed the exclusive deletion (see two comments above) because '-' is a valid file name character.
*        Optimized the recursive deletion in directories.
*        Preload some nice strings.
*/

#include "precomp.h"

#ifdef INCLUDE_CMD_DEL


enum
{
    DEL_ATTRIBUTES = 0x001,   /* /A */
    DEL_NOTHING    = 0x004,   /* /N */
    DEL_PROMPT     = 0x008,   /* /P */
    DEL_QUIET      = 0x010,   /* /Q */
    DEL_SUBDIR     = 0x020,   /* /S */
    DEL_TOTAL      = 0x040,   /* /T */
    DEL_WIPE       = 0x080,   /* /W */
    DEL_EMPTYDIR   = 0x100,   /* /X : not implemented */
    DEL_YES        = 0x200,   /* /Y */
    DEL_FORCE      = 0x800    /* /F */
};

enum
{
    ATTR_ARCHIVE     = 0x001,   /* /A:A */
    ATTR_HIDDEN      = 0x002,   /* /A:H */
    ATTR_SYSTEM      = 0x004,   /* /A:S */
    ATTR_READ_ONLY   = 0x008,   /* /A:R */
    ATTR_N_ARCHIVE   = 0x010,   /* /A:-A */
    ATTR_N_HIDDEN    = 0x020,   /* /A:-H */
    ATTR_N_SYSTEM    = 0x040,   /* /A:-S */
    ATTR_N_READ_ONLY = 0x080    /* /A:-R */
};

static TCHAR szDeleteWipe[RC_STRING_MAX_SIZE];
static TCHAR CMDPath[MAX_PATH];

static BOOLEAN StringsLoaded = FALSE;

static VOID LoadStrings(VOID)
{
    LoadString( CMD_ModuleHandle, STRING_DELETE_WIPE, szDeleteWipe, RC_STRING_MAX_SIZE);
    GetModuleFileName(NULL, CMDPath, MAX_PATH);
    StringsLoaded = TRUE;
}

static BOOL
RemoveFile (LPTSTR lpFileName, DWORD dwFlags, WIN32_FIND_DATA* f)
{
    /*This function is called by CommandDelete and
    does the actual process of deleting the single
    file*/
    if (CheckCtrlBreak(BREAK_INPUT))
        return 1;

    /*check to see if it is read only and if this is done based on /A
      if it is done by file name, access is denied. However, if it is done
      using the /A switch you must un-read only the file and allow it to be
      deleted*/
    if ((dwFlags & DEL_ATTRIBUTES) || (dwFlags & DEL_FORCE))
    {
        if (f->dwFileAttributes & FILE_ATTRIBUTE_READONLY)
        {
            /*setting file to normal, not saving old attrs first
              because the file is going to be deleted anyways
              so the only thing that matters is that it isn't
              read only.*/
            SetFileAttributes(lpFileName,FILE_ATTRIBUTE_NORMAL);
        }
    }

    if (dwFlags & DEL_WIPE)
    {
        HANDLE file;
        DWORD temp;
#define BufferSize 65536
        BYTE buffer[BufferSize];
        LONGLONG i;
        LARGE_INTEGER FileSize;

        FileSize.u.HighPart = f->nFileSizeHigh;
        FileSize.u.LowPart = f->nFileSizeLow;

        for(i = 0; i < BufferSize; i++)
        {
            buffer[i]=rand() % 256;
        }
        file = CreateFile (lpFileName, GENERIC_WRITE, 0, NULL, OPEN_EXISTING,  FILE_FLAG_WRITE_THROUGH, NULL);
        if (file != INVALID_HANDLE_VALUE)
        {
            for(i = 0; i < (FileSize.QuadPart - BufferSize); i += BufferSize)
            {
                WriteFile (file, buffer, BufferSize, &temp, NULL);
                ConOutPrintf (_T("%I64d%% %s\r"),(i * (LONGLONG)100)/FileSize.QuadPart,szDeleteWipe);
            }
            WriteFile (file, buffer, (DWORD)(FileSize.QuadPart - i), &temp, NULL);
            ConOutPrintf (_T("100%% %s\n"),szDeleteWipe);
            CloseHandle (file);
        }
    }

    return DeleteFile (lpFileName);
}


static DWORD
DeleteFiles(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
{
    TCHAR szFullPath[MAX_PATH];
    TCHAR szFileName[MAX_PATH];
    LPTSTR pFilePart;
    HANDLE hFile;
    WIN32_FIND_DATA f;
    BOOL bExclusion;
    INT res;
    DWORD dwFiles = 0;

    _tcscpy(szFileName, FileName);

    if (_tcschr (szFileName, _T('*')) == NULL &&
        IsExistingDirectory (szFileName))
    {
        /* If it doesnt have a \ at the end already then on needs to be added */
        if (szFileName[_tcslen(szFileName) -  1] != _T('\\'))
            _tcscat (szFileName, _T("\\"));
        /* Add a wildcard after the \ */
        _tcscat (szFileName, _T("*"));
    }

    if (!_tcscmp (szFileName, _T("*")) ||
        !_tcscmp (szFileName, _T("*.*")) ||
        (szFileName[_tcslen(szFileName) -  2] == _T('\\') && szFileName[_tcslen(szFileName) -  1] == _T('*')))
    {
        /* well, the user wants to delete everything but if they didnt yes DEL_YES, DEL_QUIET, or DEL_PROMPT
           then we are going to want to make sure that in fact they want to do that.  */

        if (!((*dwFlags & DEL_YES) || (*dwFlags & DEL_QUIET) || (*dwFlags & DEL_PROMPT)))
        {
            res = FilePromptYNA (STRING_DEL_HELP2);
            if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
                return 0x80000000;
            if (res == PROMPT_ALL)
                *dwFlags |= DEL_YES;
        }
    }

    GetFullPathName(szFileName,
                    MAX_PATH,
                    szFullPath,
                    &pFilePart);

    hFile = FindFirstFile(szFullPath, &f);
    if (hFile != INVALID_HANDLE_VALUE)
    {
        do
        {
            bExclusion = FALSE;

            /*if it is going to be excluded by - no need to check attrs*/
            if (*dwFlags & DEL_ATTRIBUTES && !bExclusion)
            {
                /*save if file attr check if user doesnt care about that attr anyways*/
                if (dwAttrFlags & ATTR_ARCHIVE && !(f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_HIDDEN && !(f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_SYSTEM && !(f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_READ_ONLY && !(f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_N_ARCHIVE && (f.dwFileAttributes & FILE_ATTRIBUTE_ARCHIVE))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_N_HIDDEN && (f.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_N_SYSTEM && (f.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM))
                    bExclusion = TRUE;
                if (dwAttrFlags & ATTR_N_READ_ONLY && (f.dwFileAttributes & FILE_ATTRIBUTE_READONLY))
                    bExclusion = TRUE;
            }
            if (bExclusion) continue;

            /* ignore directories */
            if (f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) continue;

            _tcscpy (pFilePart, f.cFileName);

            /* We cant delete ourselves */
            if (!_tcscmp (CMDPath,szFullPath)) continue;

            TRACE("Full filename: %s\n", debugstr_aw(szFullPath));

            /* ask for deleting */
            if (*dwFlags & DEL_PROMPT)
            {
                ConErrResPrintf(STRING_DEL_ERROR5, szFullPath);

                res = FilePromptYN (STRING_DEL_ERROR6);

                if ((res == PROMPT_NO) || (res == PROMPT_BREAK))
                {
                    nErrorLevel = 0;
                    continue;
                }
            }

            /*user cant ask it to be quiet and tell you what it did*/
            if (!(*dwFlags & DEL_QUIET) && !(*dwFlags & DEL_TOTAL))
            {
                ConErrResPrintf(STRING_DEL_ERROR7, szFullPath);
            }

            /* delete the file */
            if (*dwFlags & DEL_NOTHING) continue;

            if (RemoveFile (szFullPath, *dwFlags, &f))
            {
                dwFiles++;
            }
            else
            {
                ErrorMessage (GetLastError(), _T(""));
//              FindClose(hFile);
//              return -1;
            }
        }
        while (FindNextFile (hFile, &f));
        FindClose (hFile);
    }
    else error_sfile_not_found(szFullPath);
    return dwFiles;
}

static DWORD
ProcessDirectory(LPTSTR FileName, DWORD* dwFlags, DWORD dwAttrFlags)
{
    TCHAR szFullPath[MAX_PATH];
    LPTSTR pFilePart;
    LPTSTR pSearchPart;
    HANDLE hFile;
    WIN32_FIND_DATA f;
    DWORD dwFiles = 0;

    GetFullPathName(FileName,
                    MAX_PATH,
                    szFullPath,
                    &pFilePart);

    dwFiles = DeleteFiles(szFullPath, dwFlags, dwAttrFlags);
    if (dwFiles & 0x80000000) return dwFiles;

    if (*dwFlags & DEL_SUBDIR)
    {
        /* Get just the file name */
        pSearchPart = _tcsrchr(FileName,_T('\\'));
        if (pSearchPart != NULL)
            pSearchPart++;
        else
            pSearchPart = FileName;

        /* Get the full path to the file */
        GetFullPathName (FileName,MAX_PATH,szFullPath,NULL);

        /* strip the filename off of it */
        pFilePart = _tcsrchr(szFullPath, _T('\\'));
        if (pFilePart == NULL)
        {
            pFilePart = szFullPath;
        }
        else
        {
            pFilePart++;
        }

        _tcscpy(pFilePart, _T("*"));

        hFile = FindFirstFile(szFullPath, &f);
        if (hFile != INVALID_HANDLE_VALUE)
        {
            do
            {
                if (!(f.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) ||
                        !_tcscmp(f.cFileName, _T(".")) ||
                        !_tcscmp(f.cFileName, _T("..")))
                    continue;

                    _tcscpy(pFilePart, f.cFileName);
                    _tcscat(pFilePart, _T("\\"));
                    _tcscat(pFilePart, pSearchPart);

                    dwFiles +=ProcessDirectory(szFullPath, dwFlags, dwAttrFlags);
                    if (dwFiles & 0x80000000)
                    {
                        break;
                    }
            }
            while (FindNextFile (hFile, &f));
            FindClose (hFile);
        }
    }
    return dwFiles;
}



INT CommandDelete (LPTSTR param)
{
    /*cmd is the command that was given, in this case it will always be "del" or "delete"
    param is whatever is given after the command*/

    LPTSTR *arg = NULL;
    INT args;
    INT i;
    INT   nEvalArgs = 0; /* nunber of evaluated arguments */
    DWORD dwFlags = 0;
    DWORD dwAttrFlags = 0;
    DWORD dwFiles = 0;
    LONG ch;
    TCHAR szOrginalArg[MAX_PATH];

    /*checks the first two chars of param to see if it is /?
    this however allows the following command to not show help
    "del frog.txt /?" */

    if (!StringsLoaded)
    {
        LoadStrings();
    }

    if (!_tcsncmp (param, _T("/?"), 2))
    {
        ConOutResPaging(TRUE,STRING_DEL_HELP1);
        return 0;
    }

    nErrorLevel = 0;

    arg = split (param, &args, FALSE, FALSE);

    if (args == 0)
    {
        /* only command given */
        error_req_param_missing ();
        freep (arg);
        return 1;
    }
    /* check for options anywhere in command line */
    for (i = 0; i < args; i++)
    {
        if (*arg[i] == _T('/'))
        {
            /*found a command, but check to make sure it has something after it*/
            if (_tcslen (arg[i]) >= 2)
            {
                ch = _totupper (arg[i][1]);
                if (ch == _T('N'))
                {
                    dwFlags |= DEL_NOTHING;
                }
                else if (ch == _T('P'))
                {
                    dwFlags |= DEL_PROMPT;
                }
                else if (ch == _T('Q'))
                {
                    dwFlags |= DEL_QUIET;
                }
                else if (ch == _T('F'))
                {
                    dwFlags |= DEL_FORCE;
                }
                else if (ch == _T('S'))
                {
                    dwFlags |= DEL_SUBDIR;
                }
                else if (ch == _T('T'))
                {
                    dwFlags |= DEL_TOTAL;
                }
                else if (ch == _T('W'))
                {
                    dwFlags |= DEL_WIPE;
                }
                else if (ch == _T('Y'))
                {
                    dwFlags |= DEL_YES;
                }
                else if (ch == _T('A'))
                {
                    dwFlags |= DEL_ATTRIBUTES;
                    /*the proper syntax for /A has a min of 4 chars
                    i.e. /A:R or /A:-H */
                    if (_tcslen (arg[i]) < 4)
                    {
                        error_invalid_parameter_format(arg[i]);
                        return 0;
                    }
                    ch = _totupper (arg[i][3]);
                    if (_tcslen (arg[i]) == 4)
                    {
                        if (ch == _T('A'))
                        {
                            dwAttrFlags |= ATTR_ARCHIVE;
                        }
                        if (ch == _T('H'))
                        {
                            dwAttrFlags |= ATTR_HIDDEN;
                        }
                        if (ch == _T('S'))
                        {
                            dwAttrFlags |= ATTR_SYSTEM;
                        }
                        if (ch == _T('R'))
                        {
                            dwAttrFlags |= ATTR_READ_ONLY;
                        }
                    }
                    if (_tcslen (arg[i]) == 5)
                    {
                        if (ch == _T('-'))
                        {
                            ch = _totupper (arg[i][4]);
                            if (ch == _T('A'))
                            {
                                dwAttrFlags |= ATTR_N_ARCHIVE;
                            }
                            if (ch == _T('H'))
                            {
                                dwAttrFlags |= ATTR_N_HIDDEN;
                            }
                            if (ch == _T('S'))
                            {
                                dwAttrFlags |= ATTR_N_SYSTEM;
                            }
                            if (ch == _T('R'))
                            {
                                dwAttrFlags |= ATTR_N_READ_ONLY;
                            }
                        }
                    }
                }
            }

            nEvalArgs++;
        }
    }

    /* there are only options on the command line --> error!!!
    there is the same number of args as there is flags, so none of the args were filenames*/
    if (args == nEvalArgs)
    {
        error_req_param_missing ();
        freep (arg);
        return 1;
    }

    /* keep quiet within batch files */
    if (bc != NULL) dwFlags |= DEL_QUIET;

    /* check for filenames anywhere in command line */
    for (i = 0; i < args && !(dwFiles & 0x80000000); i++)
    {
        /*this checks to see if it is a flag; if it isn't, we assume it is a file name*/
        if ((*arg[i] == _T('/')) || (*arg[i] == _T('-')))
            continue;

        /* We want to make a copies of the argument */
        if (_tcslen(arg[i]) == 2 && arg[i][1] == _T(':'))
        {
            /* Check for C: D: ... */
            GetRootPath(arg[i], szOrginalArg, MAX_PATH);
        }
        else
        {
            _tcscpy(szOrginalArg,arg[i]);
        }
        dwFiles += ProcessDirectory(szOrginalArg, &dwFlags, dwAttrFlags);
    }

    freep (arg);

    /*Based on MS cmd, we only tell what files are being deleted when /S is used */
    if (dwFlags & DEL_TOTAL)
    {
        dwFiles &= 0x7fffffff;
        if (dwFiles < 2)
        {
            ConOutResPrintf(STRING_DEL_HELP3, dwFiles);
        }
        else
        {
            ConOutResPrintf(STRING_DEL_HELP4, dwFiles);
        }
    }

    return 0;
}

#endif
