/*
 *  DIR.C - dir internal command.
 *
 *
 *  History:
 *
 *    01/29/97 (Tim Norman)
 *        started.
 *
 *    06/13/97 (Tim Norman)
 *      Fixed code.
 *
 *    07/12/97 (Tim Norman)
 *        Fixed bug that caused the root directory to be unlistable
 *
 *    07/12/97 (Marc Desrochers)
 *        Changed to use maxx, maxy instead of findxy()
 *
 *    06/08/98 (Rob Lake)
 *        Added compatibility for /w in dir
 *
 *    06/09/98 (Rob Lake)
 *        Compatibility for dir/s started
 *        Tested that program finds directories off root fine
 *
 *    06/10/98 (Rob Lake)
 *        do_recurse saves the cwd and also stores it in Root
 *        build_tree adds the cwd to the beginning of its' entries
 *        Program runs fine, added print_tree -- works fine.. as EXE,
 *        program won't work properly as COM.
 *
 *    06/11/98 (Rob Lake)
 *        Found problem that caused COM not to work
 *
 *    06/12/98 (Rob Lake)
 *        debugged...
 *        added free mem routine
 *
 *    06/13/98 (Rob Lake)
 *        debugged the free mem routine
 *        debugged whole thing some more
 *        Notes:
 *        ReadDir stores Root name and _Read_Dir does the hard work
 *        PrintDir prints Root and _Print_Dir does the hard work
 *        KillDir kills Root _after_ _Kill_Dir does the hard work
 *        Integrated program into DIR.C(this file) and made some same
 *        changes throughout
 *
 *    06/14/98 (Rob Lake)
 *        Cleaned up code a bit, added comments
 *
 *    06/16/98 (Rob Lake)
 *        Added error checking to my previously added routines
 *
 *    06/17/98 (Rob Lake)
 *        Rewrote recursive functions, again! Most other recursive
 *        functions are now obsolete -- ReadDir, PrintDir, _Print_Dir,
 *        KillDir and _Kill_Dir.  do_recurse does what PrintDir did
 *        and _Read_Dir did what it did before along with what _Print_Dir
 *        did.  Makes /s a lot faster!
 *        Reports 2 more files/dirs that MS-DOS actually reports
 *        when used in root directory(is this because dir defaults
 *        to look for read only files?)
 *        Added support for /b, /a and /l
 *        Made error message similar to DOS error messages
 *        Added help screen
 *
 *    06/20/98 (Rob Lake)
 *        Added check for /-(switch) to turn off previously defined
 *        switches.
 *        Added ability to check for DIRCMD in environment and
 *        process it
 *
 *    06/21/98 (Rob Lake)
 *        Fixed up /B
 *        Now can dir *.ext/X, no spaces!
 *
 *    06/29/98 (Rob Lake)
 *        error message now found in command.h
 *
 *    07/08/1998 (John P. Price)
 *        removed extra returns; closer to MSDOS
 *        fixed wide display so that an extra return is not displayed
 *        when there is five filenames in the last line.
 *
 *    07/12/98 (Rob Lake)
 *        Changed error messages
 *
 *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
 *        added config.h include
 *
 *
 *    04-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
 *        Converted source code to Win32, except recursive dir ("dir /s").
 *
 *    10-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
 *        Fixed recursive dir ("dir /s").
 *
 *    14-Dec-1998 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
 *        Converted to Win32 directory functions and
 *        fixed some output bugs. There are still some more ;)
 *
 *    10-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
 *        Added "/N" and "/4" options, "/O" is a dummy.
 *        Added locale support.
 *
 *    20-Jan-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
 *        Redirection safe!
 *
 *    01-Mar-1999 (Eric Kohl <ekohl@abo.rhein-zeitung.de>)
 *        Replaced all runtime io functions by their Win32 counterparts.
 */

#include "config.h"

#ifdef INCLUDE_CMD_DIR
#include <windows.h>
#include <tchar.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>

#include "cmd.h"



