xref: /reactos/base/shell/cmd/dir.c (revision 60eea2d7)
1 /*
2  *  DIR.C - dir internal command.
3  *
4  *
5  *  History:
6  *
7  *    01/29/97 (Tim Norman)
8  *        started.
9  *
10  *    06/13/97 (Tim Norman)
11  *      Fixed code.
12  *
13  *    07/12/97 (Tim Norman)
14  *        Fixed bug that caused the root directory to be unlistable
15  *
16  *    07/12/97 (Marc Desrochers)
17  *        Changed to use maxx, maxy instead of findxy()
18  *
19  *    06/08/98 (Rob Lake)
20  *        Added compatibility for /w in dir
21  *
22  *    06/09/98 (Rob Lake)
23  *        Compatibility for dir/s started
24  *        Tested that program finds directories off root fine
25  *
26  *    06/10/98 (Rob Lake)
27  *        do_recurse saves the cwd and also stores it in Root
28  *        build_tree adds the cwd to the beginning of its' entries
29  *        Program runs fine, added print_tree -- works fine.. as EXE,
30  *        program won't work properly as COM.
31  *
32  *    06/11/98 (Rob Lake)
33  *        Found problem that caused COM not to work
34  *
35  *    06/12/98 (Rob Lake)
36  *        debugged...
37  *        added free mem routine
38  *
39  *    06/13/98 (Rob Lake)
40  *        debugged the free mem routine
41  *        debugged whole thing some more
42  *        Notes:
43  *        ReadDir stores Root name and _Read_Dir does the hard work
44  *        PrintDir prints Root and _Print_Dir does the hard work
45  *        KillDir kills Root _after_ _Kill_Dir does the hard work
46  *        Integrated program into DIR.C(this file) and made some same
47  *        changes throughout
48  *
49  *    06/14/98 (Rob Lake)
50  *        Cleaned up code a bit, added comments
51  *
52  *    06/16/98 (Rob Lake)
53  *        Added error checking to my previously added routines
54  *
55  *    06/17/98 (Rob Lake)
56  *        Rewrote recursive functions, again! Most other recursive
57  *        functions are now obsolete -- ReadDir, PrintDir, _Print_Dir,
58  *        KillDir and _Kill_Dir.  do_recurse does what PrintDir did
59  *        and _Read_Dir did what it did before along with what _Print_Dir
60  *        did.  Makes /s a lot faster!
61  *        Reports 2 more files/dirs that MS-DOS actually reports
62  *        when used in root directory(is this because dir defaults
63  *        to look for read only files?)
64  *        Added support for /b, /a and /l
65  *        Made error message similar to DOS error messages
66  *        Added help screen
67  *
68  *    06/20/98 (Rob Lake)
69  *        Added check for /-(switch) to turn off previously defined
70  *        switches.
71  *        Added ability to check for DIRCMD in environment and
72  *        process it
73  *
74  *    06/21/98 (Rob Lake)
75  *        Fixed up /B
76  *        Now can dir *.ext/X, no spaces!
77  *
78  *    06/29/98 (Rob Lake)
79  *        error message now found in command.h
80  *
81  *    07/08/1998 (John P. Price)
82  *        removed extra returns; closer to MSDOS
83  *        fixed wide display so that an extra return is not displayed
84  *        when there is five filenames in the last line.
85  *
86  *    07/12/98 (Rob Lake)
87  *        Changed error messages
88  *
89  *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
90  *        added config.h include
91  *
92  *
93  *    04-Dec-1998 (Eric Kohl)
94  *        Converted source code to Win32, except recursive dir ("dir /s").
95  *
96  *    10-Dec-1998 (Eric Kohl)
97  *        Fixed recursive dir ("dir /s").
98  *
99  *    14-Dec-1998 (Eric Kohl)
100  *        Converted to Win32 directory functions and
101  *        fixed some output bugs. There are still some more ;)
102  *
103  *    10-Jan-1999 (Eric Kohl)
104  *        Added "/N" and "/4" options, "/O" is a dummy.
105  *        Added locale support.
106  *
107  *    20-Jan-1999 (Eric Kohl)
108  *        Redirection safe!
109  *
110  *    01-Mar-1999 (Eric Kohl)
111  *        Replaced all runtime io functions by their Win32 counterparts.
112  *
113  *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.se>)
114  *        dir /s now works in deeper trees
115  *
116  *    28-Jan-2004 (Michael Fritscher <michael@fritscher.net>)
117  *        Fix for /p, so it is working under Windows in GUI-mode, too.
118  *
119  *    30-Apr-2004 (Filip Navara <xnavara@volny.cz>)
120  *        Fix /w to print long names.
121  *
122  *    27-Feb-2005 (Konstantinos Paliouras <squarious@gmail.com>)
123  *        Implemented all the switches that were missing, and made
124  *        the ros dir very similar to windows dir. Major part of
125  *        the code is rewritten. /p is removed, to be rewriten in
126  *        the main cmd code.
127  *
128  *    1-Jul-2004 (Brandon Turner <turnerb7@msu.edu>)
129  *        Added /p back in using ConOutPrintfPaging
130  *
131  *    3-feb-2007 (Paolo Devoti devotip at gmail)
132  *        Removed variables formerly in use to handle pagination
133  *		  Pagination belongs to ConOutPrintfPaging
134  *		  Removed already commented out code of old pagination
135  */
136 
137 #include <precomp.h>
138 
139 #ifdef INCLUDE_CMD_DIR
140 
141 
142 
143 /* Time Field enumeration */
144 enum ETimeField
145 {
146 	TF_CREATIONDATE		= 0,
147 	TF_MODIFIEDDATE		= 1,
148 	TF_LASTACCESSEDDATE	= 2
149 };
150 
151 /* Ordered by enumeration */
152 enum EOrderBy
153 {
154 	ORDER_NAME		= 0,
155 	ORDER_SIZE		= 1,
156 	ORDER_DIRECTORY	= 2,
157 	ORDER_EXTENSION	= 3,
158 	ORDER_TIME		= 4
159 };
160 
161 /* The struct for holding the switches */
162 typedef struct _DirSwitchesFlags
163 {
164 	BOOL bBareFormat;	/* Bare Format */
165 	BOOL bTSeperator;	/* Thousands seperator */
166 	BOOL bWideList;		/* Wide list format	*/
167 	BOOL bWideListColSort;	/* Wide list format but sorted by column */
168 	BOOL bLowerCase;	/* Uses lower case */
169 	BOOL bNewLongList;	/* New long list */
170 	BOOL bPause;		/* Pause per page */
171 	BOOL bUser;			/* Displays the owner of file */
172 	BOOL bRecursive;	/* Displays files in specified directory and all sub */
173 	BOOL bShortName;	/* Displays the sort name of files if exist	*/
174 	BOOL b4Digit;		/* Four digit year	*/
175 	struct
176 	{
177 		DWORD dwAttribVal;	/* The desired state of attribute */
178 		DWORD dwAttribMask;	/* Which attributes to check */
179 	} stAttribs;		/* Displays files with this attributes only */
180 	struct
181 	{
182 		enum EOrderBy eCriteria[3];	/* Criterias used to order by */
183 		BOOL bCriteriaRev[3];		/* If the criteria is in reversed order */
184 		short sCriteriaCount;		/* The quantity of criterias */
185 	} stOrderBy;		/* Ordered by criterias */
186 	struct
187 	{
188 		enum ETimeField eTimeField;	/* The time field that will be used for */
189 	} stTimeField;		/* The time field to display or use for sorting */
190 } DIRSWITCHFLAGS, *LPDIRSWITCHFLAGS;
191 
192 
193 typedef struct _DIRFINDLISTNODE
194 {
195   WIN32_FIND_DATA stFindInfo;
196   struct _DIRFINDLISTNODE *ptrNext;
197 } DIRFINDLISTNODE, *PDIRFINDLISTNODE;
198 
199 
200 typedef BOOL
201 (WINAPI *PGETFREEDISKSPACEEX)(LPCTSTR, PULARGE_INTEGER, PULARGE_INTEGER, PULARGE_INTEGER);
202 
203 
204 /* Globally save the # of dirs, files and bytes,
205  * probabaly later pass them to functions. Rob Lake  */
206 static ULONG recurse_dir_cnt;
207 static ULONG recurse_file_cnt;
208 static ULONGLONG recurse_bytes;
209 
210 
211 /*
212  * help
213  *
214  * displays help screen for dir
215  * Rob Lake
216  */
217 static VOID
218 DirHelp(VOID)
219 {
220   ConOutResPaging(TRUE, STRING_DIR_HELP1);
221 }
222 
223 
224 
225 /*
226  * DirReadParameters
227  *
228  * Parse the parameters and switches of the command line and exports them
229  */
230 static BOOL
231 DirReadParam(LPTSTR Line,				/* [IN] The line with the parameters & switches */
232 			LPTSTR** params,			/* [OUT] The parameters after parsing */
233 			LPINT entries,				/* [OUT] The number of parameters after parsing */
234 			LPDIRSWITCHFLAGS lpFlags)	/* [IN/OUT] The flags after calculating switches */
235 {
236 	TCHAR cCurSwitch;	/* The current switch */
237 	TCHAR cCurChar;		/* Current examing character */
238 	TCHAR cCurUChar;	/* Current upper examing character */
239 	BOOL bNegative;		/* Negative switch */
240 	BOOL bPNegative;	/* Negative switch parameter */
241 	BOOL bIntoQuotes;	/* A flag showing if we are in quotes (") */
242 	LPTSTR ptrStart;	/* A pointer to the first character of a parameter */
243 	LPTSTR ptrEnd;		/* A pointer to the last character of a parameter */
244 	BOOL bOrderByNoPar;	/* A flag to indicate /O with no switch parameter */
245 	LPTSTR temp;
246 
247 	/* Initialize parameter array */
248 	*params = NULL;
249 	*entries = 0;
250 
251 	/* Initialize variables; */
252 	cCurSwitch = _T(' ');
253 	bNegative = FALSE;
254 	bPNegative = FALSE;
255 
256 	/* We suppose that switch parameters
257 	   were given to avoid setting them to default
258 	   if the switch was not given */
259 	bOrderByNoPar = FALSE;
260 
261 	/* Main Loop (see README_DIR.txt) */
262 	/* scan the command line char per char, and we process its char */
263 	while (*Line)
264 	{
265 		/* we save current character as it is and its upper case */
266 		cCurChar = *Line;
267 		cCurUChar = _totupper(*Line);
268 
269 		/* 1st section (see README_DIR.txt) */
270 		/* When a switch is expecting */
271 		if (cCurSwitch == _T('/'))
272 		{
273 			while (*Line == _T(' '))
274 				Line++;
275 
276 			bNegative = (*Line == _T('-'));
277 			Line += bNegative;
278 
279 			cCurChar = *Line;
280 			cCurUChar = _totupper(*Line);
281 
282 			if ((cCurUChar == _T('A')) ||(cCurUChar == _T('T')) || (cCurUChar == _T('O')))
283 			{
284 				/* If positive, prepare for parameters... if negative, reset to defaults */
285 				switch (cCurUChar)
286 				{
287 				case _T('A'):
288 					lpFlags->stAttribs.dwAttribVal = 0L;
289 					lpFlags->stAttribs.dwAttribMask = 0L;
290 					if (bNegative)
291 						lpFlags->stAttribs.dwAttribMask = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
292 					break;
293 				case _T('T'):
294 					if (bNegative)
295 						lpFlags->stTimeField.eTimeField = TF_MODIFIEDDATE;
296 					break;
297 				case _T('O'):
298 					bOrderByNoPar = !bNegative;
299 					lpFlags->stOrderBy.sCriteriaCount = 0;
300 					break;
301 				}
302 
303 				if (!bNegative)
304 				{
305 					/* Positive switch, so it can take parameters. */
306 					cCurSwitch = cCurUChar;
307 					Line++;
308 					/* Skip optional leading colon */
309 					if (*Line == _T(':'))
310 						Line++;
311 					continue;
312 				}
313 			}
314 			else if (cCurUChar == _T('L'))
315 				lpFlags->bLowerCase = ! bNegative;
316 			else if (cCurUChar == _T('B'))
317 				lpFlags->bBareFormat = ! bNegative;
318 			else if (cCurUChar == _T('C'))
319 				lpFlags->bTSeperator = ! bNegative;
320 			else if (cCurUChar == _T('W'))
321 				lpFlags->bWideList = ! bNegative;
322 			else if (cCurUChar == _T('D'))
323 				lpFlags->bWideListColSort = ! bNegative;
324 			else if (cCurUChar == _T('N'))
325 				lpFlags->bNewLongList = ! bNegative;
326 			else if (cCurUChar == _T('P'))
327 				lpFlags->bPause = ! bNegative;
328 			else if (cCurUChar == _T('Q'))
329 				lpFlags->bUser = ! bNegative;
330 			else if (cCurUChar == _T('S'))
331 				lpFlags->bRecursive = ! bNegative;
332 			else if (cCurUChar == _T('X'))
333 				lpFlags->bShortName = ! bNegative;
334 			else if (cCurChar == _T('4'))
335 				lpFlags->b4Digit = ! bNegative;
336 			else if (cCurChar == _T('?'))
337 			{
338 				DirHelp();
339 				return FALSE;
340 			}
341 			else
342 			{
343 				error_invalid_switch ((TCHAR)_totupper (*Line));
344 				return FALSE;
345 			}
346 
347 			/* Make sure there's no extra characters at the end of the switch */
348 			if (Line[1] && Line[1] != _T('/') && Line[1] != _T(' '))
349 			{
350 				error_parameter_format(Line[1]);
351 				return FALSE;
352 			}
353 
354 			cCurSwitch = _T(' ');
355 		}
356 		else if (cCurSwitch == _T(' '))
357 		{
358 			/* 2nd section (see README_DIR.txt) */
359 			/* We are expecting parameter or the unknown */
360 
361 			if (cCurChar == _T('/'))
362 				cCurSwitch = _T('/');
363 			else if (cCurChar == _T(' '))
364 				/* do nothing */;
365 			else
366 			{
367 				/* This is a file/directory name parameter. Find its end */
368 				ptrStart = Line;
369 				bIntoQuotes = FALSE;
370 				while (*Line)
371 				{
372 					if (!bIntoQuotes && (*Line == _T('/') || *Line == _T(' ')))
373 						break;
374 					bIntoQuotes ^= (*Line == _T('"'));
375 					Line++;
376 				}
377 				ptrEnd = Line;
378 
379 				/* Copy it to the entries list */
380 				temp = cmd_alloc((ptrEnd - ptrStart + 1) * sizeof (TCHAR));
381 				if(!temp)
382 					return FALSE;
383 				memcpy(temp, ptrStart, (ptrEnd - ptrStart) * sizeof (TCHAR));
384 				temp[ptrEnd - ptrStart] = _T('\0');
385 				StripQuotes(temp);
386 				if(!add_entry(entries, params, temp))
387 				{
388 					cmd_free(temp);
389 					freep(*params);
390 					return FALSE;
391 				}
392 
393 				cmd_free(temp);
394 				continue;
395 			}
396 		}
397 		else
398 		{
399 			/* 3rd section (see README_DIR.txt) */
400 			/* We are waiting for switch parameters */
401 
402 			/* Check if there are no more switch parameters */
403 			if ((cCurChar == _T('/')) || ( cCurChar == _T(' ')))
404 			{
405 				/* Wrong desicion path, reprocess current character */
406 				cCurSwitch = _T(' ');
407 				continue;
408 			}
409 			/* Process parameter switch */
410 			switch(cCurSwitch)
411 			{
412 			case _T('A'):	/* Switch parameters for /A (attributes filter) */
413 				if(cCurChar == _T('-'))
414 					bPNegative = TRUE;
415 				else if(cCurUChar == _T('D'))
416 				{
417 					lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_DIRECTORY;
418 					if (bPNegative)
419 						lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_DIRECTORY;
420 					else
421 						lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_DIRECTORY;
422 				}
423 				else if(cCurUChar == _T('R'))
424 				{
425 					lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_READONLY;
426 					if (bPNegative)
427 						lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_READONLY;
428 					else
429 						lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_READONLY;
430 				}
431 				else if(cCurUChar == _T('H'))
432 				{
433 					lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_HIDDEN;
434 					if (bPNegative)
435 						lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_HIDDEN;
436 					else
437 						lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_HIDDEN;
438 				}
439 				else if(cCurUChar == _T('A'))
440 				{
441 					lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_ARCHIVE;
442 					if (bPNegative)
443 						lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_ARCHIVE;
444 					else
445 						lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_ARCHIVE;
446 				}
447 				else if(cCurUChar == _T('S'))
448 				{
449 					lpFlags->stAttribs.dwAttribMask |= FILE_ATTRIBUTE_SYSTEM;
450 					if (bPNegative)
451 						lpFlags->stAttribs.dwAttribVal &= ~FILE_ATTRIBUTE_SYSTEM;
452 					else
453 						lpFlags->stAttribs.dwAttribVal |= FILE_ATTRIBUTE_SYSTEM;
454 				}
455 				else
456 				{
457 					error_parameter_format((TCHAR)_totupper (*Line));
458 					return FALSE;
459 				}
460 				break;
461 			case _T('T'):	/* Switch parameters for /T (time field) */
462 				if(cCurUChar == _T('C'))
463 					lpFlags->stTimeField.eTimeField= TF_CREATIONDATE ;
464 				else if(cCurUChar == _T('A'))
465 					lpFlags->stTimeField.eTimeField= TF_LASTACCESSEDDATE ;
466 				else if(cCurUChar == _T('W'))
467 					lpFlags->stTimeField.eTimeField= TF_MODIFIEDDATE  ;
468 				else
469 				{
470 					error_parameter_format((TCHAR)_totupper (*Line));
471 					return FALSE;
472 				}
473 				break;
474 			case _T('O'):	/* Switch parameters for /O (order) */
475 				/* Ok a switch parameter was given */
476 				bOrderByNoPar = FALSE;
477 
478 				if(cCurChar == _T('-'))
479 					bPNegative = TRUE;
480 				else if(cCurUChar == _T('N'))
481 				{
482 					if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
483 					lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
484 					lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_NAME;
485 				}
486 				else if(cCurUChar == _T('S'))
487 				{
488 					if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
489 					lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
490 					lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_SIZE;
491 				}
492 				else if(cCurUChar == _T('G'))
493 				{
494 					if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
495 					lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
496 					lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_DIRECTORY;
497 				}
498 				else if(cCurUChar == _T('E'))
499 				{
500 					if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
501 					lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
502 					lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_EXTENSION;
503 				}
504 				else if(cCurUChar == _T('D'))
505 				{
506 					if (lpFlags->stOrderBy.sCriteriaCount < 3) lpFlags->stOrderBy.sCriteriaCount++;
507 					lpFlags->stOrderBy.bCriteriaRev[lpFlags->stOrderBy.sCriteriaCount - 1] = bPNegative;
508 					lpFlags->stOrderBy.eCriteria[lpFlags->stOrderBy.sCriteriaCount - 1] = ORDER_TIME;
509 				}
510 
511 				else
512 				{
513 					error_parameter_format((TCHAR)_totupper (*Line));
514 					return FALSE;
515 				}
516 
517 
518 			}
519 			/* We check if we calculated the negative value and realese the flag */
520 			if ((cCurChar != _T('-')) && bPNegative)
521 				bPNegative = FALSE;
522 		}
523 
524 		Line++;
525 	}
526 
527 	/* /O with no switch parameters acts like /O:GN */
528 	if (bOrderByNoPar)
529 	{
530 		lpFlags->stOrderBy.sCriteriaCount = 2;
531 		lpFlags->stOrderBy.eCriteria[0] = ORDER_DIRECTORY;
532 		lpFlags->stOrderBy.bCriteriaRev[0] = FALSE;
533 		lpFlags->stOrderBy.eCriteria[1] = ORDER_NAME;
534 		lpFlags->stOrderBy.bCriteriaRev[1] = FALSE;
535 	}
536 
537 	return TRUE;
538 }
539 
540 /* Print either with or without paging, depending on /P switch */
541 static INT
542 DirPrintf(LPDIRSWITCHFLAGS lpFlags, LPTSTR szFormat, ...)
543 {
544 	INT iReturn = 0;
545 	va_list arg_ptr;
546 	va_start(arg_ptr, szFormat);
547 	if (lpFlags->bPause)
548 		iReturn = ConPrintfPaging(FALSE, szFormat, arg_ptr, STD_OUTPUT_HANDLE);
549 	else
550 		ConPrintf(szFormat, arg_ptr, STD_OUTPUT_HANDLE);
551 	va_end(arg_ptr);
552 	return iReturn;
553 }
554 
555 
556 /*
557  * PrintDirectoryHeader
558  *
559  * print the header for the dir command
560  */
561 static BOOL
562 PrintDirectoryHeader(LPTSTR szPath, LPDIRSWITCHFLAGS lpFlags)
563 {
564   TCHAR szMsg[RC_STRING_MAX_SIZE];
565   TCHAR szFullDir[MAX_PATH];
566   TCHAR szRootName[MAX_PATH];
567   TCHAR szVolName[80];
568   LPTSTR pszFilePart;
569   DWORD dwSerialNr;
570 
571   if (lpFlags->bBareFormat)
572     return TRUE;
573 
574   if (GetFullPathName(szPath, sizeof(szFullDir) / sizeof(TCHAR), szFullDir, &pszFilePart) == 0)
575     {
576       ErrorMessage(GetLastError(), _T("Failed to build full directory path"));
577       return FALSE;
578     }
579 
580   if (pszFilePart != NULL)
581       *pszFilePart = _T('\0');
582 
583   /* get the media ID of the drive */
584   if (!GetVolumePathName(szFullDir, szRootName, sizeof(szRootName) / sizeof(TCHAR)) ||
585       !GetVolumeInformation(szRootName, szVolName, 80, &dwSerialNr,
586 			    NULL, NULL, NULL, 0))
587     {
588       return(TRUE);
589     }
590 
591   /* print drive info */
592   if (szVolName[0] != _T('\0'))
593     {
594       LoadString(CMD_ModuleHandle, STRING_DIR_HELP2, szMsg, RC_STRING_MAX_SIZE);
595       DirPrintf(lpFlags, szMsg, szRootName[0], szVolName);
596     }
597   else
598     {
599       LoadString(CMD_ModuleHandle, STRING_DIR_HELP3, szMsg, RC_STRING_MAX_SIZE);
600       DirPrintf(lpFlags, szMsg, szRootName[0]);
601     }
602 
603   /* print the volume serial number if the return was successful */
604   LoadString(CMD_ModuleHandle, STRING_DIR_HELP4, (LPTSTR) szMsg, RC_STRING_MAX_SIZE);
605   DirPrintf(lpFlags, szMsg, HIWORD(dwSerialNr), LOWORD(dwSerialNr));
606 
607   return TRUE;
608 }
609 
610 
611 static VOID
612 DirPrintFileDateTime(TCHAR *lpDate,
613                      TCHAR *lpTime,
614                      LPWIN32_FIND_DATA lpFile,
615                      LPDIRSWITCHFLAGS lpFlags)
616 {
617 	FILETIME ft;
618 	SYSTEMTIME dt;
619 
620 	/* Select the right time field */
621 	switch (lpFlags->stTimeField.eTimeField)
622 	{
623 		case TF_CREATIONDATE:
624 			if (!FileTimeToLocalFileTime(&lpFile->ftCreationTime, &ft))
625 				return;
626 			FileTimeToSystemTime(&ft, &dt);
627 			break;
628 
629 		case TF_LASTACCESSEDDATE :
630 			if (!FileTimeToLocalFileTime(&lpFile->ftLastAccessTime, &ft))
631 				return;
632 			FileTimeToSystemTime(&ft, &dt);
633 			break;
634 
635 		case TF_MODIFIEDDATE:
636 			if (!FileTimeToLocalFileTime(&lpFile->ftLastWriteTime, &ft))
637 				return;
638 			FileTimeToSystemTime(&ft, &dt);
639 			break;
640 	}
641 
642 	FormatDate(lpDate, &dt, lpFlags->b4Digit);
643 	FormatTime(lpTime, &dt);
644 }
645 
646 INT
647 FormatDate(TCHAR *lpDate, LPSYSTEMTIME dt, BOOL b4Digit)
648 {
649 	/* Format date */
650 	WORD wYear = b4Digit ? dt->wYear : dt->wYear%100;
651 	switch (nDateFormat)
652 	{
653 		case 0: /* mmddyy */
654 		default:
655 			return _stprintf(lpDate, _T("%02d%c%02d%c%0*d"),
656 					dt->wMonth, cDateSeparator,
657 					dt->wDay, cDateSeparator,
658 					b4Digit?4:2, wYear);
659 			break;
660 
661 		case 1: /* ddmmyy */
662 			return _stprintf(lpDate, _T("%02d%c%02d%c%0*d"),
663 					dt->wDay, cDateSeparator, dt->wMonth,
664 					cDateSeparator, b4Digit?4:2, wYear);
665 			break;
666 
667 		case 2: /* yymmdd */
668 			return _stprintf(lpDate, _T("%0*d%c%02d%c%02d"),
669 					b4Digit?4:2, wYear, cDateSeparator,
670 					dt->wMonth, cDateSeparator, dt->wDay);
671 			break;
672 	}
673 }
674 
675 INT
676 FormatTime(TCHAR *lpTime, LPSYSTEMTIME dt)
677 {
678 	/* Format Time */
679 	switch (nTimeFormat)
680 	{
681 		case 0: /* 12 hour format */
682 		default:
683 			return _stprintf(lpTime,_T("%02d%c%02u %cM"),
684 					(dt->wHour == 0 ? 12 : (dt->wHour <= 12 ? dt->wHour : dt->wHour - 12)),
685 					cTimeSeparator,
686 					 dt->wMinute, (dt->wHour <= 11 ? _T('A') : _T('P')));
687 			break;
688 
689 		case 1: /* 24 hour format */
690 			return _stprintf(lpTime, _T("%02d%c%02u"),
691 					dt->wHour, cTimeSeparator, dt->wMinute);
692 			break;
693 	}
694 }
695 
696 
697 static VOID
698 GetUserDiskFreeSpace(LPCTSTR lpRoot,
699 		     PULARGE_INTEGER lpFreeSpace)
700 {
701   PGETFREEDISKSPACEEX pGetFreeDiskSpaceEx;
702   HINSTANCE hInstance;
703   DWORD dwSecPerCl;
704   DWORD dwBytPerSec;
705   DWORD dwFreeCl;
706   DWORD dwTotCl;
707   ULARGE_INTEGER TotalNumberOfBytes, TotalNumberOfFreeBytes;
708 
709   lpFreeSpace->QuadPart = 0;
710 
711   hInstance = GetModuleHandle(_T("KERNEL32"));
712   if (hInstance != NULL)
713     {
714       pGetFreeDiskSpaceEx = (PGETFREEDISKSPACEEX)GetProcAddress(hInstance,
715 #ifdef _UNICODE
716 					                        "GetDiskFreeSpaceExW");
717 #else
718 				                                "GetDiskFreeSpaceExA");
719 #endif
720       if (pGetFreeDiskSpaceEx != NULL)
721 	{
722 	  if (pGetFreeDiskSpaceEx(lpRoot, lpFreeSpace, &TotalNumberOfBytes, &TotalNumberOfFreeBytes) == TRUE)
723 	    return;
724 	}
725     }
726 
727   GetDiskFreeSpace(lpRoot,
728 		   &dwSecPerCl,
729 		   &dwBytPerSec,
730 		   &dwFreeCl,
731 		   &dwTotCl);
732 
733   lpFreeSpace->QuadPart = dwSecPerCl * dwBytPerSec * dwFreeCl;
734 }
735 
736 
737 /*
738  * print_summary: prints dir summary
739  * Added by Rob Lake 06/17/98 to compact code
740  * Just copied Tim's Code and patched it a bit
741  *
742  */
743 static INT
744 PrintSummary(LPTSTR szPath,
745 	     ULONG ulFiles,
746 	     ULONG ulDirs,
747 	     ULONGLONG u64Bytes,
748 	     LPDIRSWITCHFLAGS lpFlags,
749 	     BOOL TotalSummary)
750 {
751 	TCHAR szMsg[RC_STRING_MAX_SIZE];
752 	TCHAR szBuffer[64];
753 	ULARGE_INTEGER uliFree;
754 
755 
756 	/* Here we check if we didn't find anything */
757 	if (!(ulFiles + ulDirs))
758 	{
759 		if (!lpFlags->bRecursive || (TotalSummary && lpFlags->bRecursive))
760 			error_file_not_found();
761 		return 1;
762 	}
763 
764 
765 	/* In bare format we don't print results */
766 	if (lpFlags->bBareFormat)
767 		return 0;
768 
769 	/* Print recursive specific results */
770 
771     /* Take this code offline to fix /S does not print duoble info */
772    if (TotalSummary && lpFlags->bRecursive)
773    {
774       ConvertULargeInteger(u64Bytes, szBuffer, sizeof(szBuffer), lpFlags->bTSeperator);
775       LoadString(CMD_ModuleHandle, STRING_DIR_HELP5, szMsg, RC_STRING_MAX_SIZE);
776       DirPrintf(lpFlags, szMsg, ulFiles, szBuffer);
777    }
778    else
779    {
780       /* Print File Summary */
781       /* Condition to print summary is:
782       If we are not in bare format and if we have results! */
783       ConvertULargeInteger(u64Bytes, szBuffer, 20, lpFlags->bTSeperator);
784       LoadString(CMD_ModuleHandle, STRING_DIR_HELP8, szMsg, RC_STRING_MAX_SIZE);
785       DirPrintf(lpFlags, szMsg, ulFiles, szBuffer);
786    }
787 
788 	/* Print total directories and freespace */
789 	if (!lpFlags->bRecursive || TotalSummary)
790 	{
791 		GetUserDiskFreeSpace(szPath, &uliFree);
792 		ConvertULargeInteger(uliFree.QuadPart, szBuffer, sizeof(szBuffer), lpFlags->bTSeperator);
793 		LoadString(CMD_ModuleHandle, STRING_DIR_HELP6, (LPTSTR) szMsg, RC_STRING_MAX_SIZE);
794 		DirPrintf(lpFlags, szMsg, ulDirs, szBuffer);
795 	}
796 
797 	return 0;
798 }
799 
800 /*
801  * getExt
802  *
803  * Get the extension of a filename
804  */
805 TCHAR* getExt(const TCHAR* file)
806 {
807         static TCHAR *NoExt = _T("");
808         TCHAR* lastdot = _tcsrchr(file, _T('.'));
809 	return (lastdot != NULL ? lastdot + 1 : NoExt);
810 }
811 
812 /*
813  * getName
814  *
815  * Get the name of the file without extension
816  */
817 static LPTSTR
818 getName(const TCHAR* file, TCHAR * dest)
819 {
820 	int iLen;
821 	LPTSTR end;
822 
823 	/* Check for "." and ".." folders */
824 	if ((_tcscmp(file, _T(".")) == 0) ||
825 	    (_tcscmp(file, _T("..")) == 0))
826 	{
827 		_tcscpy(dest,file);
828 		return dest;
829 	}
830 
831 	end = _tcsrchr(file, _T('.'));
832 	if (!end)
833 		iLen = _tcslen(file);
834 	else
835 		iLen = (end - file);
836 
837 
838 	_tcsncpy(dest, file, iLen);
839 	*(dest + iLen) = _T('\0');
840 
841 	return dest;
842 }
843 
844 
845 /*
846  *  DirPrintNewList
847  *
848  * The function that prints in new style
849  */
850 static VOID
851 DirPrintNewList(LPWIN32_FIND_DATA ptrFiles[],	/* [IN]Files' Info */
852 		DWORD dwCount,			/* [IN] The quantity of files */
853 		TCHAR *szCurPath,		/* [IN] Full path of current directory */
854 		LPDIRSWITCHFLAGS lpFlags)	/* [IN] The flags used */
855 {
856   DWORD i;
857   TCHAR szSize[30];
858   TCHAR szShortName[15];
859   TCHAR szDate[20];
860   TCHAR szTime[20];
861   INT iSizeFormat;
862   ULARGE_INTEGER u64FileSize;
863 
864   for (i = 0; i < dwCount && !bCtrlBreak; i++)
865   {
866     /* Calculate size */
867     if (ptrFiles[i]->dwFileAttributes & FILE_ATTRIBUTE_REPARSE_POINT)
868     {
869       /* Junction */
870       iSizeFormat = -14;
871       _tcscpy(szSize, _T("<JUNCTION>"));
872     }
873     else if (ptrFiles[i]->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
874     {
875       /* Directory */
876       iSizeFormat = -14;
877       _tcscpy(szSize, _T("<DIR>"));
878     }
879     else
880     {
881       /* File */
882       iSizeFormat = 14;
883       u64FileSize.HighPart = ptrFiles[i]->nFileSizeHigh;
884       u64FileSize.LowPart = ptrFiles[i]->nFileSizeLow;
885       ConvertULargeInteger(u64FileSize.QuadPart, szSize, 20, lpFlags->bTSeperator);
886     }
887 
888     /* Calculate short name */
889     szShortName[0] = _T('\0');
890     if (lpFlags->bShortName)
891       _stprintf(szShortName, _T(" %-12s"), ptrFiles[i]->cAlternateFileName);
892 
893     /* Format date and time */
894     DirPrintFileDateTime(szDate, szTime, ptrFiles[i], lpFlags);
895 
896     /* Print the line */
897     DirPrintf(lpFlags, _T("%10s  %-6s    %*s%s %s\n"),
898 							szDate,
899 							szTime,
900 							iSizeFormat,
901 							szSize,
902 							szShortName,
903 							ptrFiles[i]->cFileName);
904   }
905 }
906 
907 
908 /*
909  *  DirPrintWideList
910  *
911  * The function that prints in wide list
912  */
913 static VOID
914 DirPrintWideList(LPWIN32_FIND_DATA ptrFiles[],	/* [IN] Files' Info */
915 				 DWORD dwCount,			/* [IN] The quantity of files */
916 				 TCHAR *szCurPath,		/* [IN] Full path of current directory */
917 				 LPDIRSWITCHFLAGS lpFlags)	/* [IN] The flags used */
918 {
919   SHORT iScreenWidth;
920   USHORT iColumns;
921   USHORT iLines;
922   UINT iLongestName;
923   TCHAR szTempFname[MAX_PATH];
924   DWORD i;
925   DWORD j;
926   DWORD temp;
927 
928   /* Calculate longest name */
929   iLongestName = 1;
930   for (i = 0; i < dwCount; i++)
931   {
932     if (ptrFiles[i]->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
933     {
934       /* Directories need 2 additinal characters for brackets */
935       if ((_tcslen(ptrFiles[i]->cFileName) + 2) > iLongestName)
936         iLongestName = _tcslen(ptrFiles[i]->cFileName) + 2;
937     }
938     else
939     {
940       if (_tcslen(ptrFiles[i]->cFileName) > iLongestName)
941         iLongestName = _tcslen(ptrFiles[i]->cFileName);
942     }
943   }
944 
945   /* Count the highest number of columns */
946   GetScreenSize(&iScreenWidth, 0);
947   iColumns = iScreenWidth / iLongestName;
948 
949   /* Check if there is enough space for spaces between names */
950   if (((iLongestName * iColumns) + iColumns) >= (UINT)iScreenWidth)
951     iColumns --;
952 
953   /* A last check at iColumns to avoid division by zero */
954   if (!(iColumns))
955     iColumns = 1;
956 
957   /* Calculate the lines that will be printed */
958   iLines = (USHORT)((dwCount + iColumns - 1) / iColumns);
959 
960   for (i = 0; i < iLines && !bCtrlBreak; i++)
961   {
962     for (j = 0; j < iColumns; j++)
963     {
964       if (lpFlags->bWideListColSort)
965       {
966         /* Print Column sorted */
967         temp = (j * iLines) + i;
968       }
969       else
970       {
971         /* Print Line sorted */
972         temp = (i * iColumns) + j;
973       }
974 
975       if (temp >= dwCount)
976          break;
977 
978       if (ptrFiles[temp]->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
979         _stprintf(szTempFname, _T("[%s]"), ptrFiles[temp]->cFileName);
980       else
981         _stprintf(szTempFname, _T("%s"), ptrFiles[temp]->cFileName);
982 
983       DirPrintf(lpFlags, _T("%-*s"), iLongestName + 1, szTempFname);
984     }
985 
986     /* Add a new line after the last item in the column */
987     DirPrintf(lpFlags, _T("\n"));
988   }
989 }
990 
991 
992 /*
993  *  DirPrintOldList
994  *
995  * The function that prints in old style
996  */
997 static VOID
998 DirPrintOldList(LPWIN32_FIND_DATA ptrFiles[],	/* [IN] Files' Info */
999 				DWORD dwCount,					/* [IN] The quantity of files */
1000 				TCHAR * szCurPath,				/* [IN] Full path of current directory */
1001 				LPDIRSWITCHFLAGS lpFlags)		/* [IN] The flags used */
1002 {
1003 DWORD i;						/* An indexer for "for"s */
1004 TCHAR szName[10];				/* The name of file */
1005 TCHAR szExt[5];					/* The extension of file */
1006 TCHAR szDate[30],szTime[30];	/* Used to format time and date */
1007 TCHAR szSize[30];				/* The size of file */
1008 int iSizeFormat;				/* The format of size field */
1009 ULARGE_INTEGER u64FileSize;		/* The file size */
1010 
1011 	for (i = 0; i < dwCount && !bCtrlBreak; i++)
1012 	{
1013 		/* Broke 8.3 format */
1014 		if (*ptrFiles[i]->cAlternateFileName )
1015 		{
1016 			/* If the file is long named then we read the alter name */
1017 			getName( ptrFiles[i]->cAlternateFileName, szName);
1018 			_tcscpy(szExt, getExt( ptrFiles[i]->cAlternateFileName));
1019 		}
1020 		else
1021 		{
1022 			/* If the file is not long name we read its original name */
1023 			getName( ptrFiles[i]->cFileName, szName);
1024 			_tcscpy(szExt, getExt( ptrFiles[i]->cFileName));
1025 		}
1026 
1027 		/* Calculate size */
1028 		if (ptrFiles[i]->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1029 		{
1030 			/* Directory, no size it's a directory*/
1031 			iSizeFormat = -17;
1032 			_tcscpy(szSize, _T("<DIR>"));
1033 		}
1034 		else
1035 		{
1036 			/* File */
1037 			iSizeFormat = 17;
1038 			u64FileSize.HighPart = ptrFiles[i]->nFileSizeHigh;
1039 			u64FileSize.LowPart = ptrFiles[i]->nFileSizeLow;
1040 			ConvertULargeInteger(u64FileSize.QuadPart, szSize, 20, lpFlags->bTSeperator);
1041 		}
1042 
1043 		/* Format date and time */
1044 		DirPrintFileDateTime(szDate,szTime,ptrFiles[i],lpFlags);
1045 
1046 		/* Print the line */
1047 		DirPrintf(lpFlags, _T("%-8s %-3s  %*s %s  %s\n"),
1048 								szName,			/* The file's 8.3 name */
1049 								szExt,			/* The file's 8.3 extension */
1050 								iSizeFormat,	/* print format for size column */
1051 								szSize,			/* The size of file or "<DIR>" for dirs */
1052 								szDate,			/* The date of file/dir */
1053 								szTime);		/* The time of file/dir */
1054 	}
1055 }
1056 
1057 /*
1058  *  DirPrintBareList
1059  *
1060  * The function that prints in bare format
1061  */
1062 static VOID
1063 DirPrintBareList(LPWIN32_FIND_DATA ptrFiles[],	/* [IN] Files' Info */
1064 				 DWORD dwCount,			/* [IN] The number of files */
1065 				 LPTSTR lpCurPath,		/* [IN] Full path of current directory */
1066 				 LPDIRSWITCHFLAGS lpFlags)	/* [IN] The flags used */
1067 {
1068 	DWORD i;
1069 
1070 	for (i = 0; i < dwCount && !bCtrlBreak; i++)
1071 	{
1072 		if ((_tcscmp(ptrFiles[i]->cFileName, _T(".")) == 0) ||
1073 		    (_tcscmp(ptrFiles[i]->cFileName, _T("..")) == 0))
1074 		{
1075 			/* at bare format we don't print "." and ".." folder */
1076 			continue;
1077 		}
1078 		if (lpFlags->bRecursive)
1079 		{
1080 			/* at recursive mode we print full path of file */
1081 			DirPrintf(lpFlags, _T("%s\\%s\n"), lpCurPath, ptrFiles[i]->cFileName);
1082 		}
1083 		else
1084 		{
1085 			/* if we are not in recursive mode we print the file names */
1086 			DirPrintf(lpFlags, _T("%s\n"), ptrFiles[i]->cFileName);
1087 		}
1088 	}
1089 }
1090 
1091 
1092 /*
1093  * DirPrintFiles
1094  *
1095  * The functions that prints the files list
1096  */
1097 static VOID
1098 DirPrintFiles(LPWIN32_FIND_DATA ptrFiles[],	/* [IN] Files' Info */
1099 			  DWORD dwCount,			/* [IN] The quantity of files */
1100 			  TCHAR *szCurPath,			/* [IN] Full path of current directory */
1101 			  LPDIRSWITCHFLAGS lpFlags)		/* [IN] The flags used */
1102 {
1103 	TCHAR szMsg[RC_STRING_MAX_SIZE];
1104 	TCHAR szTemp[MAX_PATH];			/* A buffer to format the directory header */
1105 
1106 	/* Print trailing backslash for root directory of drive */
1107 	_tcscpy(szTemp, szCurPath);
1108 	if (_tcslen(szTemp) == 2 && szTemp[1] == _T(':'))
1109 		_tcscat(szTemp, _T("\\"));
1110 
1111 	/* Condition to print header:
1112 	   We are not printing in bare format
1113 	   and if we are in recursive mode... we must have results */
1114 	if (!(lpFlags->bBareFormat ) && !((lpFlags->bRecursive) && (dwCount <= 0)))
1115 	{
1116 		LoadString(CMD_ModuleHandle, STRING_DIR_HELP7, szMsg, RC_STRING_MAX_SIZE);
1117 		if (DirPrintf(lpFlags, szMsg, szTemp))
1118 			return;
1119 	}
1120 
1121 	if (lpFlags->bBareFormat)
1122 	{
1123 		/* Bare format */
1124 		DirPrintBareList(ptrFiles, dwCount, szCurPath, lpFlags);
1125 	}
1126 	else if(lpFlags->bShortName)
1127 	{
1128 		/* New list style / Short names */
1129 		DirPrintNewList(ptrFiles, dwCount, szCurPath, lpFlags);
1130 	}
1131 	else if(lpFlags->bWideListColSort || lpFlags->bWideList)
1132 	{
1133 		/* Wide list */
1134 		DirPrintWideList(ptrFiles, dwCount, szCurPath, lpFlags);
1135 	}
1136 	else if (lpFlags->bNewLongList )
1137 	{
1138 		/* New list style*/
1139 		DirPrintNewList(ptrFiles, dwCount, szCurPath, lpFlags);
1140 	}
1141 	else
1142 	{
1143 		/* If nothing is selected old list is the default */
1144 		DirPrintOldList(ptrFiles, dwCount, szCurPath, lpFlags);
1145 	}
1146 }
1147 
1148 
1149 
1150 /*
1151  * CompareFiles
1152  *
1153  * Compares 2 files based on the order criteria
1154  */
1155 static BOOL
1156 CompareFiles(LPWIN32_FIND_DATA lpFile1,	/* [IN] A pointer to WIN32_FIND_DATA of file 1 */
1157 			 LPWIN32_FIND_DATA lpFile2,	/* [IN] A pointer to WIN32_FIND_DATA of file 2 */
1158 			 LPDIRSWITCHFLAGS lpFlags)	/* [IN] The flags that we use to list */
1159 {
1160   ULARGE_INTEGER u64File1;
1161   ULARGE_INTEGER u64File2;
1162   int i;
1163   long iComp = 0;					/* The comparison result */
1164 
1165 	/* Calculate critiries by order given from user */
1166 	for (i = 0;i < lpFlags->stOrderBy.sCriteriaCount;i++)
1167 	{
1168 
1169 		/* Calculate criteria */
1170 		switch(lpFlags->stOrderBy.eCriteria[i])
1171 		{
1172 		case ORDER_SIZE:		/* Order by size /o:s */
1173 			/* concat the 32bit integers to a 64bit */
1174 			u64File1.LowPart = lpFile1->nFileSizeLow;
1175 			u64File1.HighPart = lpFile1->nFileSizeHigh;
1176 			u64File2.LowPart = lpFile2->nFileSizeLow;
1177 			u64File2.HighPart = lpFile2->nFileSizeHigh;
1178 
1179 			/* In case that differnce is too big for a long */
1180 			if (u64File1.QuadPart < u64File2.QuadPart)
1181 				iComp = -1;
1182 			else if (u64File1.QuadPart > u64File2.QuadPart)
1183 				iComp = 1;
1184 			else
1185 				iComp = 0;
1186 			break;
1187 
1188 		case ORDER_DIRECTORY:	/* Order by directory attribute /o:g */
1189 			iComp = ((lpFile2->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)-
1190 				(lpFile1->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY));
1191 			break;
1192 
1193 		case ORDER_EXTENSION:	/* Order by extension name /o:e */
1194 			iComp = _tcsicmp(getExt(lpFile1->cFileName),getExt(lpFile2->cFileName));
1195 			break;
1196 
1197 		case ORDER_NAME:		/* Order by filename /o:n */
1198 			iComp = _tcsicmp(lpFile1->cFileName, lpFile2->cFileName);
1199 			break;
1200 
1201 		case ORDER_TIME:		/* Order by file's time /o:t */
1202 			/* We compare files based on the time field selected by /t */
1203 			switch(lpFlags->stTimeField.eTimeField)
1204 			{
1205 			case TF_CREATIONDATE:
1206 				/* concat the 32bit integers to a 64bit */
1207 				u64File1.LowPart = lpFile1->ftCreationTime.dwLowDateTime;
1208 				u64File1.HighPart = lpFile1->ftCreationTime.dwHighDateTime ;
1209 				u64File2.LowPart = lpFile2->ftCreationTime.dwLowDateTime;
1210 				u64File2.HighPart = lpFile2->ftCreationTime.dwHighDateTime ;
1211 				break;
1212 			case TF_LASTACCESSEDDATE :
1213 				/* concat the 32bit integers to a 64bit */
1214 				u64File1.LowPart = lpFile1->ftLastAccessTime.dwLowDateTime;
1215 				u64File1.HighPart = lpFile1->ftLastAccessTime.dwHighDateTime ;
1216 				u64File2.LowPart = lpFile2->ftLastAccessTime.dwLowDateTime;
1217 				u64File2.HighPart = lpFile2->ftLastAccessTime.dwHighDateTime ;
1218 				break;
1219 			case TF_MODIFIEDDATE:
1220 				/* concat the 32bit integers to a 64bit */
1221 				u64File1.LowPart = lpFile1->ftLastWriteTime.dwLowDateTime;
1222 				u64File1.HighPart = lpFile1->ftLastWriteTime.dwHighDateTime ;
1223 				u64File2.LowPart = lpFile2->ftLastWriteTime.dwLowDateTime;
1224 				u64File2.HighPart = lpFile2->ftLastWriteTime.dwHighDateTime ;
1225 				break;
1226 			}
1227 
1228 			/* In case that differnce is too big for a long */
1229 			if (u64File1.QuadPart < u64File2.QuadPart)
1230 				iComp = -1;
1231 			else if (u64File1.QuadPart > u64File2.QuadPart)
1232 				iComp = 1;
1233 			else
1234 				iComp = 0;
1235 			break;
1236 		}
1237 
1238 		/* Reverse if desired */
1239 		if (lpFlags->stOrderBy.bCriteriaRev[i])
1240 			iComp *= -1;
1241 
1242 		/* If that criteria was enough for distinguishing
1243 		   the files/dirs,there is no need to calculate the others*/
1244 		if (iComp != 0) break;
1245 	}
1246 
1247 	/* Translate the value of iComp to boolean */
1248 	return iComp > 0;
1249 }
1250 
1251 /*
1252  * QsortFiles
1253  *
1254  * Sort files by the order criterias using quicksort method
1255  */
1256 static VOID
1257 QsortFiles(LPWIN32_FIND_DATA ptrArray[],	/* [IN/OUT] The array with file info pointers */
1258 	   int i,				/* [IN]     The index of first item in array */
1259 	   int j,				/* [IN]     The index to last item in array */
1260 	   LPDIRSWITCHFLAGS lpFlags)		/* [IN]     The flags that we will use to sort */
1261 {
1262 	LPWIN32_FIND_DATA lpTemp;	/* A temporary pointer */
1263 	BOOL Way;
1264 
1265 	if (i < j)
1266 	{
1267 		int First = i, Last = j, Temp;
1268 		Way = TRUE;
1269 		while (i != j)
1270 		{
1271 			if (Way == CompareFiles(ptrArray[i], ptrArray[j], lpFlags))
1272 			{
1273 				/* Swap the pointers of the array */
1274 				lpTemp = ptrArray[i];
1275 				ptrArray[i]= ptrArray[j];
1276 				ptrArray[j] = lpTemp;
1277 
1278 				/* Swap the indexes for inverting sorting */
1279 				Temp = i;
1280 				i = j;
1281 				j =Temp;
1282 
1283 				Way = !Way;
1284 			}
1285 
1286 			j += (!Way - Way);
1287 		}
1288 
1289 		QsortFiles(ptrArray,First, i-1, lpFlags);
1290 		QsortFiles(ptrArray,i+1,Last, lpFlags);
1291 	}
1292 }
1293 
1294 
1295 
1296 /*
1297  * DirList
1298  *
1299  * The functions that does everything except for printing results
1300  */
1301 static INT
1302 DirList(LPTSTR szPath,			/* [IN] The path that dir starts */
1303 		LPDIRSWITCHFLAGS lpFlags)	/* [IN] The flags of the listing */
1304 {
1305 	BOOL fPoint;							/* If szPath is a file with extension fPoint will be True*/
1306 	HANDLE hSearch;							/* The handle of the search */
1307 	HANDLE hRecSearch;						/* The handle for searching recursivly */
1308 	WIN32_FIND_DATA wfdFileInfo;			/* The info of file that found */
1309 	LPWIN32_FIND_DATA * ptrFileArray;		/* An array of pointers with all the files */
1310 	PDIRFINDLISTNODE ptrStartNode;			/* The pointer to the first node */
1311 	PDIRFINDLISTNODE ptrNextNode;			/* A pointer used for relatives refernces */
1312 	TCHAR szFullPath[MAX_PATH];				/* The full path that we are listing with trailing \ */
1313 	TCHAR szSubPath[MAX_PATH];
1314 	LPTSTR pszFilePart;
1315 	DWORD dwCount;							/* A counter of files found in directory */
1316 	DWORD dwCountFiles;						/* Counter for files */
1317 	DWORD dwCountDirs;						/* Counter for directories */
1318 	ULONGLONG u64CountBytes;				/* Counter for bytes */
1319 	ULARGE_INTEGER u64Temp;					/* A temporary counter */
1320 
1321 	/* Initialize Variables */
1322 	ptrStartNode = NULL;
1323 	ptrNextNode = NULL;
1324 	dwCount = 0;
1325 	dwCountFiles = 0;
1326 	dwCountDirs = 0;
1327 	u64CountBytes = 0;
1328 	fPoint= FALSE;
1329 
1330 	/* Create szFullPath */
1331 	if (GetFullPathName(szPath, sizeof(szFullPath) / sizeof(TCHAR), szFullPath, &pszFilePart) == 0)
1332 	{
1333 		_tcscpy (szFullPath, szPath);
1334 		pszFilePart = NULL;
1335 	}
1336 
1337 	/* If no wildcard or file was specified and this is a directory, then
1338 	   display all files in it */
1339 	if (pszFilePart == NULL || IsExistingDirectory(szFullPath))
1340 	{
1341 		pszFilePart = &szFullPath[_tcslen(szFullPath)];
1342 		if (pszFilePart[-1] != _T('\\'))
1343 			*pszFilePart++ = _T('\\');
1344 		_tcscpy(pszFilePart, _T("*"));
1345 	}
1346 
1347 	/* Prepare the linked list, first node is allocated */
1348 	ptrStartNode = cmd_alloc(sizeof(DIRFINDLISTNODE));
1349 	if (ptrStartNode == NULL)
1350 	{
1351 		WARN("DEBUG: Cannot allocate memory for ptrStartNode!\n");
1352 		return 1;	/* Error cannot allocate memory for 1st object */
1353 	}
1354 	ptrNextNode = ptrStartNode;
1355 
1356 	/*Checking ir szPath is a File with/wout extension*/
1357 	if (szPath[_tcslen(szPath) - 1] == _T('.'))
1358 		fPoint= TRUE;
1359 
1360 	/* Collect the results for the current folder */
1361 	hSearch = FindFirstFile(szFullPath, &wfdFileInfo);
1362 	if (hSearch != INVALID_HANDLE_VALUE)
1363 	{
1364 		do
1365 		{
1366 			/*If retrieved FileName has extension,and szPath doesnt have extension then JUMP the retrieved FileName*/
1367 			if(_tcschr(wfdFileInfo.cFileName,_T('.'))&&(fPoint==TRUE))
1368 			{
1369 				continue;
1370 			/* Here we filter all the specified attributes */
1371 			}else if ((wfdFileInfo.dwFileAttributes & lpFlags->stAttribs.dwAttribMask )
1372 					== (lpFlags->stAttribs.dwAttribMask & lpFlags->stAttribs.dwAttribVal ))
1373 				{
1374 					ptrNextNode->ptrNext = cmd_alloc(sizeof(DIRFINDLISTNODE));
1375 					if (ptrNextNode->ptrNext == NULL)
1376 					{
1377 						WARN("DEBUG: Cannot allocate memory for ptrNextNode->ptrNext!\n");
1378 						while (ptrStartNode)
1379 						{
1380 							ptrNextNode = ptrStartNode->ptrNext;
1381 							cmd_free(ptrStartNode);
1382 							ptrStartNode = ptrNextNode;
1383 							dwCount --;
1384 						}
1385 						FindClose(hSearch);
1386 						return 1;
1387 					}
1388 
1389 				/* If cmd_alloc fails we go to next file in hope it works,
1390 				   without braking the linked list! */
1391 					if (ptrNextNode->ptrNext)
1392 					{
1393 					/* Copy the info of search at linked list */
1394 						memcpy(&ptrNextNode->ptrNext->stFindInfo,
1395 								&wfdFileInfo,
1396 								sizeof(WIN32_FIND_DATA));
1397 
1398 					/* If lower case is selected do it here */
1399 						if (lpFlags->bLowerCase)
1400 						{
1401 						_tcslwr(ptrNextNode->ptrNext->stFindInfo.cAlternateFileName);
1402 						_tcslwr(ptrNextNode->ptrNext->stFindInfo.cFileName);
1403 						}
1404 
1405 					/* Continue at next node at linked list */
1406 						ptrNextNode = ptrNextNode->ptrNext;
1407 						dwCount ++;
1408 
1409 					/* Grab statistics */
1410 						if (wfdFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
1411 						{
1412 						/* Directory */
1413 							dwCountDirs++;
1414 						}
1415 						else
1416 							{
1417 						/* File */
1418 							dwCountFiles++;
1419 							u64Temp.HighPart = wfdFileInfo.nFileSizeHigh;
1420 							u64Temp.LowPart = wfdFileInfo.nFileSizeLow;
1421 							u64CountBytes += u64Temp.QuadPart;
1422 						}
1423 					}
1424 				}
1425 		} while (FindNextFile(hSearch, &wfdFileInfo));
1426 		FindClose(hSearch);
1427 	}
1428 
1429 	/* Terminate list */
1430 	ptrNextNode->ptrNext = NULL;
1431 
1432 	/* Calculate and allocate space need for making an array of pointers */
1433 	ptrFileArray = cmd_alloc(sizeof(LPWIN32_FIND_DATA) * dwCount);
1434 	if (ptrFileArray == NULL)
1435 	{
1436 		WARN("DEBUG: Cannot allocate memory for ptrFileArray!\n");
1437 		while (ptrStartNode)
1438 		{
1439 			ptrNextNode = ptrStartNode->ptrNext;
1440 			cmd_free(ptrStartNode);
1441 			ptrStartNode = ptrNextNode;
1442 			dwCount --;
1443 		}
1444 		return 1;
1445 	}
1446 
1447 	/*
1448 	 * Create an array of pointers from the linked list
1449 	 * this will be used to sort and print data, rather than the list
1450 	 */
1451 	ptrNextNode = ptrStartNode;
1452 	dwCount = 0;
1453 	while (ptrNextNode->ptrNext)
1454 	{
1455 		*(ptrFileArray + dwCount) = &ptrNextNode->ptrNext->stFindInfo;
1456 		ptrNextNode = ptrNextNode->ptrNext;
1457 		dwCount++;
1458 	}
1459 
1460 	/* Sort Data if requested*/
1461 	if (lpFlags->stOrderBy.sCriteriaCount > 0)
1462 		QsortFiles(ptrFileArray, 0, dwCount-1, lpFlags);
1463 
1464 	/* Print Data */
1465 	pszFilePart[-1] = _T('\0'); /* truncate to directory name only */
1466 	DirPrintFiles(ptrFileArray, dwCount, szFullPath, lpFlags);
1467 	pszFilePart[-1] = _T('\\');
1468 
1469 	if (lpFlags->bRecursive)
1470 	{
1471 		PrintSummary(szFullPath,
1472 			dwCountFiles,
1473 			dwCountDirs,
1474 			u64CountBytes,
1475 			lpFlags,
1476 			FALSE);
1477 	}
1478 
1479 	/* Free array */
1480 	cmd_free(ptrFileArray);
1481 	/* Free linked list */
1482 	while (ptrStartNode)
1483 	{
1484 		ptrNextNode = ptrStartNode->ptrNext;
1485 		cmd_free(ptrStartNode);
1486 		ptrStartNode = ptrNextNode;
1487 		dwCount --;
1488 	}
1489 
1490 	if (CheckCtrlBreak(BREAK_INPUT))
1491 		return 1;
1492 
1493 
1494 	/* Add statistics to recursive statistics*/
1495 	recurse_dir_cnt += dwCountDirs;
1496 	recurse_file_cnt += dwCountFiles;
1497 	recurse_bytes += u64CountBytes;
1498 
1499 	/* Do the recursive job if requested
1500 	   the recursive is be done on ALL(indepent of their attribs)
1501 	   directoried of the current one.*/
1502 	if (lpFlags->bRecursive)
1503 	{
1504 		/* The new search is involving any *.* file */
1505 		memcpy(szSubPath, szFullPath, (pszFilePart - szFullPath) * sizeof(TCHAR));
1506 		_tcscpy(&szSubPath[pszFilePart - szFullPath], _T("*.*"));
1507 
1508 		hRecSearch = FindFirstFile (szSubPath, &wfdFileInfo);
1509 		if (hRecSearch != INVALID_HANDLE_VALUE)
1510 		{
1511 			do
1512 			{
1513 				/* We search for directories other than "." and ".." */
1514 				if ((_tcsicmp(wfdFileInfo.cFileName, _T(".")) != 0) &&
1515 				    (_tcsicmp(wfdFileInfo.cFileName, _T("..")) != 0 ) &&
1516 				    (wfdFileInfo.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
1517 				{
1518 					/* Concat the path and the directory to do recursive */
1519 					memcpy(szSubPath, szFullPath, (pszFilePart - szFullPath) * sizeof(TCHAR));
1520 					_tcscpy(&szSubPath[pszFilePart - szFullPath], wfdFileInfo.cFileName);
1521 					_tcscat(szSubPath, _T("\\"));
1522 					_tcscat(szSubPath, pszFilePart);
1523 
1524 					/* We do the same for the folder */
1525 					if (DirList(szSubPath, lpFlags) != 0)
1526 					{
1527 						FindClose(hRecSearch);
1528 						return 1;
1529 					}
1530 				}
1531 			} while(FindNextFile(hRecSearch, &wfdFileInfo));
1532 		}
1533 		FindClose(hRecSearch);
1534 	}
1535 
1536 	return 0;
1537 }
1538 
1539 
1540 
1541 /*
1542  * dir
1543  *
1544  * internal dir command
1545  */
1546 INT
1547 CommandDir(LPTSTR rest)
1548 {
1549 	TCHAR	dircmd[256];	/* A variable to store the DIRCMD enviroment variable */
1550 	TCHAR	path[MAX_PATH];
1551 	TCHAR	prev_volume[MAX_PATH];
1552 	LPTSTR*	params = NULL;
1553 	LPTSTR	pszFilePart;
1554 	INT		entries = 0;
1555 	UINT	loop = 0;
1556 	DIRSWITCHFLAGS stFlags;
1557 	INT	ret = 1;
1558 	BOOL ChangedVolume;
1559 
1560 	/* Initialize Switch Flags < Default switches are setted here!> */
1561 	stFlags.b4Digit = TRUE;
1562 	stFlags.bBareFormat = FALSE;
1563 	stFlags.bLowerCase = FALSE;
1564 	stFlags.bNewLongList = TRUE;
1565 	stFlags.bPause = FALSE;
1566 	stFlags.bRecursive = FALSE;
1567 	stFlags.bShortName = FALSE;
1568 	stFlags.bTSeperator = TRUE;
1569 	stFlags.bUser = FALSE;
1570 	stFlags.bWideList = FALSE;
1571 	stFlags.bWideListColSort = FALSE;
1572 	stFlags.stTimeField.eTimeField = TF_MODIFIEDDATE;
1573 	stFlags.stAttribs.dwAttribMask = FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_SYSTEM;
1574 	stFlags.stAttribs.dwAttribVal = 0L;
1575 	stFlags.stOrderBy.sCriteriaCount = 0;
1576 
1577 	nErrorLevel = 0;
1578 
1579 	/* read the parameters from the DIRCMD environment variable */
1580 	if (GetEnvironmentVariable (_T("DIRCMD"), dircmd, 256))
1581 		if (!DirReadParam(dircmd, &params, &entries, &stFlags))
1582 		{
1583 			nErrorLevel = 1;
1584 			goto cleanup;
1585 		}
1586 
1587 	/* read the parameters */
1588 	if (!DirReadParam(rest, &params, &entries, &stFlags) || CheckCtrlBreak(BREAK_INPUT))
1589 	{
1590 		nErrorLevel = 1;
1591 		goto cleanup;
1592 	}
1593 
1594 	/* default to current directory */
1595 	if(entries == 0) {
1596 		if(!add_entry(&entries, &params, _T("*"))) {
1597 			nErrorLevel = 1;
1598 			goto cleanup;
1599 		}
1600 	}
1601 
1602 	prev_volume[0] = _T('\0');
1603 
1604 	/* Reset paging state */
1605 	if (stFlags.bPause)
1606 		ConOutPrintfPaging(TRUE, _T(""));
1607 
1608 	for(loop = 0; loop < (UINT)entries; loop++)
1609 	{
1610 		if (CheckCtrlBreak(BREAK_INPUT))
1611 		{
1612 			nErrorLevel = 1;
1613 			goto cleanup;
1614 		}
1615 
1616 		recurse_dir_cnt = 0L;
1617 		recurse_file_cnt = 0L;
1618 		recurse_bytes = 0;
1619 
1620 	/* <Debug :>
1621 	   Uncomment this to show the final state of switch flags*/
1622 	{
1623 		int i;
1624 		TRACE("Attributes mask/value %x/%x\n",stFlags.stAttribs.dwAttribMask,stFlags.stAttribs.dwAttribVal  );
1625 		TRACE("(B) Bare format : %i\n", stFlags.bBareFormat );
1626 		TRACE("(C) Thousand : %i\n", stFlags.bTSeperator );
1627 		TRACE("(W) Wide list : %i\n", stFlags.bWideList );
1628 		TRACE("(D) Wide list sort by column : %i\n", stFlags.bWideListColSort );
1629 		TRACE("(L) Lowercase : %i\n", stFlags.bLowerCase );
1630 		TRACE("(N) New : %i\n", stFlags.bNewLongList );
1631 		TRACE("(O) Order : %i\n", stFlags.stOrderBy.sCriteriaCount );
1632 		for (i =0;i<stFlags.stOrderBy.sCriteriaCount;i++)
1633 			TRACE(" Order Criteria [%i]: %i (Reversed: %i)\n",i, stFlags.stOrderBy.eCriteria[i], stFlags.stOrderBy.bCriteriaRev[i] );
1634 		TRACE("(P) Pause : %i\n", stFlags.bPause  );
1635 		TRACE("(Q) Owner : %i\n", stFlags.bUser );
1636 		TRACE("(S) Recursive : %i\n", stFlags.bRecursive );
1637 		TRACE("(T) Time field : %i\n", stFlags.stTimeField.eTimeField );
1638 		TRACE("(X) Short names : %i\n", stFlags.bShortName );
1639 		TRACE("Parameter : %s\n", debugstr_aw(params[loop]) );
1640 	}
1641 
1642 		/* Print the drive header if the volume changed */
1643 		ChangedVolume = TRUE;
1644 
1645 		if (!stFlags.bBareFormat &&
1646 		    GetVolumePathName(params[loop], path, sizeof(path) / sizeof(TCHAR)))
1647 		{
1648 			if (!_tcscmp(path, prev_volume))
1649 				ChangedVolume = FALSE;
1650 			else
1651 				_tcscpy(prev_volume, path);
1652 		}
1653 		else if (GetFullPathName(params[loop], sizeof(path) / sizeof(TCHAR), path, &pszFilePart) != 0)
1654 		{
1655 			if (pszFilePart != NULL)
1656 				*pszFilePart = _T('\0');
1657 		}
1658 		else
1659 			_tcscpy(path, params[loop]);
1660 
1661 		if (ChangedVolume && !stFlags.bBareFormat) {
1662 			if (!PrintDirectoryHeader (params[loop], &stFlags)) {
1663 				nErrorLevel = 1;
1664 				goto cleanup;
1665 			}
1666 		}
1667 
1668 		/* do the actual dir */
1669 		if (DirList (params[loop], &stFlags))
1670 		{
1671 			nErrorLevel = 1;
1672 			goto cleanup;
1673 		}
1674 
1675 		/* print the footer */
1676 		PrintSummary(path,
1677 			recurse_file_cnt,
1678 			recurse_dir_cnt,
1679 			recurse_bytes,
1680 			&stFlags,
1681 			TRUE);
1682 	}
1683 
1684 	ret = 0;
1685 
1686 cleanup:
1687 	freep(params);
1688 
1689 	return ret;
1690 }
1691 
1692 #endif
1693 
1694 /* EOF */
1695