/*++ Copyright (c) 1998 Microsoft Corporation Module Name: dir.c Abstract: This module implements the dir commands. Author: Wesley Witt (wesw) 21-Oct-1998 Revision History: --*/ #include "cmdcons.h" #pragma hdrstop // // global external variables // extern LARGE_INTEGER glBias; typedef struct _DIR_STATS { unsigned FileCount; LONGLONG TotalSize; RcFileSystemType fsType; } DIR_STATS, *PDIR_STATS; BOOLEAN pRcDirEnumProc( IN LPCWSTR Directory, IN PFILE_BOTH_DIR_INFORMATION FileInfo, OUT NTSTATUS *Status, IN PDIR_STATS DirStats ); NTSTATUS SpSystemTimeToLocalTime ( IN PLARGE_INTEGER SystemTime, OUT PLARGE_INTEGER LocalTime ); NTSTATUS SpLocalTimeToSystemTime ( IN PLARGE_INTEGER LocalTime, OUT PLARGE_INTEGER SystemTime ); ULONG RcCmdDir( IN PTOKENIZED_LINE TokenizedLine ) { LPCWSTR Dir; LPWSTR Path; LPWSTR DosPath; LPWSTR p; NTSTATUS Status; WCHAR Drive[4]; IO_STATUS_BLOCK IoStatusBlock; UNICODE_STRING UnicodeString; HANDLE Handle; OBJECT_ATTRIBUTES Obja; DIR_STATS DirStats; ULONG u; ULONG rc; PFILE_FS_VOLUME_INFORMATION VolumeInfo; FILE_FS_SIZE_INFORMATION SizeInfo; BYTE bfFSInfo[sizeof(FILE_FS_ATTRIBUTE_INFORMATION) + (MAX_PATH*2)]; PFILE_FS_ATTRIBUTE_INFORMATION pFSInfo = 0; if (RcCmdParseHelp( TokenizedLine, MSG_DIR_HELP )) { return 1; } // // If there's no argument, then we want the current directory. // Dir = (TokenizedLine->TokenCount == 2) ? TokenizedLine->Tokens->Next->String : L"."; // // Canonicalize the name once to get a full DOS-style path // we can print out, and another time to get the NT-style path // we'll use to actually do the work. // if (!RcFormFullPath(Dir,_CmdConsBlock->TemporaryBuffer,FALSE)) { RcMessageOut(MSG_INVALID_PATH); return 1; } DosPath = SpDupStringW(_CmdConsBlock->TemporaryBuffer); if (!RcFormFullPath(Dir,_CmdConsBlock->TemporaryBuffer,TRUE)) { RcMessageOut(MSG_INVALID_PATH); return 1; } Path = SpDupStringW(_CmdConsBlock->TemporaryBuffer); // // Open up the root directory of the drive so we can query // the volume label, serial number, and free space. // Drive[0] = DosPath[0]; Drive[1] = L':'; Drive[2] = L'\\'; Drive[3] = 0; if (!RcFormFullPath(Drive,_CmdConsBlock->TemporaryBuffer,TRUE)) { DEBUG_PRINTF(( "couldn't open root of drive!" )); RcNtError( STATUS_NO_MEDIA_IN_DEVICE, MSG_NO_MEDIA_IN_DEVICE ); goto c2; } INIT_OBJA(&Obja,&UnicodeString,_CmdConsBlock->TemporaryBuffer); Status = ZwOpenFile( &Handle, FILE_READ_ATTRIBUTES, &Obja, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_DIRECTORY_FILE ); pRcEnableMoreMode(); if(NT_SUCCESS(Status)) { // // Get the volume label and serial number. // VolumeInfo = _CmdConsBlock->TemporaryBuffer; Status = ZwQueryVolumeInformationFile( Handle, &IoStatusBlock, VolumeInfo, _CmdConsBlock->TemporaryBufferSize, FileFsVolumeInformation ); if(NT_SUCCESS(Status)) { // // We can tell the user the volume label and serial number. // VolumeInfo->VolumeLabel[VolumeInfo->VolumeLabelLength/sizeof(WCHAR)] = 0; p = SpDupStringW(VolumeInfo->VolumeLabel); u = VolumeInfo->VolumeSerialNumber; RcMessageOut( *p ? MSG_DIR_BANNER1a : MSG_DIR_BANNER1b, RcToUpper(DosPath[0]), p ); SpMemFree(p); RcMessageOut(MSG_DIR_BANNER2,u >> 16,u & 0xffff); } // // Get free space value for drive. // Status = ZwQueryVolumeInformationFile( Handle, &IoStatusBlock, &SizeInfo, sizeof(FILE_FS_SIZE_INFORMATION), FileFsSizeInformation ); if(!NT_SUCCESS(Status)) { SizeInfo.BytesPerSector = 0; } // // Get the type of the file system so that we can handle // the file times properly (NT stores the date in UTC). // RtlZeroMemory(bfFSInfo, sizeof(bfFSInfo)); pFSInfo = (PFILE_FS_ATTRIBUTE_INFORMATION) bfFSInfo; Status = ZwQueryVolumeInformationFile( Handle, &IoStatusBlock, pFSInfo, sizeof(bfFSInfo), FileFsAttributeInformation); ZwClose(Handle); } // // Tell the user the full DOS path of the directory. // RcMessageOut(MSG_DIR_BANNER3,DosPath); // // Now go enumerate the directory. // RtlZeroMemory(&DirStats,sizeof(DIR_STATS)); if (!NT_SUCCESS(Status)) { KdPrint(("SPCMDCON:Could not get volume information, Error Code:%lx\n", Status)); DirStats.fsType = RcUnknown; // assume FAT file system (by default) } else { if (!wcscmp(pFSInfo->FileSystemName, L"NTFS")) DirStats.fsType = RcNTFS; else if (!wcscmp(pFSInfo->FileSystemName, L"FAT")) DirStats.fsType = RcFAT; else if (!wcscmp(pFSInfo->FileSystemName, L"FAT32")) DirStats.fsType = RcFAT32; else if (!wcscmp(pFSInfo->FileSystemName, L"CDFS")) DirStats.fsType = RcCDFS; else DirStats.fsType = RcUnknown; } KdPrint(("SPCMDCON: RcCmdDir detected file system type (%lx)-%ws\n", DirStats.fsType, pFSInfo ? pFSInfo->FileSystemName : L"None")); Status = RcEnumerateFiles(Dir,Path,pRcDirEnumProc,&DirStats); pRcDisableMoreMode(); if(NT_SUCCESS(Status)) { RcFormat64BitIntForOutput(DirStats.TotalSize,_CmdConsBlock->TemporaryBuffer,FALSE); p = SpDupStringW(_CmdConsBlock->TemporaryBuffer); RcMessageOut(MSG_DIR_BANNER4,DirStats.FileCount,p); SpMemFree(p); if(SizeInfo.BytesPerSector) { RcFormat64BitIntForOutput( SizeInfo.AvailableAllocationUnits.QuadPart * (LONGLONG)SizeInfo.SectorsPerAllocationUnit * (LONGLONG)SizeInfo.BytesPerSector, _CmdConsBlock->TemporaryBuffer, FALSE ); p = SpDupStringW(_CmdConsBlock->TemporaryBuffer); RcMessageOut(MSG_DIR_BANNER5,p); SpMemFree(p); } } else { RcNtError(Status,MSG_FILE_ENUM_ERROR); } c2: SpMemFree(Path); SpMemFree(DosPath); return 1; } BOOLEAN pRcDirEnumProc( IN LPCWSTR Directory, IN PFILE_BOTH_DIR_INFORMATION FileInfo, OUT NTSTATUS *Status, IN PDIR_STATS DirStats ) { WCHAR LineOut[50]; WCHAR *p; NTSTATUS timeStatus; LARGE_INTEGER *pLastWriteTime = 0; LARGE_INTEGER lastWriteTime; LARGE_INTEGER timeBias; TIME_FIELDS timeFields; TIME_ZONE_INFORMATION timeZone; UNREFERENCED_PARAMETER(Directory); DirStats->FileCount++; DirStats->TotalSize += FileInfo->EndOfFile.QuadPart; lastWriteTime = FileInfo->LastWriteTime; // // Convert the time into local time from UTC if the file // system is NTFS // switch(DirStats->fsType) { case RcNTFS: case RcCDFS: // localtime = UTC - bias lastWriteTime.QuadPart -= glBias.QuadPart; break; case RcFAT: case RcFAT32: default: break; } // // Format the date and time, which go first. // RcFormatDateTime(&lastWriteTime,LineOut); RcTextOut(LineOut); // // 2 spaces for separation // RcTextOut(L" "); // // File attributes. // p = LineOut; if(FileInfo->FileAttributes & FILE_ATTRIBUTE_DIRECTORY) { *p++ = L'd'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_ARCHIVE) { *p++ = L'a'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_READONLY) { *p++ = L'r'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_HIDDEN) { *p++ = L'h'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_SYSTEM) { *p++ = L's'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_COMPRESSED) { *p++ = L'c'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_ENCRYPTED) { *p++ = L'e'; } else { *p++ = L'-'; } if(FileInfo->FileAttributes & FILE_ATTRIBUTE_REPARSE_POINT) { *p++ = L'p'; } else { *p++ = L'-'; } *p = 0; RcTextOut(LineOut); // // 2 spaces for separation // RcTextOut(L" "); // // Now, put the size in there. Right justified and space padded // up to 8 chars. Oterwise unjustified or padded. // RcFormat64BitIntForOutput(FileInfo->EndOfFile.QuadPart,LineOut,TRUE); if(FileInfo->EndOfFile.QuadPart > 99999999i64) { RcTextOut(LineOut); } else { RcTextOut(LineOut+11); // outputs 8 chars } RcTextOut(L" "); // // Finally, put the filename on the line. Need to 0-terminate it first. // wcsncpy(_CmdConsBlock->TemporaryBuffer,FileInfo->FileName,FileInfo->FileNameLength); ((WCHAR *)_CmdConsBlock->TemporaryBuffer)[FileInfo->FileNameLength] = 0; *Status = STATUS_SUCCESS; return((BOOLEAN)(RcTextOut(_CmdConsBlock->TemporaryBuffer) && RcTextOut(L"\r\n"))); } NTSTATUS RcEnumerateFiles( IN LPCWSTR OriginalPathSpec, IN LPCWSTR FullyQualifiedPathSpec, IN PENUMFILESCB Callback, IN PVOID CallerData ) { OBJECT_ATTRIBUTES Obja; IO_STATUS_BLOCK IoStatusBlock; HANDLE Handle; UNICODE_STRING UnicodeString; NTSTATUS Status; BOOLEAN b; WCHAR *p; WCHAR *LastComponent = NULL; PFILE_BOTH_DIR_INFORMATION DirectoryInfo; unsigned u; WCHAR *NameChar; BOOLEAN EndsInDot; WCHAR *DirectoryPart; // // Determine whether the original path spec ends with a . // This is used below to get around a problem with specifying // *. as a search specifier. // u = wcslen(OriginalPathSpec); if(u && (OriginalPathSpec[u-1] == L'.')) { EndsInDot = TRUE; } else { EndsInDot = FALSE; } // // Determine whether the given path points at a directory. // If so, we'll concatenate \* on the end and fall through // to the common case. // b = FALSE; INIT_OBJA(&Obja,&UnicodeString,FullyQualifiedPathSpec); Status = ZwOpenFile( &Handle, FILE_READ_ATTRIBUTES, &Obja, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_DIRECTORY_FILE ); if(NT_SUCCESS(Status)) { ZwClose(Handle); b = TRUE; } if(b) { // // Directory, append \*. // p = SpMemAlloc((wcslen(FullyQualifiedPathSpec)+3)*sizeof(WCHAR)); if (p) { wcscpy(p,FullyQualifiedPathSpec); SpConcatenatePaths(p,L"*"); EndsInDot = FALSE; } } else { // // Not directory, pass as-is. Note that this could be an actual // file, or a wild-card spec. // p = SpDupStringW((PVOID)FullyQualifiedPathSpec); } // // Now trim back the path/file specification so we can open the containing // directory for enumeration. // if (p) { LastComponent = wcsrchr(p,L'\\'); } else { return STATUS_NO_MEMORY; } if (LastComponent) { *LastComponent++ = 0; } DirectoryPart = SpMemAlloc((wcslen(p)+2)*sizeof(WCHAR)); wcscpy(DirectoryPart,p); wcscat(DirectoryPart,L"\\"); INIT_OBJA(&Obja,&UnicodeString,p); if (LastComponent) { LastComponent[-1] = L'\\'; } UnicodeString.Length += sizeof(WCHAR); Status = ZwOpenFile( &Handle, FILE_LIST_DIRECTORY | SYNCHRONIZE, &Obja, &IoStatusBlock, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_DIRECTORY_FILE | FILE_SYNCHRONOUS_IO_NONALERT | FILE_OPEN_FOR_BACKUP_INTENT ); if(!NT_SUCCESS(Status)) { SpMemFree(p); SpMemFree(DirectoryPart); return(Status); } RtlInitUnicodeString(&UnicodeString,LastComponent); // // The following code is adapted from the implementation for // the FindFirstFile Win32 API, and provides additional DOS-like // wildcard matching semantics. // // Special case *.* to * since it is so common. Otherwise transmogrify // the input name according to the following rules: // // - Change all ? to DOS_QM // - Change all . followed by ? or * to DOS_DOT // - Change all * followed by a . into DOS_STAR // // These transmogrifications are all done in place. // if(!wcscmp(LastComponent,L"*.*")) { UnicodeString.Length = sizeof(WCHAR); // trim down to just * } else { for(u=0, NameChar=UnicodeString.Buffer; u < (UnicodeString.Length/sizeof(WCHAR)); u++, NameChar++) { if(u && (*NameChar == L'.') && (*(NameChar - 1) == L'*')) { *(NameChar-1) = DOS_STAR; } if((*NameChar == L'?') || (*NameChar == L'*')) { if(*NameChar == L'?') { *NameChar = DOS_QM; } if(u && (*(NameChar-1) == L'.')) { *(NameChar-1) = DOS_DOT; } } } if(EndsInDot && (*(NameChar - 1) == L'*')) { *(NameChar-1) = DOS_STAR; } } // // Finally, iterate the directory. // #define DIRINFO_BUFFER_SIZE ((2*MAX_PATH) + sizeof(FILE_BOTH_DIR_INFORMATION)) DirectoryInfo = SpMemAlloc(DIRINFO_BUFFER_SIZE); b = TRUE; while(TRUE) { Status = ZwQueryDirectoryFile( Handle, NULL, NULL, NULL, &IoStatusBlock, DirectoryInfo, DIRINFO_BUFFER_SIZE, FileBothDirectoryInformation, TRUE, &UnicodeString, b ); b = FALSE; // // Check termination condition // if(Status == STATUS_NO_MORE_FILES) { Status = STATUS_SUCCESS; break; } if(!NT_SUCCESS(Status)) { break; } // // OK, nul-terminate filename and pass info to callback. // DirectoryInfo->FileName[DirectoryInfo->FileNameLength/sizeof(WCHAR)] = 0; if(!Callback(DirectoryPart,DirectoryInfo,&Status,CallerData)) { break; } } ZwClose(Handle); SpMemFree(DirectoryPart); SpMemFree(DirectoryInfo); SpMemFree(p); return(Status); } VOID RcFormat64BitIntForOutput( IN LONGLONG n, OUT LPWSTR Output, IN BOOLEAN RightJustify ) { WCHAR *p; LONGLONG d; BOOLEAN b; WCHAR c; // // Max signed 64-bit integer is 9223372036854775807 (19 digits). // The result will be space padded to the left so it's right-justified // if that flag is set. Otherwise it's just a plain 0-terminated string. // p = Output; d = 1000000000000000000i64; b = FALSE; do { c = (WCHAR)((n / d) % 10) + L'0'; if(c == L'0') { if(!b && (d != 1)) { c = RightJustify ? L' ' : 0; } } else { b = TRUE; } if(c) { *p++ = c; } } while(d /= 10); *p = 0; } // // This time conversion APIs should be moved to setupdd.sys // if more modules need this // NTSTATUS SpSystemTimeToLocalTime ( IN PLARGE_INTEGER SystemTime, OUT PLARGE_INTEGER LocalTime ) { NTSTATUS Status; SYSTEM_TIMEOFDAY_INFORMATION TimeOfDay; Status = ZwQuerySystemInformation( SystemTimeOfDayInformation, &TimeOfDay, sizeof(TimeOfDay), NULL ); if ( !NT_SUCCESS(Status) ) { return Status; } // // LocalTime = SystemTime - TimeZoneBias // LocalTime->QuadPart = SystemTime->QuadPart - TimeOfDay.TimeZoneBias.QuadPart; return STATUS_SUCCESS; } // // This time conversion APIs should be moved to setupdd.sys // if more modules need this // NTSTATUS SpLocalTimeToSystemTime ( IN PLARGE_INTEGER LocalTime, OUT PLARGE_INTEGER SystemTime ) { NTSTATUS Status; SYSTEM_TIMEOFDAY_INFORMATION TimeOfDay; Status = ZwQuerySystemInformation( SystemTimeOfDayInformation, &TimeOfDay, sizeof(TimeOfDay), NULL ); if ( !NT_SUCCESS(Status) ) { return Status; } // // SystemTime = LocalTime + TimeZoneBias // SystemTime->QuadPart = LocalTime->QuadPart + TimeOfDay.TimeZoneBias.QuadPart; return STATUS_SUCCESS; }