/* flag definitions */
enum
{
	DIR_RECURSE = 0x0001,
	DIR_PAGE    = 0x0002,
	DIR_WIDE    = 0x0004,        /* Rob Lake */
	DIR_BARE    = 0x0008,        /* Rob Lake */
	DIR_ALL     = 0x0010,        /* Rob Lake */
	DIR_LWR     = 0x0020,        /* Rob Lake */
	DIR_SORT    = 0x0040,        /* /O sort */
	DIR_NEW     = 0x0080,        /* /N new style */
	DIR_FOUR    = 0x0100         /* /4 four digit year */
};


/* Globally save the # of dirs, files and bytes,
 * probabaly later pass them to functions. Rob Lake  */
static ULONG recurse_dir_cnt;
static ULONG recurse_file_cnt;
static ULARGE_INTEGER recurse_bytes;


/*
 * help
 *
 * displays help screen for dir
 * Rob Lake
 */
static VOID Help (VOID)
{
	ConOutPuts (_T("Displays a list of files and subdirectories in a directory.\n"
       "\n"
       "DIR [drive:][path][filename] [/A] [/B] [/L] [/N] [/S] [/P] [/W] [/4]\n"
       "\n"
       "  [drive:][path][filename]\n"
       "              Specifies drive, directory, and/or files to list.\n"
       "\n"
       "  /A          Displays files with HIDDEN SYSTEM attributes\n"
       "              default is ARCHIVE and READ ONLY\n"
       "  /B          Uses bare format (no heading information or summary).\n"
       "  /L          Uses lowercase.\n"
       "  /N          New long list format where filenames are on the far right.\n"
       "  /S          Displays files in specified directory and all subdirectories\n"
       "  /P          Pauses after each screen full\n"
       "  /W          Prints in wide format\n"
       "  /4          Display four digit years.\n"
       "\n"
       "Switches may be present in the DIRCMD environment variable.  Use\n"
       "of the - (hyphen) can turn off defined swtiches.  Ex. /-W would\n"
       "turn off printing in wide format.\n"
      ));
}


/*
 * DirReadParam
 *
 * read the parameters from the command line
 */
static BOOL
DirReadParam (LPTSTR line, LPTSTR *param, LPDWORD lpFlags)
{
	INT slash = 0;

	if (!line)
		return TRUE;

	*param = NULL;

	/* scan the command line, processing switches */
	while (*line)
	{
		/* process switch */
		if (*line == _T('/') || slash)
		{
			if (!slash)
				line++;
			slash = 0;
			if (*line == _T('-'))
			{
				line++;
				if (_totupper (*line) == _T('S'))
					*lpFlags &= ~DIR_RECURSE;
				else if (_totupper (*line) == _T('P'))
					*lpFlags &= ~DIR_PAGE;
				else if (_totupper (*line) == _T('W'))
					*lpFlags &= ~DIR_WIDE;
				else if (_totupper (*line) == _T('B'))
					*lpFlags &= ~DIR_BARE;
				else if (_totupper (*line) == _T('A'))
					*lpFlags &= ~DIR_ALL;
				else if (_totupper (*line) == _T('L'))
					*lpFlags &= ~DIR_LWR;
				else if (_totupper (*line) == _T('N'))
					*lpFlags &= ~DIR_NEW;
				else if (_totupper (*line) == _T('O'))
					*lpFlags &= ~DIR_SORT;
				else if (_totupper (*line) == _T('4'))
					*lpFlags &= ~DIR_FOUR;
				else
				{
					error_invalid_switch ((TCHAR)_totupper (*line));
					return FALSE;
				}
				line++;
				continue;
			}
			else
			{
				if (_totupper (*line) == _T('S'))
					*lpFlags |= DIR_RECURSE;
				else if (_totupper (*line) == _T('P'))
					*lpFlags |= DIR_PAGE;
				else if (_totupper (*line) == _T('W'))
					*lpFlags |= DIR_WIDE;
				else if (_totupper (*line) == _T('B'))
					*lpFlags |= DIR_BARE;
				else if (_totupper (*line) == _T('A'))
					*lpFlags |= DIR_ALL;
				else if (_totupper (*line) == _T('L'))
					*lpFlags |= DIR_LWR;
				else if (_totupper (*line) == _T('N'))
					*lpFlags |= DIR_NEW;
				else if (_totupper (*line) == _T('O'))
					*lpFlags |= DIR_SORT;
				else if (_totupper (*line) == _T('4'))
					*lpFlags |= DIR_FOUR;
				else if (*line == _T('?'))
				{
					Help();
					return FALSE;
				}
				else
				{
					error_invalid_switch ((TCHAR)_totupper (*line));
					return FALSE;
				}
				line++;
				continue;
			}
		}

		/* process parameter */
		if (!_istspace (*line))
		{
			if (*param)
			{
				error_too_many_parameters (*param);
				return FALSE;
			}

			*param = line;

			/* skip to end of line or next whitespace or next / */
			while (*line && !_istspace (*line) && *line != _T('/'))
				line++;

			/* if end of line, return */
			if (!*line)
				return TRUE;

			/* if parameter, remember to process it later */
			if (*line == _T('/'))
				slash = 1;

			*line++ = 0;
			continue;
		}

		line++;
	}

	if (slash)
	{
		error_invalid_switch ((TCHAR)_totupper (*line));
		return FALSE;
	}

	return TRUE;
}


