/**********************************************************************/ /** Microsoft Windows NT **/ /** Copyright(c) Microsoft Corp., 1993 **/ /**********************************************************************/ /* ls.c Implements a simulated "ls" command for the FTP Server service. FILE HISTORY: KeithMo 09-May-1993 Created. */ #include "ftpdp.h" #pragma hdrstop // // Private constants. // #define IS_HIDDEN(x) (((x).dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) != 0) #define IS_SYSTEM(x) (((x).dwFileAttributes & FILE_ATTRIBUTE_SYSTEM) != 0) #define IS_DIR(x) (((x).dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) #define SORT_INDEX(m,f) ((INT)(m) + ((f) ? (INT)MaxLsSort : 0)) #define NULL_TIME(x) (((x).dwLowDateTime | (x).dwHighDateTime) == 0) #define MODE_R 0x0001 #define MODE_W 0x0002 #define MODE_X 0x0004 #define MODE_ALL 0x0007 #define DEFAULT_SD_SIZE 2048 #define DEFAULT_PS_SIZE 1024 // // Private types. // typedef INT (* PFN_COMPARE)( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); typedef enum _LS_OUTPUT // LS output format. { LsOutputSingleColumn = 0, // -1 (default) LsOutputLongFormat, // -l MaxLsOutput // Must be last! } LS_OUTPUT; typedef enum _LS_SORT // LS sort method. { LsSortByName = 0, // (default) LsSortByWriteTime, // -t LsSortByCreationTime, // -c LsSortByAccessTime, // -u MaxLsSort // Must be last! } LS_SORT; typedef struct _LS_OPTIONS // LS options set by switches. { LS_OUTPUT OutputFormat; // Output format. LS_SORT SortMethod; // Sorting method. BOOL fReverseSort; // Reverse sort order if TRUE. BOOL fDecorate; // Decorate dirs if TRUE (-F). BOOL fShowAll; // Show all files. BOOL fShowDotDot; // Show . and .. BOOL fRecursive; // Recursive listing (-R). } LS_OPTIONS; typedef struct _DIR_NODE // A directory node. { LIST_ENTRY link; // RTL link-list links. WIN32_FIND_DATA find; // From FindFirst/Next APIs. } DIR_NODE; // // Private globals. // extern CHAR * pszNoFileOrDirectory; // This lives in engine.c. DWORD FsFlags[26] = { // One per DOS drive (A - Z). (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L, (DWORD)-1L }; // // Private prototypes. // SOCKERR SimulateLsWorker( USER_DATA * pUserData, SOCKET sock, BOOL fShowHeader, BOOL fSendBlank, CHAR * pszSearchPath, LS_OPTIONS * poptions ); SOCKERR SpecialLsWorker( USER_DATA * pUserData, SOCKET sock, CHAR * pszSearchPath, BOOL fShowDirectories ); SOCKERR SendFileInfoLikeMsdos( SOCKET sock, WIN32_FIND_DATA * pFindData, LS_OPTIONS * poptions ); SOCKERR SendFileInfoLikeUnix( HANDLE hUserToken, SOCKET sock, CHAR * pszPathPart, CHAR * pszFilePart, WIN32_FIND_DATA * pFindData, BOOL fVolumeReadable, BOOL fVolumeWritable, WORD wYear, LS_OPTIONS * poptions ); FILETIME * PickFileTime( WIN32_FIND_DATA * pFindData, LS_OPTIONS * poptions ); APIERR CreateDirectoryList( USER_DATA * pUserData, CHAR * pszSearchPath, LIST_ENTRY * plist, PFN_COMPARE pfnCompare ); VOID FreeDirectoryList( LIST_ENTRY * plist ); INT CompareNames( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareWriteTimes( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareCreationTimes( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareAccessTimes( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareNamesRev( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareWriteTimesRev( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareCreationTimesRev( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); INT CompareAccessTimesRev( WIN32_FIND_DATA * pLeft, WIN32_FIND_DATA * pRight ); DWORD ComputeModeBits( HANDLE hUserToken, CHAR * pszPathPart, CHAR * pszFilePart, WIN32_FIND_DATA * pFindData, DWORD * pcLinks, BOOL fVolumeReadable, BOOL fVolumeWritable ); APIERR ComputeFileInfo( HANDLE hUserToken, CHAR * pszFile, DWORD * pdwAccessGranted, DWORD * pcLinks ); DWORD GetFsFlags( CHAR chDrive ); // // This table is indexed by the LS_SORT enum. This is used to // find the appropriate compare function for the current sort method. // // THE ORDER OF FUNCTIONS IN THIS ARRAY MUST MATCH THE ORDER IN LS_SORT! // PFN_COMPARE CompareRoutines[] = { CompareNames, // Normal sort order. CompareWriteTimes, CompareCreationTimes, CompareAccessTimes, CompareNamesRev, // Reversed sort order. CompareWriteTimesRev, CompareCreationTimesRev, CompareAccessTimesRev }; // // Public functions. // /******************************************************************* NAME: SimulateLs SYNOPSIS: Simulates an LS command. This simulated ls command supports the following switches: -C = Multi column, sorted down. -l = Long format output. -1 = One entry per line (default). -F = Directories have '/' appended. -t = Sort by time of last write. -c = Sort by time of creation. -u = Sort by time of last access. -r = Reverse sort direction. -a = Show all files (including .*). -A = Show all files (except . and ..). -R = Recursive listing. ENTRY: pUserData - The user initiating the request. sock - The socket for the listing. May be INVALID_SOCKET if a new connection is to be established. pszArg - Contains the search path preceeded by switches. RETURNS: SOCKERR - 0 if successful, !0 if not. HISTORY: KeithMo 09-May-1993 Created. ********************************************************************/ SOCKERR SimulateLs( USER_DATA * pUserData, SOCKET sock, CHAR * pszArg ) { SOCKERR serr = 0; LS_OPTIONS options; CHAR * pszToken; CHAR * pszDelimiters = " \t"; BOOL fCreateDataSocket; FTPD_ASSERT( pUserData != NULL ); FTPD_ASSERT( pszArg != NULL ); FTPD_ASSERT( *pszArg == '-' ); // // Setup default ls options. // options.OutputFormat = LsOutputSingleColumn; options.SortMethod = LsSortByName; options.fReverseSort = FALSE; options.fDecorate = FALSE; options.fShowAll = FALSE; options.fShowDotDot = FALSE; options.fRecursive = FALSE; // // Remember if we need to create a data socket later. // fCreateDataSocket = ( sock == INVALID_SOCKET ); // // Process switches. // pszToken = strtok( pszArg, pszDelimiters ); while( ( pszToken != NULL ) && ( *pszToken == '-' ) ) { FTPD_ASSERT( *pszToken == '-' ); pszToken++; while( *pszToken ) { switch( *pszToken ) { case 'C' : case '1' : options.OutputFormat = LsOutputSingleColumn; break; case 'l' : options.OutputFormat = LsOutputLongFormat; break; case 'F' : options.fDecorate = TRUE; break; case 'r' : options.fReverseSort = TRUE; break; case 't' : options.SortMethod = LsSortByWriteTime; break; case 'c' : options.SortMethod = LsSortByCreationTime; break; case 'u' : options.SortMethod = LsSortByAccessTime; break; case 'a' : options.fShowAll = TRUE; options.fShowDotDot = TRUE; break; case 'A' : options.fShowAll = TRUE; options.fShowDotDot = FALSE; break; case 'R' : options.fRecursive = TRUE; break; default : IF_DEBUG( COMMANDS ) { FTPD_PRINT(( "ls: skipping unsupported option '%c'\n", *pszToken )); } break; } pszToken++; } pszToken = strtok( NULL, pszDelimiters ); } // // If the user is requesting an MSDOS-style long-format // listing, then enable display of "." and "..". This // will make the MSDOS-style long-format output look // a little more like MSDOS. // if( TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) && ( options.OutputFormat == LsOutputLongFormat ) ) { options.fShowDotDot = TRUE; } // // If we need to create a new data connection, create it now. // if( fCreateDataSocket ) { serr = EstablishDataConnection( pUserData, "/bin/ls" ); if( serr != 0 ) { return serr; } sock = pUserData->sData; } // // At this point, pszToken is either NULL or points // to the first (of potentially many) LS search paths. // if( pszToken == NULL ) { serr = SimulateLsWorker( pUserData, sock, FALSE, FALSE, pszToken, &options ); } else { BOOL fShowHeader = FALSE; while( pszToken != NULL ) { CHAR * pszNextToken = strtok( NULL, pszDelimiters ); // // Send the directory. // serr = SimulateLsWorker( pUserData, sock, fShowHeader || ( pszNextToken != NULL ), fShowHeader, pszToken, &options ); // // If there are more directories to send, // send a blank line as a separator. // pszToken = pszNextToken; fShowHeader = TRUE; if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } } } // // If we managed to create a new data connection, // tear it down now. // if( fCreateDataSocket && ( pUserData->sData != INVALID_SOCKET ) ) { DestroyDataConnection( pUserData, !TEST_UF( pUserData, OOB_DATA ) && ( serr == 0 ) ); } return serr; } // SimulateLs /******************************************************************* NAME: SimulateLsDefaultLong SYNOPSIS: Like SimulateLs, but defaults to -l. ENTRY: pUserData - The user initiating the request. sock - The socket for the listing. May be INVALID_SOCKET if a new connection is to be established. pszArg - Contains the search path preceeded by optional switches. NULL = current directory. RETURNS: SOCKERR - 0 if successful, !0 if not. HISTORY: KeithMo 09-May-1993 Created. ********************************************************************/ SOCKERR SimulateLsDefaultLong( USER_DATA * pUserData, SOCKET sock, CHAR * pszArg ) { CHAR szCommand[MAX_COMMAND_LENGTH+4]; // extra room for "-l " FTPD_ASSERT( pUserData != NULL ); FTPD_ASSERT( ( pszArg == NULL ) || ( strlen(pszArg) <= MAX_COMMAND_LENGTH ) ); // // Prepend a "-l" option to the front of the arguments. // strcpy( szCommand, "-l " ); if( pszArg != NULL ) { strcat( szCommand, pszArg ); } // // Let SimulateLs do the dirty work. // return SimulateLs( pUserData, sock, szCommand ); } // SimulateLsDefaultLong /******************************************************************* NAME: SpecialLs SYNOPSIS: Special form of directory listing required when an NLST command is received with no switches. Most FTP clients require this special form in order to get the MGET and MDEL commands to work. ENTRY: pUserData - The user initiating the request. pszArg - Contains the search path. NULL = current directory. RETURNS: SOCKERR - 0 if successful, !0 if not. HISTORY: KeithMo 09-May-1993 Created. ********************************************************************/ SOCKERR SpecialLs( USER_DATA * pUserData, CHAR * pszArg ) { SOCKET sock = INVALID_SOCKET; SOCKERR serr = 0; CHAR * pszToken; CHAR * pszDelimiters = " \t"; FTPD_ASSERT( pUserData != NULL ); FTPD_ASSERT( ( pszArg == NULL ) || ( *pszArg != '-' ) ); // // Establish a new data connection. // serr = EstablishDataConnection( pUserData, "file list" ); if( serr != 0 ) { return serr; } sock = pUserData->sData; // // Let the worker do the dirty work. // if( pszArg == NULL ) { serr = SpecialLsWorker( pUserData, sock, // no connection yet pszArg, // search path (no switches) TRUE ); // show directories } else { pszToken = strtok( pszArg, pszDelimiters ); while( pszToken != NULL ) { serr = SpecialLsWorker( pUserData, sock, // may be INVALID, may no pszToken, // search path (no switches) TRUE ); // show directories if( sock == INVALID_SOCKET ) { sock = pUserData->sData; } if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } pszToken = strtok( NULL, pszDelimiters ); } } // // Tear down the connection. // if( pUserData->sData != INVALID_SOCKET ) { DestroyDataConnection( pUserData, !TEST_UF( pUserData, OOB_DATA ) && ( serr == 0 ) ); } return serr; } // SpecialLs // // Private functions. // /******************************************************************* NAME: SimulateLsWorker SYNOPSIS: Worker function for SimulateLs function, blasts a directory listing to the user over a specific socket. ENTRY: pUserData - The user initiating the request. sock - The target socket for the directory listing. fShowHeader - If TRUE, the "dir:" is displayed before the listing. fSendBlank - If TRUE, a blank line is sent before the "dir:" header. pszSearchPath - Search directory, NULL = current dir. poptions - LS options set by command line switches. RETURNS: SOCKERR - 0 if successful, !0 if not. HISTORY: KeithMo 09-May-1993 Created. ********************************************************************/ SOCKERR SimulateLsWorker( USER_DATA * pUserData, SOCKET sock, BOOL fShowHeader, BOOL fSendBlank, CHAR * pszSearchPath, LS_OPTIONS * poptions ) { CHAR * pszDefaultSearchPath = "*.*"; CHAR * pszFilePart; CHAR * pszOriginalFilePart; DWORD dwAttrib; SYSTEMTIME timeNow; BOOL fDecorate; BOOL fLikeMsdos; BOOL fVolumeReadable; BOOL fVolumeWritable; SOCKERR serr = 0; BOOL fHasWildcards = FALSE; APIERR err = NO_ERROR; LIST_ENTRY dirlist; LIST_ENTRY * pscan; PFN_COMPARE pfnCompare; HANDLE hUserToken; CHAR szOriginal[MAX_PATH]; CHAR szSearch[MAX_PATH]; FTPD_ASSERT( pUserData != NULL ); FTPD_ASSERT( poptions != NULL ); hUserToken = pUserData->hToken; fDecorate = poptions->fDecorate; fLikeMsdos = TEST_UF( pUserData, MSDOS_DIR_OUTPUT ); // // Check for wildcards in search path. // if( ( pszSearchPath != NULL ) && ( strpbrk( pszSearchPath, "?*" ) != NULL ) ) { // // Search path contains wildcards. // fHasWildcards = TRUE; } // // Munge the arguments around a bit. NULL = *.* in current // directory. If the user specified a directory (like d:\foo) // then append *.*. // if( ( pszSearchPath == NULL ) || ( *pszSearchPath == '\0' ) ) { pszSearchPath = pszDefaultSearchPath; strcpy( szOriginal, fLikeMsdos ? ".\\" : "./" ); } else { strcpy( szOriginal, pszSearchPath ); if( fHasWildcards ) { CHAR * pszTmp; pszTmp = strrchr( szOriginal, '\\' ); pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, '/' ); pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, ':' ); if( pszTmp == NULL ) { pszTmp = szOriginal; } else { pszTmp++; } *pszTmp = '\0'; } else if( !IS_PATH_SEP( szOriginal[strlen(szOriginal) - 1] ) ) { strcat( szOriginal, fLikeMsdos ? "\\" : "/" ); } } pszOriginalFilePart = szOriginal + strlen(szOriginal); // // Initialize the directory list now, so it will // always be in a reasonable state. // InitializeListHead( &dirlist ); // // Canonicalize the search path. // err = VirtualCanonicalize( pUserData, szSearch, pszSearchPath, ReadAccess ); if( err == NO_ERROR ) { dwAttrib = GetFileAttributes( szSearch ); if( dwAttrib != (DWORD)-1L ) { if( dwAttrib & FILE_ATTRIBUTE_DIRECTORY ) { // // User gave us a directory. Append [\]*.*. // if( !IS_PATH_SEP( szSearch[strlen(szSearch)-1] ) ) { strcat( szSearch, "\\" ); } strcat( szSearch, pszDefaultSearchPath ); } } // // GetFullPathName (called by VirtualCanonicalize) // will strip trailing dots from the path. Replace them here. // if( fHasWildcards && ( pszSearchPath[strlen(pszSearchPath)-1] == '.' ) ) { strcat( szSearch, "." ); } // // Build the directory list. // pfnCompare = CompareRoutines[SORT_INDEX(poptions->SortMethod, poptions->fReverseSort)]; err = CreateDirectoryList( pUserData, szSearch, &dirlist, pfnCompare ); } // // If there were any errors, tell them the bad news now. // if( err != NO_ERROR ) { BOOL fDelete = TRUE; CHAR * pszText; pszText = AllocErrorText( err ); if( pszText == NULL ) { pszText = pszNoFileOrDirectory; fDelete = FALSE; } serr = SockPrintf2( sock, "%s: %s", pszSearchPath, pszText ); if( fDelete ) { FreeErrorText( pszText ); } return serr; } // // Strip off the wildcards from the search path. // We need the "bare" search path so we can create // a fully qualified path to search file found. // pszFilePart = strrchr( szSearch, '\\' ); if( pszFilePart == NULL ) { pszFilePart = strrchr( szSearch, ':' ); } if( pszFilePart == NULL ) { pszFilePart = szSearch + strlen( szSearch ); } else { pszFilePart++; } if( !fLikeMsdos ) { // // Check the volume access (for Unix-like attributes). // fVolumeReadable = PathAccessCheck( pUserData, szSearch, ReadAccess ); fVolumeWritable = PathAccessCheck( pUserData, szSearch, WriteAccess ); // // Retrieve the current time. The Unix-like output // function needs the current year. // GetLocalTime( &timeNow ); } // // Loop until we're out of files to find. // for( pscan = dirlist.Flink ; pscan != &dirlist ; pscan = pscan->Flink ) { DIR_NODE * pnode = CONTAINING_RECORD( pscan, DIR_NODE, link ); // // Filter out system & hidden files. // if( !poptions->fShowAll && ( IS_HIDDEN(pnode->find) || IS_SYSTEM(pnode->find) ) ) { continue; } // // Filter out .* // if( !poptions->fShowAll && !poptions->fShowDotDot && ( pnode->find.cFileName[0] == '.' ) ) { continue; } // // Filter out . and .. // if( !poptions->fShowDotDot && ( ( strcmp( pnode->find.cFileName, "." ) == 0 ) || ( strcmp( pnode->find.cFileName, ".." ) == 0 ) ) ) { continue; } // // Dump it. // if( fShowHeader ) { if( fSendBlank ) { serr = SockPrintf2( sock, "" ); fSendBlank = FALSE; if( serr != 0 ) { break; } } serr = SockPrintf2( sock, "%s:", pszSearchPath ); fShowHeader = FALSE; if( serr != 0 ) { break; } } if( poptions->OutputFormat == LsOutputLongFormat ) { // // Long format output. Just send the file/dir info. // if( fLikeMsdos ) { serr = SendFileInfoLikeMsdos( sock, &pnode->find, poptions ); } else { serr = SendFileInfoLikeUnix( hUserToken, sock, szSearch, pszFilePart, &pnode->find, fVolumeReadable, fVolumeWritable, timeNow.wYear, poptions ); } } else { // // Short format output. // serr = SockPrintf2( sock, "%s%s", pnode->find.cFileName, ( fDecorate && IS_DIR(pnode->find) ) ? "/" : "" ); } // // Check for socket errors on send or pending OOB data. // if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } } if( poptions->fRecursive ) { // // The user want's a recursive directory search... // for( pscan = dirlist.Flink ; pscan != &dirlist ; pscan = pscan->Flink ) { DIR_NODE * pnode = CONTAINING_RECORD( pscan, DIR_NODE, link ); // // Filter out non-directories. // if( !IS_DIR(pnode->find) ) { continue; } // // Filter out system & hidden files. // if( !poptions->fShowAll && ( IS_HIDDEN(pnode->find) || IS_SYSTEM(pnode->find) ) ) { continue; } // // Filter out .* // if( !poptions->fShowAll && !poptions->fShowDotDot && ( pnode->find.cFileName[0] == '.' ) ) { continue; } // // Filter out . and .. // if( ( strcmp( pnode->find.cFileName, "." ) == 0 ) || ( strcmp( pnode->find.cFileName, ".." ) == 0 ) ) { continue; } // // Dump it. // strcpy( pszOriginalFilePart, pnode->find.cFileName ); serr = SimulateLsWorker( pUserData, sock, TRUE, TRUE, szOriginal, poptions ); // // Check for socket errors on send or pending OOB data. // if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } } } FreeDirectoryList( &dirlist ); // // Success! // return serr; } // SimulateLsWorker /******************************************************************* NAME: SpecialLsWorker SYNOPSIS: Worker function for SpecialLs funciton, blasts a directory listing to the user over a specific socket. ENTRY: pUserData - The user that initiated the request. sock - The target socket for the directory listing. pszSearchPath - Search directory, NULL = current dir. fShowDirectories - Only show directories if TRUE. RETURNS: SOCKERR - 0 if successful, !0 if not. HISTORY: KeithMo 17-Mar-1993 Created. ********************************************************************/ SOCKERR SpecialLsWorker( USER_DATA * pUserData, SOCKET sock, CHAR * pszSearchPath, BOOL fShowDirectories ) { CHAR * pszRecurse; CHAR * pszDefaultSearchPath = "*.*"; CHAR chSeparator; DWORD dwAttrib; SOCKERR serr = 0; BOOL fHasWildcards = FALSE; APIERR err = NO_ERROR; LIST_ENTRY dirlist; LIST_ENTRY * pscan; CHAR szSearch[MAX_PATH]; CHAR szRecurse[MAX_PATH]; chSeparator = TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ? '\\' : '/'; // // Check for wildcards in search path. // if( ( pszSearchPath != NULL ) && ( *pszSearchPath != '\0' ) ) { // // Setup for recursive directory search. We'll set things up // so we can strcpy a new directory to pszRecurse, then // recursively call ourselves with szRecurse as the search // path. // // We also use szRecurse as a "prefix" to display before each // file/directory. The FTP Client software needs this for the // MDEL & MGET commands. // strcpy( szRecurse, pszSearchPath ); if( strpbrk( szRecurse, "?*" ) != NULL ) { // // Search path contains wildcards. // fHasWildcards = TRUE; // // Strip the wildcard pattern from the search path. // // Note that since we're working with the pre- // canonicalized search path, we need to check for // both types of path separators. // if( ( pszRecurse = strrchr( szRecurse, '\\' ) ) == NULL ) { pszRecurse = strrchr( szRecurse, '/' ); } if( pszRecurse == NULL ) { // // No directory components in search path. // pszRecurse = szRecurse; } else { // // Found the right-most directory component. // Skip the path separator. // pszRecurse++; } } else { // // No wildcards, so the argument must be a path. // Ensure it is terminated with a path separator. // pszRecurse = szRecurse + strlen(szRecurse) - 1; if( !IS_PATH_SEP( *pszRecurse ) ) { *++pszRecurse = chSeparator; } pszRecurse++; } } else { // // No arguments. // pszRecurse = szRecurse; } *pszRecurse = '\0'; // // Munge the arguments around a bit. NULL = *.* in current // directory. If the user specified a directory (like d:\foo) // then append *.*. // if( ( pszSearchPath == NULL ) || ( *pszSearchPath == '\0' ) ) { pszSearchPath = pszDefaultSearchPath; } // // Initialize the directory list now, so it will // always be in a reasonable state. // InitializeListHead( &dirlist ); // // Canonicalize the search path. // err = VirtualCanonicalize( pUserData, szSearch, pszSearchPath, ReadAccess ); if( err == NO_ERROR ) { dwAttrib = GetFileAttributes( szSearch ); if( dwAttrib != (DWORD)-1L ) { if( dwAttrib & FILE_ATTRIBUTE_DIRECTORY ) { // // User gave us a directory. Append [\]*.*. // if( !IS_PATH_SEP( szSearch[strlen(szSearch)-1] ) ) { CHAR szTmp[] = "\\"; szTmp[0] = chSeparator; strcat( szSearch, szTmp ); } strcat( szSearch, pszDefaultSearchPath ); } else { // // User gave us a real file. // pszRecurse = szRecurse; *pszRecurse = '\0'; } } // // GetFullPathName (called by VirtualCanonicalize) // will strip trailing dots from the path. Replace them here. // if( fHasWildcards && ( pszSearchPath[strlen(pszSearchPath)-1] == '.' ) ) { strcat( szSearch, "." ); } // // Build the directory list. // err = CreateDirectoryList( pUserData, szSearch, &dirlist, NULL ); // NULL = unsorted list. } // // If there were any errors, tell them the bad news now. // if( err != NO_ERROR ) { BOOL fDelete = TRUE; CHAR * pszText; pszText = AllocErrorText( err ); if( pszText == NULL ) { pszText = pszNoFileOrDirectory; fDelete = FALSE; } serr = SockPrintf2( sock, "%s: %s", pszSearchPath, pszText ); if( fDelete ) { FreeErrorText( pszText ); } return serr; } // // Loop until we're out of files to find. // for( pscan = dirlist.Flink ; pscan != &dirlist ; pscan = pscan->Flink ) { DIR_NODE * pnode = CONTAINING_RECORD( pscan, DIR_NODE, link ); // // Filter out system & hidden files. // if( IS_HIDDEN(pnode->find) || IS_SYSTEM(pnode->find) ) { continue; } // // Filter out directories if necessary. // if( !fShowDirectories && IS_DIR(pnode->find) ) { continue; } // // If no wildcards were given, then just dump out the // file/directory. If wildcards were given, AND this // is a directory, then recurse (one level only) into // the directory. The mere fact that we don't append // any wildcards to the recursed search path will // prevent a full depth-first recursion of the file system. // if( fHasWildcards && IS_DIR(pnode->find) ) { if( strcmp( pnode->find.cFileName, "." ) && strcmp( pnode->find.cFileName, ".." ) ) { strcpy( pszRecurse, pnode->find.cFileName ); serr = SpecialLsWorker( pUserData, sock, szRecurse, FALSE ); } } else { *pszRecurse = '\0'; serr = SockPrintf2( sock, "%s%s", szRecurse, pnode->find.cFileName ); } // // Test for aborted directory listing or socket error. // if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) ) { break; } } FreeDirectoryList( &dirlist ); // // Success! // return serr; } // SpecialLsWorker /******************************************************************* NAME: SendFileInfoLikeMsdos SYNOPSIS: Sends an MSDOS-like directory entry to the client. ENTRY: sock - The target socket. pFindData - The data describing this dir entry. poptions - Current LS options. RETURNS: SOCKERR - Result of send. HISTORY: KeithMo 11-Mar-1993 Created. ********************************************************************/ SOCKERR SendFileInfoLikeMsdos( SOCKET sock, WIN32_FIND_DATA * pFindData, LS_OPTIONS * poptions ) { SOCKERR serr; FILETIME filetime; SYSTEMTIME systime; WORD wHour; CHAR * pszAmPm; BOOL fDir; CHAR szSizeOrDir[32]; // // Is this a directory? // fDir = IS_DIR(*pFindData); // // Munge the time around like CMD.EXE's DIR command. // FileTimeToLocalFileTime( PickFileTime( pFindData, poptions ), &filetime ); FileTimeToSystemTime( &filetime, &systime ); wHour = systime.wHour; pszAmPm = ( wHour < 12 ) ? "AM" : "PM"; if( wHour == 0 ) { wHour = 12; } else if( wHour > 12 ) { wHour -= 12; } if( fDir ) { strcpy( szSizeOrDir, "