update for HEAD-2003050101
[reactos.git] / subsys / system / cmd / dir.c
diff --git a/subsys/system/cmd/dir.c b/subsys/system/cmd/dir.c
new file mode 100644 (file)
index 0000000..fc579fb
--- /dev/null
@@ -0,0 +1,1223 @@
+/* $Id$
+ *
+ *  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.
+ *  
+ *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
+ *        dir /s now works in deeper trees
+ */
+
+#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"
+
+
+typedef BOOL STDCALL
+(*PGETFREEDISKSPACEEX)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
+
+
+/* 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;
+}
+
+
+/*
+ * incline
+ *
+ * increment our line if paginating, display message at end of screen
+ */
+static BOOL
+IncLine (LPINT pLine, DWORD dwFlags)
+{
+       if (!(dwFlags & DIR_PAGE))
+               return FALSE;
+
+       (*pLine)++;
+
+       if (*pLine >= (int)maxy - 2)
+       {
+               *pLine = 0;
+               return (PagePrompt () == PROMPT_BREAK);
+       }
+
+       return FALSE;
+}
+
+
+/*
+ * PrintDirectoryHeader
+ *
+ * print the header for the dir command
+ */
+static BOOL
+PrintDirectoryHeader (LPTSTR szPath, LPINT pLine, DWORD dwFlags)
+{
+  TCHAR szRootName[MAX_PATH];
+  TCHAR szVolName[80];
+  DWORD dwSerialNr;
+  LPTSTR p;
+
+  if (dwFlags & DIR_BARE)
+    return(TRUE);
+
+  /* build usable root path */
+  if (szPath[1] == _T(':') && szPath[2] == _T('\\'))
+    {
+      /* normal path */
+      szRootName[0] = szPath[0];
+      szRootName[1] = _T(':');
+      szRootName[2] = _T('\\');
+      szRootName[3] = 0;
+    }
+  else if (szPath[0] == _T('\\') && szPath[1] == _T('\\'))
+    {
+      /* UNC path */
+      p = _tcschr(&szPath[2], _T('\\'));
+      if (p == NULL)
+       {
+         error_invalid_drive();
+         return(FALSE);
+       }
+      p = _tcschr(p+1, _T('\\'));
+      if (p == NULL)
+       {
+         _tcscpy(szRootName, szPath);
+         _tcscat(szRootName, _T("\\"));
+       }
+      else
+       {
+         *p = 0;
+         _tcscpy(szRootName, szPath);
+         _tcscat(szRootName, _T("\\"));
+         *p = _T('\\');
+       }
+    }
+  else
+    {
+      error_invalid_drive();
+      return(FALSE);
+    }
+
+  /* get the media ID of the drive */
+  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;
+       }
+}
+
+
+static VOID
+GetUserDiskFreeSpace(LPCTSTR lpRoot,
+                    PULARGE_INTEGER lpFreeSpace)
+{
+  PGETFREEDISKSPACEEX pGetFreeDiskSpaceEx;
+  HINSTANCE hInstance;
+  DWORD dwSecPerCl;
+  DWORD dwBytPerSec;
+  DWORD dwFreeCl;
+  DWORD dwTotCl;
+
+  lpFreeSpace->QuadPart = 0;
+
+  hInstance = LoadLibrary(_T("KERNEL32"));
+  if (hInstance != NULL)
+    {
+#ifndef UNICODE
+      pGetFreeDiskSpaceEx = GetProcAddress(hInstance,
+                                          "GetDiskFreeSpaceExA");
+#else
+      pGetFreeDiskSpaceEx = GetProcAddress(hInstance,
+                                          "GetDiskFreeSpaceExW");
+#endif
+      if (pGetFreeDiskSpaceEx != NULL)
+       {
+         if (pGetFreeDiskSpaceEx(lpRoot, lpFreeSpace, NULL, NULL) == TRUE)
+           return;
+       }
+      FreeLibrary(hInstance);
+    }
+
+  GetDiskFreeSpace(lpRoot,
+                 &dwSecPerCl,
+                 &dwBytPerSec,
+                 &dwFreeCl,
+                 &dwTotCl);
+
+  lpFreeSpace->QuadPart = dwSecPerCl * dwBytPerSec * dwFreeCl;
+}
+
+
+/*
+ * 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];
+  ULARGE_INTEGER uliFree;
+  TCHAR szRoot[] = _T("A:\\");
+
+  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'));
+  ConOutPrintf (_T("  %I64u byte%c\n"),
+                bytes.QuadPart, 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))
+    {
+      szRoot[0] = szPath[0];
+      GetUserDiskFreeSpace(szRoot, &uliFree);
+      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)
+               {
+                       FindClose (hFile);
+                       error_file_not_found ();
+                       if (IncLine (pLine, dwFlags))
+                               return 0;
+                       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.LowPart = file.nFileSizeLow;
+                       uliSize.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.LowPart = file.nFileSizeLow;
+                       uliSize.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.LowPart = file.nFileSizeLow;
+                                       uliSize.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.LowPart = file.nFileSizeLow;
+                                       uliSize.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)
+               {
+                       _tcscpy (szFullPath, szPath);
+                       if (szFullPath[_tcslen (szFullPath) - 1] != _T('\\'))
+                               _tcscat (szFullPath, _T("\\"));
+                       _tcscat (szFullPath, file.cFileName);
+                       
+                       if (DirList (szFullPath, szFilespec, pLine, dwFlags))
+                       {
+                               FindClose (hFile);
+                               return 1;
+                       }
+
+                       if ((dwFlags & DIR_BARE) == 0)
+                       {
+                               ConOutPrintf ("\n");
+                               if (IncLine (pLine, dwFlags) != 0)
+                                       return 1;
+                               ConOutPrintf ("\n");
+                               if (IncLine (pLine, dwFlags) != 0)
+                                       return 1;
+                       }
+
+                       if (DirRead (szFullPath, 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 CommandDir (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)
+       {
+               if (IncLine (&nLine, dwFlags))
+                       return 0;
+               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
+
+/* EOF */