/*
 * ExtendFilespec
 *
 * extend the filespec, possibly adding wildcards
 */
static VOID
ExtendFilespec (LPTSTR file)
{
        INT len = 0;

	if (!file)
		return;

	/* if no file spec, change to "*.*" */
	if (*file == _T('\0'))
	{
		_tcscpy (file, _T("*.*"));
		return;
	}

	/* if starts with . add * in front */
	if (*file == _T('.'))
	{
		memmove (&file[1], &file[0], (_tcslen (file) + 1) * sizeof(TCHAR));
		file[0] = _T('*');
	}

	/* if no . add .* */
	if (!_tcschr (file, _T('.')))
	{
		_tcscat (file, _T(".*"));
		return;
	}

        /* if last character is '.' add '*' */
        len = _tcslen (file);
        if (file[len - 1] == _T('.'))
        {
                _tcscat (file, _T("*"));
                return;
        }
}


/*
 * dir_parse_pathspec
 *
 * split the pathspec into drive, directory, and filespec
 */
static INT
DirParsePathspec (LPTSTR szPathspec, LPTSTR szPath, LPTSTR szFilespec)
{
	TCHAR  szOrigPath[MAX_PATH];
	LPTSTR start;
	LPTSTR tmp;
	INT    i;
	BOOL   bWildcards = FALSE;

	GetCurrentDirectory (MAX_PATH, szOrigPath);

	/* get the drive and change to it */
	if (szPathspec[1] == _T(':'))
	{
		TCHAR szRootPath[] = _T("A:");

		szRootPath[0] = szPathspec[0];
		start = szPathspec + 2;
		SetCurrentDirectory (szRootPath);
	}
	else
	{
		start = szPathspec;
	}


	/* check for wildcards */
	for (i = 0; szPathspec[i]; i++)
	{
		if (szPathspec[i] == _T('*') || szPathspec[i] == _T('?'))
			bWildcards = TRUE;
	}

	/* check if this spec is a directory */
	if (!bWildcards)
	{
		if (SetCurrentDirectory (szPathspec))
		{
			_tcscpy (szFilespec, _T("*.*"));

			if (!GetCurrentDirectory (MAX_PATH, szPath))
			{
				szFilespec[0] = _T('\0');
				SetCurrentDirectory (szOrigPath);
				error_out_of_memory();
				return 1;
			}

			SetCurrentDirectory (szOrigPath);
			return 0;
		}
	}

	/* find the file spec */
	tmp = _tcsrchr (start, _T('\\'));

	/* if no path is specified */
	if (!tmp)
	{
		_tcscpy (szFilespec, start);
		ExtendFilespec (szFilespec);

		if (!GetCurrentDirectory (MAX_PATH, szPath))
		{
			szFilespec[0] = _T('\0');
			SetCurrentDirectory (szOrigPath);
			error_out_of_memory();
			return 1;
		}

		SetCurrentDirectory (szOrigPath);
		return 0;
	}

	/* get the filename */
	_tcscpy (szFilespec, tmp+1);
	ExtendFilespec (szFilespec);

	*tmp = _T('\0');

	/* change to this directory and get its full name */
	if (!SetCurrentDirectory (start))
	{
		*tmp = _T('\\');
		szFilespec[0] = _T('\0');
		SetCurrentDirectory (szOrigPath);
		error_path_not_found ();
		return 1;
	}

	if (!GetCurrentDirectory (MAX_PATH, szPath))
	{
		*tmp = _T('\\');
		szFilespec[0] = _T('\0');
		SetCurrentDirectory (szOrigPath);
		error_out_of_memory ();
		return 1;
	}

	*tmp = _T('\\');

	SetCurrentDirectory (szOrigPath);

	return 0;
}


/*
 * pause
 *
 * pause until a key is pressed
 */
static INT
Pause (VOID)
{
	cmd_pause ("", "");

	return 0;
}


/*
 * incline
 *
 * increment our line if paginating, display message at end of screen
 */
static INT
IncLine (LPINT pLine, DWORD dwFlags)
{
	if (!(dwFlags & DIR_PAGE))
		return 0;

	(*pLine)++;

	if (*pLine >= (int)maxy - 2)
	{
		*pLine = 0;
		return Pause ();
	}

	return 0;
}


/*
 * PrintDirectoryHeader
 *
 * print the header for the dir command
 */
static BOOL
PrintDirectoryHeader (LPTSTR szPath, LPINT pLine, DWORD dwFlags)
{
	TCHAR szRootName[] = _T("A:\\");
	TCHAR szVolName[80];
	DWORD dwSerialNr;

	if (dwFlags & DIR_BARE)
		return TRUE;

	/* get the media ID of the drive */
	szRootName[0] = szPath[0];
	if (!GetVolumeInformation (szRootName, szVolName, 80, &dwSerialNr,
							   NULL, NULL, NULL, 0))
	{
		error_invalid_drive();
		return FALSE;
	}

	/* print drive info */
	ConOutPrintf (_T(" Volume in drive %c"), szRootName[0]);

	if (szVolName[0] != _T('\0'))
		ConOutPrintf (_T(" is %s\n"), szVolName);
	else
		ConOutPrintf (_T(" has no label\n"));

	if (IncLine (pLine, dwFlags))
		return FALSE;

	/* print the volume serial number if the return was successful */
	ConOutPrintf (_T(" Volume Serial Number is %04X-%04X\n"),
				  HIWORD(dwSerialNr), LOWORD(dwSerialNr));
	if (IncLine (pLine, dwFlags))
		return FALSE;

	return TRUE;
}


/*
 * convert
 *
 * insert commas into a number
 */
static INT
ConvertULong (ULONG num, LPTSTR des, INT len)
{
	TCHAR temp[32];
	INT c = 0;
	INT n = 0;

	if (num == 0)
	{
		des[0] = _T('0');
		des[1] = _T('\0');
		n = 1;
	}
	else
	{
		temp[31] = 0;
		while (num > 0)
		{
			if (((c + 1) % (nNumberGroups + 1)) == 0)
				temp[30 - c++] = cThousandSeparator;
			temp[30 - c++] = (TCHAR)(num % 10) + _T('0');
			num /= 10;
		}

		for (n = 0; n <= c; n++)
			des[n] = temp[31 - c + n];
	}

	return n;
}


static INT
ConvertULargeInteger (ULARGE_INTEGER num, LPTSTR des, INT len)
{
	TCHAR temp[32];
	INT c = 0;
	INT n = 0;

	if (num.QuadPart == 0)
	{
		des[0] = _T('0');
		des[1] = _T('\0');
		n = 1;
	}
	else
	{
		temp[31] = 0;
		while (num.QuadPart > 0)
		{
			if (((c + 1) % (nNumberGroups + 1)) == 0)
				temp[30 - c++] = cThousandSeparator;
			temp[30 - c++] = (TCHAR)(num.QuadPart % 10) + _T('0');
			num.QuadPart /= 10;
		}

		for (n = 0; n <= c; n++)
			des[n] = temp[31 - c + n];
	}

	return n;
}


static VOID
PrintFileDateTime (LPSYSTEMTIME dt, DWORD dwFlags)
{
	WORD wYear = (dwFlags & DIR_FOUR) ? dt->wYear : dt->wYear%100;

	switch (nDateFormat)
	{
		case 0: /* mmddyy */
		default:
			ConOutPrintf (_T("%.2d%c%.2d%c%d"),
					dt->wMonth, cDateSeparator, dt->wDay, cDateSeparator, wYear);
			break;

		case 1: /* ddmmyy */
			ConOutPrintf (_T("%.2d%c%.2d%c%d"),
					dt->wDay, cDateSeparator, dt->wMonth, cDateSeparator, wYear);
			break;

		case 2: /* yymmdd */
			ConOutPrintf (_T("%d%c%.2d%c%.2d"),
					wYear, cDateSeparator, dt->wMonth, cDateSeparator, dt->wDay);
			break;
	}

	switch (nTimeFormat)
	{
		case 0: /* 12 hour format */
		default:
			ConOutPrintf (_T("  %2d%c%.2u%c"),
					(dt->wHour == 0 ? 12 : (dt->wHour <= 12 ? dt->wHour : dt->wHour - 12)),
					cTimeSeparator,
					 dt->wMinute, (dt->wHour <= 11 ? 'a' : 'p'));
			break;

		case 1: /* 24 hour format */
			ConOutPrintf (_T("  %2d%c%.2u"),
					dt->wHour, cTimeSeparator, dt->wMinute);
			break;
	}
}


/*
 * print_summary: prints dir summary
 * Added by Rob Lake 06/17/98 to compact code
 * Just copied Tim's Code and patched it a bit
 *
 */
static INT
PrintSummary (LPTSTR szPath, ULONG ulFiles, ULONG ulDirs, ULARGE_INTEGER bytes,
	      LPINT pLine, DWORD dwFlags)
{
	TCHAR buffer[64];

	if (dwFlags & DIR_BARE)
		return 0;

	/* print number of files and bytes */
	ConvertULong (ulFiles, buffer, sizeof(buffer));
	ConOutPrintf (_T("          %6s File%c"),
                      buffer, ulFiles == 1 ? _T(' ') : _T('s'));

	ConvertULargeInteger (bytes, buffer, sizeof(buffer));
	ConOutPrintf (_T("  %15s byte%c\n"),
                      buffer, bytes.QuadPart == 1 ? _T(' ') : _T('s'));

	if (IncLine (pLine, dwFlags))
		return 1;

	/* print number of dirs and bytes free */
	ConvertULong (ulDirs, buffer, sizeof(buffer));
	ConOutPrintf (_T("          %6s Dir%c"),
                      buffer, ulDirs == 1 ? _T(' ') : _T('s'));


	if (!(dwFlags & DIR_RECURSE))
	{
		ULARGE_INTEGER uliFree;
		TCHAR szRoot[] = _T("A:\\");
		DWORD dwSecPerCl;
		DWORD dwBytPerSec;
		DWORD dwFreeCl;
		DWORD dwTotCl;

		szRoot[0] = szPath[0];
		GetDiskFreeSpace (szRoot, &dwSecPerCl, &dwBytPerSec, &dwFreeCl, &dwTotCl);
                uliFree.QuadPart = dwSecPerCl * dwBytPerSec * dwFreeCl;
		ConvertULargeInteger (uliFree, buffer, sizeof(buffer));
		ConOutPrintf (_T("   %15s bytes free\n"), buffer);
	}

	if (IncLine (pLine, dwFlags))
		return 1;

	return 0;
}


/*
 * dir_list
 *
 * list the files in the directory
 */
static INT
DirList (LPTSTR szPath, LPTSTR szFilespec, LPINT pLine, DWORD dwFlags)
{
	TCHAR szFullPath[MAX_PATH];
	WIN32_FIND_DATA file;
	ULARGE_INTEGER bytecount;
	FILETIME   ft;
	SYSTEMTIME dt;
	HANDLE hFile;
	TCHAR  buffer[32];
	ULONG filecount = 0;
	ULONG dircount = 0;
	INT count;

	bytecount.QuadPart = 0;

	_tcscpy (szFullPath, szPath);
	if (szFullPath[_tcslen(szFullPath) - 1] != _T('\\'))
		_tcscat (szFullPath, _T("\\"));
	_tcscat (szFullPath, szFilespec);

	hFile = FindFirstFile (szFullPath, &file);
	if (hFile == INVALID_HANDLE_VALUE)
	{
		/* Don't want to print anything if scanning recursively
		 * for a file. RL
		 */
		if ((dwFlags & DIR_RECURSE) == 0)
		{
			error_file_not_found ();
			IncLine (pLine, dwFlags);
			FindClose (hFile);
			return 1;
		}
		FindClose (hFile);
		return 0;
	}

	/* moved down here because if we are recursively searching and
	 * don't find any files, we don't want just to print
	 * Directory of C:\SOMEDIR
	 * with nothing else
	 * Rob Lake 06/13/98
	 */
	if ((dwFlags & DIR_BARE) == 0)
	{
		ConOutPrintf (_T(" Directory of %s\n"), szPath);
		if (IncLine (pLine, dwFlags))
			return 1;
		ConOutPrintf (_T("\n"));
		if (IncLine (pLine, dwFlags))
			return 1;
	}

	/* For counting columns of output */
	count = 0;

	do
	{
		/* next file, if user doesn't want all files */
		if (!(dwFlags & DIR_ALL) &&
			((file.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) ||
			 (file.dwFileAttributes & FILE_ATTRIBUTE_SYSTEM)))
			continue;

		if (dwFlags & DIR_LWR)
		{
			_tcslwr (file.cAlternateFileName);
		}

		if (dwFlags & DIR_WIDE && (dwFlags & DIR_BARE) == 0)
		{
			ULARGE_INTEGER uliSize;

			if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
			{
				if (file.cAlternateFileName[0] == _T('\0'))
					_stprintf (buffer, _T("[%s]"), file.cFileName);
				else
					_stprintf (buffer, _T("[%s]"), file.cAlternateFileName);
				dircount++;
			}
			else
			{
				if (file.cAlternateFileName[0] == _T('\0'))
					_stprintf (buffer, _T("%s"), file.cFileName);
				else
					_stprintf (buffer, _T("%s"), file.cAlternateFileName);
				filecount++;
			}

			ConOutPrintf (_T("%-15s"), buffer);
			count++;
			if (count == 5)
			{
				/* output 5 columns */
				ConOutPrintf (_T("\n"));
				if (IncLine (pLine, dwFlags))
					return 1;
				count = 0;
			}

                        uliSize.u.LowPart = file.nFileSizeLow;
                        uliSize.u.HighPart = file.nFileSizeHigh;
			bytecount.QuadPart += uliSize.QuadPart;
		}
		else if (dwFlags & DIR_BARE)
		{
			ULARGE_INTEGER uliSize;

			if (_tcscmp (file.cFileName, _T(".")) == 0 ||
				_tcscmp (file.cFileName, _T("..")) == 0)
				continue;

			if (dwFlags & DIR_RECURSE)
			{
				TCHAR dir[MAX_PATH];

				_tcscpy (dir, szPath);
				_tcscat (dir, _T("\\"));
				if (dwFlags & DIR_LWR)
					_tcslwr (dir);
				ConOutPrintf (dir);
			}

			ConOutPrintf (_T("%-13s\n"), file.cFileName);
			if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				dircount++;
			else
				filecount++;
			if (IncLine (pLine, dwFlags))
				return 1;

                        uliSize.u.LowPart = file.nFileSizeLow;
                        uliSize.u.HighPart = file.nFileSizeHigh;
			bytecount.QuadPart += uliSize.QuadPart;
		}
		else
		{
			if (dwFlags & DIR_NEW)
			{
				/* print file date and time */
				if (FileTimeToLocalFileTime (&file.ftLastWriteTime, &ft))
				{
					FileTimeToSystemTime (&ft, &dt);
					PrintFileDateTime (&dt, dwFlags);
				}

				/* print file size */
				if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					ConOutPrintf (_T("         <DIR>         "));
					dircount++;
				}
				else
				{
					ULARGE_INTEGER uliSize;

                                        uliSize.u.LowPart = file.nFileSizeLow;
                                        uliSize.u.HighPart = file.nFileSizeHigh;

					ConvertULargeInteger (uliSize, buffer, sizeof(buffer));
					ConOutPrintf (_T("   %20s"), buffer);

					bytecount.QuadPart += uliSize.QuadPart;
                                        filecount++;
				}

				/* print long filename */
				ConOutPrintf (_T(" %s\n"), file.cFileName);
			}
			else
			{
				if (file.cFileName[0] == _T('.'))
					ConOutPrintf (_T("%-13s "), file.cFileName);
				else if (file.cAlternateFileName[0] == _T('\0'))
				{
					TCHAR szShortName[13];
					LPTSTR ext;

					_tcsncpy (szShortName, file.cFileName, 13);
					ext = _tcschr (szShortName, _T('.'));
					if (!ext)
						ext = _T("");
					else
						*ext++ = _T('\0');
					ConOutPrintf (_T("%-8s %-3s  "), szShortName, ext);
				}
				else
				{
					LPTSTR ext;

					ext = _tcschr (file.cAlternateFileName, _T('.'));
					if (!ext)
						ext = _T("");
					else
						*ext++ = _T('\0');
					ConOutPrintf (_T("%-8s %-3s  "), file.cAlternateFileName, ext);
				}

				/* print file size */
				if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
				{
					ConOutPrintf ("%-14s", "<DIR>");
					dircount++;
				}
				else
				{
					ULARGE_INTEGER uliSize;

                                        uliSize.u.LowPart = file.nFileSizeLow;
                                        uliSize.u.HighPart = file.nFileSizeHigh;
                                        ConvertULargeInteger (uliSize, buffer, sizeof(buffer));
					ConOutPrintf (_T("   %10s "), buffer);
					bytecount.QuadPart += uliSize.QuadPart;
					filecount++;
				}

				/* print file date and time */
				if (FileTimeToLocalFileTime (&file.ftLastWriteTime, &ft))
				{
					FileTimeToSystemTime (&ft, &dt);
					PrintFileDateTime (&dt, dwFlags);
				}

				/* print long filename */
				ConOutPrintf (" %s\n", file.cFileName);
			}

			if (IncLine (pLine, dwFlags))
				return 1;
		}
	}
	while (FindNextFile (hFile, &file));
	FindClose (hFile);

	/* Rob Lake, need to make clean output */
	/* JPP 07/08/1998 added check for count != 0 */
	if ((dwFlags & DIR_WIDE) && (count != 0))
	{
		ConOutPrintf (_T("\n"));
		if (IncLine (pLine, dwFlags))
			return 1;
	}

	if (filecount || dircount)
	{
		recurse_dir_cnt += dircount;
		recurse_file_cnt += filecount;
		recurse_bytes.QuadPart += bytecount.QuadPart;

		/* print_summary */
		if (PrintSummary (szPath, filecount, dircount, bytecount, pLine, dwFlags))
			return 1;
	}
	else
	{
		error_file_not_found ();
		return 1;
	}

	return 0;
}


/*
 * _Read_Dir: Actual function that does recursive listing
 */
static INT
DirRead (LPTSTR szPath, LPTSTR szFilespec, LPINT pLine, DWORD dwFlags)
{
	TCHAR szFullPath[MAX_PATH];
	WIN32_FIND_DATA file;
	HANDLE hFile;

	_tcscpy (szFullPath, szPath);
	if (szFullPath[_tcslen (szFullPath) - 1] != _T('\\'))
		_tcscat (szFullPath, _T("\\"));
	_tcscat (szFullPath, szFilespec);

	hFile = FindFirstFile (szFullPath, &file);
	if (hFile == INVALID_HANDLE_VALUE)
		return 1;

	do
	{
		/* don't list "." and ".." */
		if (_tcscmp (file.cFileName, _T(".")) == 0 ||
			_tcscmp (file.cFileName, _T("..")) == 0)
			continue;

		if (file.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
		{
			if (DirList (file.cFileName, szFilespec, pLine, dwFlags))
			{
				FindClose (hFile);
				return 1;
			}

			if ((dwFlags & DIR_BARE) == 0)
			{
				ConOutPrintf ("\n");
				if (IncLine (pLine, dwFlags) != 0)
					return 1;
			}

			if (DirRead (file.cFileName, szFilespec, pLine, dwFlags) == 1)
			{
				FindClose (hFile);
				return 1;
			}
		}
	}
	while (FindNextFile (hFile, &file));

	if (!FindClose (hFile))
		return 1;

	return 0;
}


/*
 * do_recurse: Sets up for recursive directory listing
 */
static INT
DirRecurse (LPTSTR szPath, LPTSTR szSpec, LPINT pLine, DWORD dwFlags)
{
	if (!PrintDirectoryHeader (szPath, pLine, dwFlags))
		return 1;

	if (DirList (szPath, szSpec, pLine, dwFlags))
		return 1;

	if ((dwFlags & DIR_BARE) == 0)
	{
		ConOutPrintf (_T("\n"));
		if (IncLine (pLine, dwFlags))
			return 1;
	}

	if (DirRead (szPath, szSpec, pLine, dwFlags))
		return 1;

	if ((dwFlags & DIR_BARE) == 0)
		ConOutPrintf (_T("Total files listed:\n"));

	dwFlags &= ~DIR_RECURSE;

	if (PrintSummary (szPath, recurse_file_cnt,
                          recurse_dir_cnt, recurse_bytes, pLine, dwFlags))
		return 1;

	if ((dwFlags & DIR_BARE) == 0)
	{
		ConOutPrintf (_T("\n"));
		if (IncLine (pLine, dwFlags))
			return 1;
	}

	return 0;
}


/*
 * dir
 *
 * internal dir command
 */
INT cmd_dir (LPTSTR first, LPTSTR rest)
{
	DWORD  dwFlags = DIR_NEW | DIR_FOUR;
	TCHAR  dircmd[256];
	TCHAR  szPath[MAX_PATH];
	TCHAR  szFilespec[MAX_PATH];
	LPTSTR param;
	INT    nLine = 0;


	recurse_dir_cnt = 0L;
	recurse_file_cnt = 0L;
	recurse_bytes.QuadPart = 0;

	/* read the parameters from the DIRCMD environment variable */
	if (GetEnvironmentVariable (_T("DIRCMD"), dircmd, 256))
	{
		if (!DirReadParam (dircmd, &param, &dwFlags))
			return 1;
	}

	/* read the parameters */
	if (!DirReadParam (rest, &param, &dwFlags))
		return 1;

	/* default to current directory */
	if (!param)
		param = ".";

	/* parse the directory info */
	if (DirParsePathspec (param, szPath, szFilespec))
		return 1;

	if (dwFlags & DIR_RECURSE)
	{
		IncLine (&nLine, dwFlags);
		if (DirRecurse (szPath, szFilespec, &nLine, dwFlags))
			return 1;
		return 0;
	}

	/* print the header */
	if (!PrintDirectoryHeader (szPath, &nLine, dwFlags))
		return 1;

	if (DirList (szPath, szFilespec, &nLine, dwFlags))
		return 1;

	return 0;
}

#endif
