You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1693 lines
50 KiB
1693 lines
50 KiB
/**********************************************************************/
|
|
/** Microsoft Windows NT **/
|
|
/** Copyright(c) Microsoft Corp., 1995 **/
|
|
/**********************************************************************/
|
|
/*
|
|
newls.cxx
|
|
|
|
Implements a simulated "ls" command for the FTP Server service,
|
|
with buffering and possibly caching of the results generated.
|
|
|
|
Functions exported by this module:
|
|
|
|
SimulateLs()
|
|
SpecialLs()
|
|
|
|
FILE HISTORY:
|
|
MuraliK 19-April-1995 Created.
|
|
*/
|
|
|
|
# include "ftpdp.hxx"
|
|
# include "tsunami.hxx"
|
|
# include "lsaux.hxx"
|
|
# include <mbstring.h>
|
|
|
|
|
|
/**********************************************************************
|
|
* Private Globals
|
|
**********************************************************************/
|
|
|
|
// Following message is required to send error msg when the file
|
|
// or directory is absent.
|
|
extern CHAR * p_NoFileOrDirectory; // This lives in engine.c.
|
|
|
|
static const char * PSZ_DEFAULT_SEARCH_PATH = "";
|
|
|
|
static const char * PSZ_WILD_CHARACTERS = "*?<>"; // include DOS wilds!
|
|
|
|
|
|
/**********************************************************************
|
|
* Prototypes of Functions
|
|
**********************************************************************/
|
|
|
|
DWORD
|
|
FormatFileInfoLikeMsdos(
|
|
IN OUT LS_BUFFER * plsb,
|
|
IN const WIN32_FIND_DATA * pfdInfo,
|
|
IN const LS_FORMAT_INFO * pFormatInfo
|
|
);
|
|
|
|
DWORD
|
|
FormatFileInfoLikeUnix(
|
|
IN OUT LS_BUFFER * plsb,
|
|
IN const WIN32_FIND_DATA * pfdInfo,
|
|
IN const LS_FORMAT_INFO * pFormatInfo
|
|
);
|
|
|
|
|
|
APIERR
|
|
SimulateLsWorker(
|
|
IN USER_DATA * pUserData,
|
|
IN BOOL fUseDataSocket,
|
|
IN CHAR * pszSearchPath,
|
|
IN const LS_OPTIONS * pOptions,
|
|
IN BOOL fSendHeader = FALSE,
|
|
IN BOOL fSendBlank = FALSE
|
|
);
|
|
|
|
|
|
APIERR
|
|
SpecialLsWorker(
|
|
IN USER_DATA *pUserData,
|
|
IN BOOL fUseDataSocket,
|
|
CHAR * pszSearchPath,
|
|
BOOL fShowDirectories,
|
|
IN OUT LS_BUFFER * plsb
|
|
);
|
|
|
|
|
|
//
|
|
// The following is a table consisting of the sort methods used
|
|
// for generating various dir listing. The table is indexed by LS_SORT.
|
|
// This is used for finding the appropriate compare function for
|
|
// any given sort method.
|
|
// THE ORDER OF FUNCTIONS IN THIS ARRAY MUST MATCH THE ORDER IN LS_SORT!
|
|
//
|
|
|
|
static PFN_CMP_WIN32_FIND_DATA CompareRoutines[] = {
|
|
|
|
CompareNamesInFileInfo, // Normal sort order.
|
|
CompareWriteTimesInFileInfo,
|
|
CompareCreationTimesInFileInfo,
|
|
CompareAccessTimesInFileInfo,
|
|
|
|
CompareNamesRevInFileInfo, // Reversed sort order.
|
|
CompareWriteTimesRevInFileInfo,
|
|
CompareCreationTimesRevInFileInfo,
|
|
CompareAccessTimesRevInFileInfo
|
|
};
|
|
|
|
// method,direction are used for indexing.
|
|
#define SORT_INDEX(method, dirn) ((INT)(method) + \
|
|
((dirn) ? (INT)MaxLsSort : 0))
|
|
|
|
|
|
// constants and literals
|
|
#define ILLEGAL_FILE_CHARS (UCHAR*)"*?<>/\\\"|:"
|
|
|
|
|
|
/**********************************************************************
|
|
* Functions
|
|
**********************************************************************/
|
|
|
|
static
|
|
BOOL
|
|
SeparateOutFilterSpec( IN OUT CHAR * szPath, IN BOOL fHasWildCards,
|
|
OUT LPCSTR * ppszFilterSpec)
|
|
/*++
|
|
The path has the form c:\ftppath\foo\bar\*.*
|
|
Check to see if the path is already a directory.
|
|
If so set filter as nothing.
|
|
This function identifies the last "\" and terminates the
|
|
path at that point. The remaining forms a filter (here: *.*)
|
|
--*/
|
|
{
|
|
char * pszFilter;
|
|
BOOL fDir = FALSE;
|
|
|
|
IF_DEBUG( DIR_LIST) {
|
|
DBGPRINTF((DBG_CONTEXT, "SeparateOutFilter( %s, %d)\n",
|
|
szPath, fHasWildCards));
|
|
}
|
|
|
|
DBG_ASSERT( ppszFilterSpec != NULL);
|
|
*ppszFilterSpec = NULL; // initialize.
|
|
|
|
if ( !fHasWildCards) {
|
|
|
|
// Identify if the path is a directory
|
|
|
|
DWORD dwAttribs = GetFileAttributes( szPath);
|
|
|
|
if ( dwAttribs == INVALID_FILE_ATTRIBUTES) {
|
|
|
|
return ( FALSE);
|
|
} else {
|
|
|
|
fDir = ( IS_DIR(dwAttribs));
|
|
}
|
|
}
|
|
|
|
if ( !fDir ) {
|
|
pszFilter = (PCHAR)_mbsrchr( (PUCHAR)szPath, '\\');
|
|
|
|
//This has to exist, since valid path was supplied.
|
|
DBG_ASSERT( pszFilter != NULL);
|
|
*pszFilter = '\0'; // terminate the old path.
|
|
pszFilter++; // skip past the terminating null character.
|
|
|
|
*ppszFilterSpec = (*pszFilter == '\0') ? NULL : pszFilter;
|
|
|
|
IF_DEBUG(DIR_LIST) {
|
|
DBGPRINTF((DBG_CONTEXT, "Path = %s; Filter = %s\n",
|
|
szPath, *ppszFilterSpec));
|
|
}
|
|
}
|
|
|
|
return (TRUE);
|
|
|
|
} // SeparateOutFilterSpec()
|
|
|
|
|
|
|
|
|
|
APIERR
|
|
SimulateLs(
|
|
IN USER_DATA * pUserData,
|
|
IN OUT CHAR * pszArg,
|
|
IN BOOL fUseDataSocket,
|
|
IN BOOL fDefaultLong
|
|
)
|
|
/*++
|
|
This function 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.
|
|
|
|
Arguments:
|
|
pUserData -- the user initiating the request.
|
|
pszArg -- contains the search path, preceded by switches.
|
|
Note: The argument is destroyed during processing!!!!
|
|
fUseDataSocket -- if TRUE use Data socket, else use control socket.
|
|
fDefaultLong -- should the default be long ? ( if TRUE)
|
|
|
|
Returns:
|
|
APIERR, 0 on success.
|
|
--*/
|
|
{
|
|
APIERR serr = 0;
|
|
LS_OPTIONS options;
|
|
CHAR * pszToken = pszArg;
|
|
CHAR * pszDelimiters = " \t";
|
|
|
|
DBG_ASSERT( pUserData != NULL );
|
|
|
|
//
|
|
// Setup default ls options.
|
|
//
|
|
|
|
options.OutputFormat = (( fDefaultLong) ?
|
|
LsOutputLongFormat : LsOutputSingleColumn);
|
|
options.SortMethod = LsSortByName;
|
|
options.fReverseSort = FALSE;
|
|
options.fDecorate = FALSE;
|
|
options.fShowAll = FALSE;
|
|
options.fShowDotDot = FALSE;
|
|
options.fRecursive = FALSE;
|
|
options.lsStyle = ( TEST_UF( pUserData, MSDOS_DIR_OUTPUT)
|
|
? LsStyleMsDos
|
|
: LsStyleUnix
|
|
);
|
|
options.fFourDigitYear= TEST_UF( pUserData, 4_DIGIT_YEAR);
|
|
|
|
//
|
|
// Process switches in the input, if any
|
|
//
|
|
|
|
// simplify things by skipping whitespace...
|
|
|
|
if (pszArg && isspace(*pszArg)) {
|
|
while (isspace(*pszArg))
|
|
pszArg++;
|
|
}
|
|
|
|
// now we should be pointing to the options, or the filename
|
|
|
|
if (pszArg && (*pszArg == '-')) {
|
|
|
|
for( pszToken = strtok( pszArg, pszDelimiters ); // getfirst Tok.
|
|
( ( pszToken != NULL ) && ( *pszToken == '-' ) );
|
|
pszToken = strtok( NULL, pszDelimiters) // get next token
|
|
) {
|
|
|
|
DBG_ASSERT( *pszToken == '-' );
|
|
|
|
// process all the switches in single token
|
|
|
|
// for( pszToken++; *pszToken; pszToken++) is written as follows
|
|
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 ) {
|
|
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"ls: skipping unsupported option '%c'\n",
|
|
*pszToken ));
|
|
}
|
|
break;
|
|
} // switch()
|
|
} // process all switches in a token
|
|
|
|
} // for
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
//
|
|
|
|
options.fShowDotDot = ( options.fShowDotDot ||
|
|
( options.lsStyle == LsStyleMsDos &&
|
|
( options.OutputFormat == LsOutputLongFormat ))
|
|
);
|
|
|
|
|
|
//
|
|
// since LIST is sent out synchronously, bump up thread count
|
|
// before beginning to send out the response for LIST
|
|
//
|
|
// A better method:
|
|
// Make LIST generate response in a buffer and use async IO
|
|
// operations for sending response.
|
|
// TBD (To Be Done)
|
|
//
|
|
AtqSetInfo( AtqIncMaxPoolThreads, 0);
|
|
|
|
|
|
//
|
|
// At this point, pszToken is either NULL or points
|
|
// to the first (of potentially many) LS search paths.
|
|
//
|
|
|
|
serr = SimulateLsWorker(pUserData, fUseDataSocket, pszToken, &options);
|
|
|
|
//
|
|
// bring down the thread count when response is completed
|
|
// TBD: Use Async send reponse()
|
|
//
|
|
|
|
AtqSetInfo( AtqDecMaxPoolThreads, 0);
|
|
|
|
|
|
return ( serr);
|
|
} // SimulateLs()
|
|
|
|
|
|
|
|
|
|
APIERR
|
|
SpecialLs(
|
|
USER_DATA * pUserData,
|
|
CHAR * pszArg,
|
|
IN BOOL fUseDataSocket
|
|
)
|
|
/*++
|
|
This produces a special form of the directory listing that is required
|
|
when an NLST command is received with no switches. Most of the FTP clients
|
|
require this special form in order to get the MGET and MDEL commands
|
|
to work. This produces atmost one level of directory information.
|
|
|
|
Arguments:
|
|
pUserData - the user initiating the request.
|
|
pszArg - pointer to null-terminated string containing the argument.
|
|
NULL=current directory for UserData.
|
|
fUseDataSocket - if TRUE use Data Socket, else use the ContorlSocket.
|
|
|
|
Returns:
|
|
APIERR - 0 if successful, !0 if not.
|
|
--*/
|
|
{
|
|
APIERR dwError = 0;
|
|
LS_BUFFER lsb;
|
|
|
|
DBG_ASSERT( pUserData != NULL );
|
|
DBG_ASSERT( ( pszArg == NULL ) || ( *pszArg != '-' ) ); // No options
|
|
|
|
|
|
|
|
if ((dwError = lsb.AllocateBuffer( DEFAULT_LS_BUFFER_ALLOC_SIZE))
|
|
!= NO_ERROR) {
|
|
|
|
IF_DEBUG(ERROR) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "Buffer allocation(%d bytes) failed.\n",
|
|
DEFAULT_LS_BUFFER_ALLOC_SIZE));
|
|
}
|
|
|
|
return (dwError);
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// since LIST is sent out synchronously, bump up thread count
|
|
// before beginning to send out the response for LIST
|
|
//
|
|
// A better method:
|
|
// Make LIST generate response in a buffer and use async IO
|
|
// operations for sending response.
|
|
// TBD (To Be Done)
|
|
//
|
|
AtqSetInfo( AtqIncMaxPoolThreads, 0);
|
|
|
|
|
|
//
|
|
// Let the worker do the dirty work.
|
|
//
|
|
dwError = SpecialLsWorker(pUserData,
|
|
fUseDataSocket,
|
|
pszArg, // search path (no switches)
|
|
TRUE, // show directories
|
|
&lsb);
|
|
|
|
if ( dwError == NO_ERROR) {
|
|
|
|
// send all the remaining bytes in the buffer and then free memory.
|
|
|
|
if ( lsb.QueryCB() != 0) {
|
|
|
|
SOCKET sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() :
|
|
pUserData->QueryControlSocket());
|
|
|
|
dwError = SockSend(pUserData, sock,
|
|
lsb.QueryBuffer(),
|
|
lsb.QueryCB()/sizeof(CHAR));
|
|
}
|
|
|
|
lsb.FreeBuffer();
|
|
}
|
|
|
|
|
|
//
|
|
// bring down the thread count when response is completed
|
|
// TBD: Use Async send reponse()
|
|
//
|
|
|
|
AtqSetInfo( AtqDecMaxPoolThreads, 0);
|
|
|
|
return ( dwError);
|
|
|
|
} // SpecialLs()
|
|
|
|
|
|
|
|
|
|
|
|
//
|
|
// Private functions.
|
|
//
|
|
APIERR
|
|
SimulateLsWorker(
|
|
USER_DATA * pUserData,
|
|
IN BOOL fUseDataSocket,
|
|
IN CHAR * pszSearchPath,
|
|
IN const LS_OPTIONS * pOptions,
|
|
IN BOOL fSendHeader,
|
|
IN BOOL fSendBlank
|
|
)
|
|
/*++
|
|
Worker function for SimulateLs function, forms directory listing
|
|
for requested directory, formats the directory listing and
|
|
sends it to the client.
|
|
|
|
Arguments:
|
|
pUserData - The user initiating the request.
|
|
|
|
pszSearchPath - Search directory, NULL = current dir.
|
|
|
|
pOptions - LS options set by command line switches.
|
|
|
|
fSendHeader - if TRUE send header with directory name in it.
|
|
fSendBlank - also add a blank if there is one that has to be sent.
|
|
|
|
Returns:
|
|
APIERR - 0 if successful, !0 if not.
|
|
|
|
HISTORY:
|
|
MuraliK 24-Apr-1995 ReCreated.
|
|
--*/
|
|
{
|
|
SOCKET sock;
|
|
BOOL fLikeMsdos;
|
|
CHAR szSearch[MAX_PATH+1];
|
|
CHAR rgchLowFileName[MAX_PATH+1]; // used for lower casing filename
|
|
BOOL fMapToLowerCase = FALSE;
|
|
DWORD dwAccessMask = 0;
|
|
BOOL fImpersonated = FALSE;
|
|
|
|
LS_BUFFER lsb;
|
|
LS_FORMAT_INFO lsfi; // required only for long formatting.
|
|
|
|
BOOL fHasWildCards = FALSE;
|
|
DWORD dwError = NO_ERROR;
|
|
APIERR serr = 0;
|
|
TS_DIRECTORY_INFO tsDirInfo( pUserData->QueryInstance()->GetTsvcCache());
|
|
|
|
|
|
IF_DEBUG( DIR_LIST) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT, " SimulateLsWorker( %08x, %d, %s)\n",
|
|
pUserData, fUseDataSocket, pszSearchPath));
|
|
}
|
|
|
|
DBG_ASSERT( pUserData != NULL && pOptions != NULL);
|
|
|
|
//
|
|
// Check for emptiness of path or wildcards in search path.
|
|
// We are only concerned about wild cards in user input. The reason
|
|
// is all trailing '.' will be removed when we canonicalize
|
|
// the path ( which user may not appreciate).
|
|
//
|
|
|
|
if ( IS_EMPTY_PATH(pszSearchPath)) {
|
|
|
|
// we know pszSearchPath will not change the buffer!
|
|
pszSearchPath = (char *) PSZ_DEFAULT_SEARCH_PATH;
|
|
|
|
} else if (strpbrk( pszSearchPath, PSZ_WILD_CHARACTERS ) != NULL) {
|
|
|
|
//
|
|
// Search path contains wildcards.
|
|
//
|
|
|
|
fHasWildCards = TRUE;
|
|
}
|
|
|
|
//
|
|
// Canonicalize the search path.
|
|
//
|
|
|
|
DWORD cbSize = sizeof(szSearch) - 1; // save space for '.' we add later
|
|
dwError = pUserData->VirtualCanonicalize(szSearch, &cbSize,
|
|
pszSearchPath,
|
|
AccessTypeRead,
|
|
&dwAccessMask);
|
|
|
|
DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
if( dwError == NO_ERROR ) {
|
|
|
|
FTP_LS_FILTER_INFO fls; // required for generating directory listing
|
|
PFN_CMP_WIN32_FIND_DATA pfnCompare;
|
|
|
|
//
|
|
// VirtualCanonicalize() when sanitizing the path removes
|
|
// trailing dots from the path. Replace them here
|
|
//
|
|
DBG_ASSERT( !fHasWildCards || strlen(pszSearchPath) >= 1);
|
|
|
|
if( fHasWildCards && (pszSearchPath[strlen(pszSearchPath)-1] == '.')) {
|
|
|
|
DBG_ASSERT( strlen(szSearch) < MAX_PATH);
|
|
strcat( szSearch, "." );
|
|
}
|
|
|
|
//
|
|
// Build the directory list.
|
|
//
|
|
|
|
pfnCompare = CompareRoutines[SORT_INDEX(pOptions->SortMethod,
|
|
pOptions->fReverseSort)
|
|
];
|
|
|
|
// Separate the filter out ( that is the last component)
|
|
|
|
if (pUserData->ImpersonateUser()) {
|
|
|
|
if (SeparateOutFilterSpec( szSearch,
|
|
fHasWildCards,
|
|
&fls.pszExpression) ) {
|
|
|
|
fls.fFilterHidden = !pOptions->fShowAll;
|
|
fls.fFilterSystem = !pOptions->fShowAll;
|
|
fls.fFilterDotDot = !pOptions->fShowDotDot;
|
|
|
|
fls.fRegExpression = ( fls.pszExpression != NULL && fHasWildCards);
|
|
fls.fIgnoreCase = pUserData->QueryInstance()->QueryLowercaseFiles();
|
|
|
|
dwError = GetDirectoryInfo(pUserData,
|
|
&tsDirInfo,
|
|
szSearch,
|
|
&fls,
|
|
pfnCompare);
|
|
} else {
|
|
|
|
dwError = GetLastError();
|
|
}
|
|
|
|
pUserData->RevertToSelf();
|
|
|
|
} else {
|
|
|
|
dwError = GetLastError();
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there were any errors, tell them the bad news now.
|
|
//
|
|
|
|
if( dwError != NO_ERROR ) {
|
|
|
|
return (dwError);
|
|
}
|
|
|
|
sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() :
|
|
pUserData->QueryControlSocket());
|
|
|
|
DBG_ASSERT( tsDirInfo.IsValid());
|
|
|
|
int cDirEntries = tsDirInfo.QueryFilesCount();
|
|
|
|
if ( cDirEntries > 0) {
|
|
|
|
//
|
|
// put out the header block before starting dir listing
|
|
//
|
|
|
|
if( fSendHeader ) {
|
|
|
|
serr = SockPrintf2( pUserData, sock,
|
|
"%s%s:",
|
|
(fSendBlank)? "\r\n" : "", // send \r\n
|
|
pszSearchPath);
|
|
|
|
if ( serr != 0) {
|
|
|
|
return (serr);
|
|
}
|
|
}
|
|
}
|
|
|
|
fLikeMsdos = (pOptions->lsStyle == LsStyleMsDos);
|
|
|
|
lsfi.fFourDigitYear = pOptions->fFourDigitYear;
|
|
|
|
if( !fLikeMsdos ) {
|
|
|
|
//
|
|
// Initialize the information in lsfi if we are doing
|
|
// long format output.
|
|
//
|
|
|
|
if ( pOptions->OutputFormat == LsOutputLongFormat) {
|
|
|
|
SYSTEMTIME timeNow;
|
|
BOOL fUserRead, fUserWrite;
|
|
|
|
//
|
|
// Obtain the current time.
|
|
// The Unix-like output requires current year
|
|
//
|
|
|
|
GetLocalTime( &timeNow );
|
|
|
|
lsfi.wCurrentYear = timeNow.wYear;
|
|
lsfi.hUserToken = TsTokenToImpHandle(pUserData->QueryUserToken());
|
|
|
|
//
|
|
// Since szSearch contains the complete path, we call
|
|
// PathAccessCheck directly without resolving
|
|
// from absolute to virtual
|
|
//
|
|
|
|
fUserRead = TEST_UF( pUserData, READ_ACCESS);
|
|
fUserWrite = TEST_UF( pUserData, WRITE_ACCESS);
|
|
|
|
lsfi.fVolumeReadable =
|
|
PathAccessCheck(AccessTypeRead,
|
|
dwAccessMask,
|
|
fUserRead,
|
|
fUserWrite);
|
|
|
|
lsfi.fVolumeWritable =
|
|
PathAccessCheck(AccessTypeWrite,
|
|
dwAccessMask,
|
|
fUserRead,
|
|
fUserWrite);
|
|
|
|
|
|
lsfi.pszPathPart = szSearch;
|
|
lsfi.pszFileName = NULL;
|
|
lsfi.pszDecorate = NULL;
|
|
} // if ( long format output)
|
|
|
|
//
|
|
// We need to be impersonated only for UNIX-style listing.
|
|
// For UNIX style listing, we make some NTsecurity queries
|
|
// and they work only under the context of an impersonation.
|
|
//
|
|
|
|
if ( !(fImpersonated = pUserData->ImpersonateUser())) {
|
|
|
|
dwError = GetLastError();
|
|
}
|
|
}
|
|
|
|
//
|
|
// Loop for each directory entry
|
|
//
|
|
|
|
if (dwError != NO_ERROR ||
|
|
(dwError = lsb.AllocateBuffer( DEFAULT_LS_BUFFER_ALLOC_SIZE))
|
|
!= NO_ERROR) {
|
|
|
|
IF_DEBUG(ERROR) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Impersonation or Buffer allocation(%d bytes)",
|
|
" failed.\n",
|
|
DEFAULT_LS_BUFFER_ALLOC_SIZE));
|
|
}
|
|
|
|
if ( fImpersonated) {
|
|
|
|
pUserData->RevertToSelf();
|
|
}
|
|
|
|
return (dwError);
|
|
}
|
|
|
|
//
|
|
// Only map to lower case if not a remote drive AND the lower-case file
|
|
// names flag is set AND this is not a case perserving file system.
|
|
//
|
|
|
|
if (*szSearch != '\\') {
|
|
fMapToLowerCase = pUserData->QueryInstance()->QueryLowercaseFiles();
|
|
}
|
|
|
|
for( int idx = 0; serr == 0 && idx < cDirEntries; idx++) {
|
|
|
|
const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx];
|
|
DBG_ASSERT( pfdInfo != NULL);
|
|
const CHAR * pszFileName = pfdInfo->cFileName;
|
|
DWORD dwAttribs = pfdInfo->dwFileAttributes;
|
|
|
|
//
|
|
// Dump it.
|
|
//
|
|
|
|
// We may need to convert all filenames to lower case if so desired!!
|
|
// Also, if the filename was converted from UNICODE and ended up having
|
|
// characters such as '/' or '\' that can be harmful on the client, sanitize.
|
|
|
|
BOOL fHasSlash = !!_mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS);
|
|
|
|
if( fMapToLowerCase || fHasSlash) {
|
|
|
|
//
|
|
// copy file name to local scratch and change the ptr pszFileName
|
|
// because we cannot destroy pfdInfo->cFileName
|
|
//
|
|
P_strncpy( rgchLowFileName, pszFileName, sizeof(rgchLowFileName));
|
|
pszFileName = rgchLowFileName;
|
|
|
|
// lowercase if needed
|
|
if( fMapToLowerCase ) {
|
|
CharLower( rgchLowFileName);
|
|
}
|
|
|
|
// sanitize if need to
|
|
if( fHasSlash ) {
|
|
for( UCHAR *pch = (UCHAR*)rgchLowFileName;
|
|
(pch = _mbspbrk(pch, ILLEGAL_FILE_CHARS)) != NULL;
|
|
*pch++ = '^')
|
|
;
|
|
}
|
|
}
|
|
|
|
IF_DEBUG( DIR_LIST) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT, "Dir list for %s\n",
|
|
pszFileName));
|
|
}
|
|
|
|
//
|
|
// Send the partial data obtained so far.
|
|
// Use buffering to minimize number of sends occuring
|
|
//
|
|
|
|
if ( dwError == NO_ERROR) {
|
|
|
|
if ( lsb.QueryRemainingCB() < MIN_LS_BUFFER_SIZE) {
|
|
|
|
// send the bytes available in buffer and reset the buffer
|
|
serr = SockSend(pUserData, sock,
|
|
lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR));
|
|
lsb.ResetAppendPtr();
|
|
}
|
|
|
|
} else {
|
|
|
|
serr = dwError;
|
|
}
|
|
|
|
//
|
|
// Check for socket errors on send or pending OOB data.
|
|
//
|
|
|
|
if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
|
|
CHAR * pszDecorate = ( (pOptions->fDecorate && IS_DIR(dwAttribs) )
|
|
? "/" : "");
|
|
|
|
if( pOptions->OutputFormat == LsOutputLongFormat )
|
|
{
|
|
FILETIME ftLocal;
|
|
|
|
//
|
|
// Long format output. Just send the file/dir info.
|
|
//
|
|
|
|
//
|
|
// Map the file's last write time to (local) system time.
|
|
//
|
|
if ( !FileTimeToLocalFileTime(
|
|
PickFileTime( pfdInfo, pOptions),
|
|
&ftLocal) ||
|
|
! FileTimeToSystemTime(
|
|
&ftLocal,
|
|
&lsfi.stFile)
|
|
) {
|
|
|
|
dwError = GetLastError();
|
|
|
|
IF_DEBUG( ERROR) {
|
|
DBGPRINTF(( DBG_CONTEXT,
|
|
"Error in converting largeintger time %lu\n",
|
|
dwError));
|
|
}
|
|
} else {
|
|
|
|
lsfi.pszDecorate = pszDecorate;
|
|
lsfi.pszFileName = pszFileName;
|
|
|
|
if( fLikeMsdos ) {
|
|
|
|
dwError = FormatFileInfoLikeMsdos(&lsb,
|
|
pfdInfo,
|
|
&lsfi);
|
|
|
|
DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
|
|
} else {
|
|
|
|
dwError = FormatFileInfoLikeUnix(&lsb,
|
|
pfdInfo,
|
|
&lsfi);
|
|
|
|
DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
|
|
}
|
|
}
|
|
|
|
} else {
|
|
|
|
//
|
|
// Short format output.
|
|
//
|
|
|
|
INT cchSize = _snprintf(lsb.QueryAppendPtr(), lsb.QueryRemainingCB(),
|
|
"%s%s\r\n", pszFileName, pszDecorate);
|
|
|
|
DBG_ASSERT( cchSize > 0 );
|
|
|
|
if ((cchSize < 0) || (cchSize >= (INT)lsb.QueryRemainingCB()) ) {
|
|
dwError = ERROR_INSUFFICIENT_BUFFER;
|
|
} else {
|
|
lsb.IncrementCB( cchSize * sizeof(CHAR));
|
|
}
|
|
}
|
|
|
|
} // for()
|
|
|
|
//
|
|
// Get out of being impersonated.
|
|
//
|
|
if ( fImpersonated) {
|
|
|
|
pUserData->RevertToSelf();
|
|
}
|
|
|
|
if ( dwError == NO_ERROR) {
|
|
|
|
// send all the remaining bytes in the buffer and then free memory.
|
|
|
|
if ( lsb.QueryCB() != 0) {
|
|
|
|
serr = SockSend(pUserData, sock,
|
|
lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR));
|
|
}
|
|
|
|
lsb.FreeBuffer();
|
|
} else {
|
|
|
|
return ( dwError); // an error has occured. stop processing
|
|
}
|
|
|
|
|
|
if( serr == 0 && !TEST_UF( pUserData, OOB_DATA) && pOptions->fRecursive )
|
|
{
|
|
//
|
|
// The user want's a recursive directory search...
|
|
//
|
|
|
|
CHAR szOriginal[ MAX_PATH*2];
|
|
CHAR * pszOriginalFilePart;
|
|
|
|
|
|
// Obtain a copy of the path in the szOriginal so that we
|
|
// can change it while recursively calling ourselves.
|
|
|
|
if ( pszSearchPath == PSZ_DEFAULT_SEARCH_PATH) {
|
|
|
|
// means that we had all files/dir of current directory.
|
|
|
|
strcpy( szOriginal, fLikeMsdos ? ".\\" : "./" );
|
|
|
|
} else {
|
|
|
|
DBG_ASSERT( strlen(pszSearchPath) < MAX_PATH);
|
|
P_strncpy( szOriginal, pszSearchPath, MAX_PATH );
|
|
|
|
// strip off the wild cards if any present
|
|
if( fHasWildCards )
|
|
{
|
|
CHAR * pszTmp;
|
|
|
|
pszTmp = (PCHAR)_mbsrchr( (PUCHAR)szOriginal, '\\');
|
|
|
|
pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, '/' );
|
|
pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, ':' );
|
|
|
|
pszTmp = ( pszTmp) ? pszTmp+1 : szOriginal;
|
|
|
|
*pszTmp = '\0';
|
|
} else {
|
|
CHAR ch;
|
|
int cb = strlen( szOriginal);
|
|
|
|
DBG_ASSERT( cb > 0);
|
|
ch = *CharPrev( szOriginal, szOriginal + cb );
|
|
if( !IS_PATH_SEP( ch ) ) {
|
|
|
|
// to add "/"
|
|
DBG_ASSERT( strlen( szOriginal) + 2 < MAX_PATH);
|
|
strcat( szOriginal, fLikeMsdos ? "\\" : "/" );
|
|
}
|
|
}
|
|
}
|
|
|
|
DWORD szOriginalLen = strlen(szOriginal);
|
|
DWORD MaxFilePartLen = sizeof(szOriginal) - szOriginalLen;
|
|
pszOriginalFilePart = szOriginal + szOriginalLen;
|
|
|
|
DBG_ASSERT( tsDirInfo.IsValid());
|
|
DBG_ASSERT( cDirEntries == tsDirInfo.QueryFilesCount());
|
|
|
|
for( int idx = 0; serr == 0 && idx < cDirEntries; idx++) {
|
|
|
|
const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx];
|
|
DBG_ASSERT( pfdInfo != NULL);
|
|
const char * pszFileName = pfdInfo->cFileName;
|
|
DWORD dwAttribs = pfdInfo->dwFileAttributes;
|
|
|
|
//
|
|
// Filter out non-directories.
|
|
//
|
|
|
|
if( !IS_DIR( dwAttribs) ) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Also filter out directories with names containing illegal characters
|
|
//
|
|
|
|
if( _mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS) ) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Dump it.
|
|
//
|
|
|
|
DBG_ASSERT( strlen( pszOriginalFilePart) + strlen(pszFileName)
|
|
< MAX_PATH + 2);
|
|
P_strncpy( pszOriginalFilePart, pszFileName, MaxFilePartLen);
|
|
|
|
serr = SimulateLsWorker(pUserData,
|
|
fUseDataSocket,
|
|
szOriginal,
|
|
pOptions,
|
|
TRUE, TRUE);
|
|
|
|
//
|
|
// Check for socket errors on send or pending OOB data.
|
|
//
|
|
|
|
if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
} // for( directory looping)
|
|
|
|
} // if ( fRecursive)
|
|
|
|
|
|
// At the end of directory listing. Return back.
|
|
|
|
IF_DEBUG( DIR_LIST) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"SimulateLsWorker() for User %08x, Dir %s returns %d\n",
|
|
pUserData, pszSearchPath, serr));
|
|
}
|
|
|
|
|
|
return serr;
|
|
} // SimulateLsWorker()
|
|
|
|
|
|
|
|
|
|
APIERR
|
|
SpecialLsWorker(
|
|
USER_DATA * pUserData,
|
|
IN BOOL fUseDataSocket,
|
|
CHAR * pszSearchPath,
|
|
BOOL fShowDirectories,
|
|
IN OUT LS_BUFFER * plsb
|
|
)
|
|
/*++
|
|
This is the worker function for Special Ls function. It is similar to
|
|
the the SimulateLsWorker, only in that it shows directory if the
|
|
fShowDirectories flag is set.
|
|
|
|
The reason for this comes from a special FTP command which inquires about
|
|
all the files in the first level and second level of current directory,
|
|
which is not a recursive listing at all. This function when it recursively
|
|
calls itself, always sets the fShowDirectories as FALSE.
|
|
|
|
Arguments:
|
|
pUserData pointer to user data object that initiated the request.
|
|
fUseDataSocket if TRUE use DataSocket of UserData else
|
|
use the control socket of UserData.
|
|
pszSearchPath pointer to null-terminated string for requested directory.
|
|
NULL means use current directory.
|
|
fShowDirectories only show directories if TRUE.
|
|
plsb pointer to buffer to accumulate the data generated and send
|
|
it out in a single bulk.
|
|
|
|
Returns:
|
|
APIERR 0 if successful.
|
|
|
|
History:
|
|
KeithMo 17-Mar-1993 Created.
|
|
MuraliK 26-Apr-1995 ReCreated to use new way of generation.
|
|
--*/
|
|
{
|
|
CHAR chSeparator;
|
|
CHAR * pszRecurse;
|
|
SOCKET sock;
|
|
BOOL fHasWildCards = FALSE;
|
|
DWORD dwError = NO_ERROR;
|
|
TS_DIRECTORY_INFO tsDirInfo( pUserData->QueryInstance()->GetTsvcCache());
|
|
CHAR szSearch[MAX_PATH+1];
|
|
CHAR szRecurse[MAX_PATH+1];
|
|
BOOL fMapToLowerCase = FALSE;
|
|
CHAR rgchLowFileName[MAX_PATH+1]; // used for lower casing filename
|
|
BOOL fHadOneComponent = FALSE;
|
|
|
|
DBG_ASSERT( pUserData != NULL);
|
|
|
|
IF_DEBUG( DIR_LIST) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Entering SpecialLsWorker( %08x, %s)\n",
|
|
pUserData, pszSearchPath));
|
|
}
|
|
|
|
|
|
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.
|
|
//
|
|
if ( strlen(pszSearchPath) > sizeof(szRecurse) - 1 )
|
|
{
|
|
return ERROR_BUFFER_OVERFLOW;
|
|
}
|
|
|
|
strcpy( szRecurse, pszSearchPath);
|
|
|
|
// get slash.
|
|
pszRecurse = (PCHAR)_mbsrchr( (PUCHAR)szRecurse, '\\');
|
|
pszRecurse = pszRecurse ? pszRecurse : strrchr( szRecurse, '/');
|
|
fHadOneComponent = (pszRecurse == NULL);
|
|
|
|
if( strpbrk( szRecurse, PSZ_WILD_CHARACTERS) != NULL )
|
|
{
|
|
//
|
|
// Search path contains wildcards.
|
|
//
|
|
|
|
fHasWildCards = TRUE;
|
|
|
|
// we do not care about components when wild card is present
|
|
fHadOneComponent = FALSE;
|
|
|
|
//
|
|
// Strip the wildcard pattern from the search path.
|
|
// look for both kind of slashes ( since precanonicalized)
|
|
//
|
|
|
|
//
|
|
// If we found right-most dir component, skip path separator
|
|
// else set it to start of search path.
|
|
//
|
|
pszRecurse = pszRecurse ? pszRecurse + 1 : szRecurse;
|
|
} else {
|
|
|
|
//
|
|
// No wildcards, so the argument must be a path.
|
|
// Ensure it is terminated with a path separator.
|
|
//
|
|
|
|
pszRecurse = CharPrev( szRecurse, szRecurse + strlen(szRecurse) );
|
|
|
|
if( !IS_PATH_SEP( *pszRecurse ) )
|
|
{
|
|
*++pszRecurse = chSeparator;
|
|
}
|
|
|
|
pszRecurse++; // skip the path separator
|
|
}
|
|
} else {
|
|
|
|
//
|
|
// No arguments.
|
|
//
|
|
|
|
pszRecurse = szRecurse;
|
|
|
|
|
|
//
|
|
// Munge the arguments around a bit. NULL = *.* in current
|
|
// directory. If the user specified a directory (like d:\foo)
|
|
// then append *.*.
|
|
//
|
|
|
|
pszSearchPath = (char *) PSZ_DEFAULT_SEARCH_PATH;
|
|
}
|
|
|
|
*pszRecurse = '\0';
|
|
|
|
//
|
|
// Canonicalize the search path.
|
|
//
|
|
DWORD cbSize = sizeof(szSearch) - 1; // save space for '.' we may need to add
|
|
dwError = pUserData->VirtualCanonicalize(szSearch, &cbSize,
|
|
pszSearchPath,
|
|
AccessTypeRead);
|
|
|
|
DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
|
|
|
|
if( dwError == NO_ERROR ) {
|
|
|
|
FTP_LS_FILTER_INFO fls; // required for generating directory listing
|
|
|
|
//
|
|
// VirtualCanonicalize() when sanitizing the path removes
|
|
// trailing dots from the path. Replace them here
|
|
//
|
|
|
|
if( fHasWildCards && (pszSearchPath[strlen(pszSearchPath)-1] == '.')) {
|
|
|
|
strcat( szSearch, "." );
|
|
}
|
|
|
|
//
|
|
// Build the directory list.
|
|
//
|
|
|
|
if (pUserData->ImpersonateUser()) {
|
|
|
|
if (SeparateOutFilterSpec( szSearch,
|
|
fHasWildCards,
|
|
&fls.pszExpression) ) {
|
|
|
|
fls.fFilterHidden = TRUE;
|
|
fls.fFilterSystem = TRUE;
|
|
fls.fFilterDotDot = TRUE;
|
|
|
|
fls.fRegExpression = ( fls.pszExpression != NULL && fHasWildCards);
|
|
fls.fIgnoreCase = pUserData->QueryInstance()->QueryLowercaseFiles();
|
|
|
|
|
|
dwError = GetDirectoryInfo(pUserData,
|
|
&tsDirInfo,
|
|
szSearch,
|
|
&fls,
|
|
NULL); // unsorted list
|
|
} else {
|
|
|
|
dwError = GetLastError();
|
|
}
|
|
|
|
pUserData->RevertToSelf();
|
|
|
|
} else {
|
|
|
|
dwError = GetLastError();
|
|
}
|
|
}
|
|
|
|
//
|
|
// If there were any errors, tell them the bad news now.
|
|
//
|
|
|
|
if( dwError != NO_ERROR ) {
|
|
|
|
return ( dwError);
|
|
}
|
|
|
|
if ( fHadOneComponent) {
|
|
|
|
// HARD CODE! Spend some time and understand this....
|
|
|
|
//
|
|
// Adjust the szRecurse buffer to contain appropriate path
|
|
// such that in presence of one component we generate proper
|
|
// result.
|
|
//
|
|
|
|
// the given path is either invalid or non-directory
|
|
// so reset the string stored in szRecurse.
|
|
szRecurse[0] = '\0';
|
|
pszRecurse = szRecurse;
|
|
}
|
|
|
|
|
|
//
|
|
// Only map to lower case if not a remote drive AND the lower-case file
|
|
// names flag is set AND this is not a case perserving file system.
|
|
//
|
|
|
|
if (*szSearch != '\\') {
|
|
fMapToLowerCase = pUserData->QueryInstance()->QueryLowercaseFiles();
|
|
}
|
|
|
|
//
|
|
// Loop until we're out of files to find.
|
|
//
|
|
|
|
sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() :
|
|
pUserData->QueryControlSocket());
|
|
|
|
int cDirEntries = tsDirInfo.QueryFilesCount();
|
|
|
|
for( int idx = 0; dwError == NO_ERROR && idx < cDirEntries; idx++) {
|
|
|
|
const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx];
|
|
DBG_ASSERT( pfdInfo != NULL);
|
|
const CHAR * pszFileName = pfdInfo->cFileName;
|
|
DWORD dwAttribs = pfdInfo->dwFileAttributes;
|
|
|
|
if ( !fShowDirectories && IS_DIR( dwAttribs)) {
|
|
|
|
continue;
|
|
}
|
|
|
|
//
|
|
// Dump it.
|
|
//
|
|
|
|
// We may need to convert all filenames to lower case if so desired!!
|
|
// Also, if the filename was converted from UNICODE and ended up having
|
|
// characters such as '/' or '\' that can be harmful on the client, sanitize.
|
|
|
|
BOOL fHasSlash = !!_mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS);
|
|
|
|
if( fMapToLowerCase || fHasSlash) {
|
|
|
|
//
|
|
// copy file name to local scratch and change the ptr pszFileName
|
|
// because we cannot destroy pfdInfo->cFileName
|
|
//
|
|
P_strncpy( rgchLowFileName, pszFileName, sizeof(rgchLowFileName));
|
|
pszFileName = rgchLowFileName;
|
|
|
|
// lowercase if needed
|
|
if( fMapToLowerCase ) {
|
|
CharLower( rgchLowFileName);
|
|
}
|
|
|
|
// sanitize if need to
|
|
if( fHasSlash ) {
|
|
for( UCHAR *pch = (UCHAR*)rgchLowFileName;
|
|
(pch = _mbspbrk(pch, ILLEGAL_FILE_CHARS)) != NULL;
|
|
*pch++ = '^')
|
|
;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Send the partial data obtained so far.
|
|
// Use buffering to minimize number of sends occuring
|
|
//
|
|
|
|
if ( dwError == NO_ERROR) {
|
|
|
|
if ( plsb->QueryRemainingCB() < MIN_LS_BUFFER_SIZE) {
|
|
|
|
// send the bytes available in buffer and reset the buffer
|
|
dwError = SockSend(pUserData, sock,
|
|
plsb->QueryBuffer(),
|
|
plsb->QueryCB()/sizeof(CHAR));
|
|
plsb->ResetAppendPtr();
|
|
}
|
|
|
|
}
|
|
|
|
//
|
|
// Test for aborted directory listing or socket error.
|
|
//
|
|
|
|
if( TEST_UF( pUserData, OOB_DATA ) || ( dwError != NO_ERROR ) )
|
|
{
|
|
break;
|
|
}
|
|
|
|
//
|
|
// 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.
|
|
// Do not recurse if directory name contains illegal characters (converted from Unicode)
|
|
//
|
|
|
|
if( fHasWildCards && IS_DIR(dwAttribs) && !_mbspbrk((UCHAR*)pszFileName, ILLEGAL_FILE_CHARS) ) {
|
|
|
|
DBG_ASSERT(strcmp( pszFileName, "." ) != 0);
|
|
DBG_ASSERT(strcmp( pszFileName, "..") != 0);
|
|
|
|
DBG_ASSERT(strlen(szRecurse)+strlen( pszFileName) < MAX_PATH);
|
|
// BUGBUG: need to understand this better and ensure this does not overflow
|
|
strcpy( pszRecurse, pszFileName );
|
|
strcat( pszRecurse, "/"); // indicating this is a directory
|
|
|
|
dwError = SpecialLsWorker(pUserData,
|
|
fUseDataSocket,
|
|
szRecurse,
|
|
FALSE,
|
|
plsb);
|
|
} else {
|
|
|
|
INT cchSize;
|
|
|
|
*pszRecurse = '\0'; // as a side effect this terminates szRecurse.
|
|
|
|
//
|
|
// Short format output.
|
|
//
|
|
|
|
cchSize = _snprintf(plsb->QueryAppendPtr(), plsb->QueryRemainingCB(),
|
|
"%s%s\r\n",
|
|
szRecurse,
|
|
pszFileName);
|
|
|
|
DBG_ASSERT( cchSize > 0 );
|
|
|
|
if ( (cchSize < 0) || (cchSize >= (INT)plsb->QueryRemainingCB()) ) {
|
|
dwError = ERROR_INSUFFICIENT_BUFFER;
|
|
} else {
|
|
plsb->IncrementCB( cchSize*sizeof(CHAR));
|
|
}
|
|
|
|
}
|
|
|
|
} // for
|
|
|
|
|
|
IF_DEBUG( DIR_LIST) {
|
|
|
|
DBGPRINTF((DBG_CONTEXT,
|
|
"Leaving SpecialLsWorker() with Error = %d\n",
|
|
dwError));
|
|
}
|
|
|
|
return (dwError);
|
|
|
|
} // SpecialLsWorker()
|
|
|
|
|
|
|
|
|
|
/**************************************************
|
|
* Formatting functions.
|
|
**************************************************/
|
|
|
|
|
|
|
|
DWORD
|
|
FormatFileInfoLikeMsdos(
|
|
IN OUT LS_BUFFER * plsb,
|
|
IN const WIN32_FIND_DATA * pfdInfo,
|
|
IN const LS_FORMAT_INFO * pFormatInfo
|
|
)
|
|
/*++
|
|
|
|
Forms an MSDOS like directory entry for the given dir info object.
|
|
|
|
Arguments:
|
|
|
|
plsb pointer to buffer into which the dir line is generated.
|
|
|
|
pfdInfo pointer to dir information element.
|
|
pFormatInfo pointer to information required for formatting.
|
|
( use the file name in pFormatInfo, becauze it may have been
|
|
made into lower case if necessary)
|
|
|
|
Returns:
|
|
Win32 error code and NO_ERROR on success.
|
|
|
|
History:
|
|
MuraliK 25-Apr-1995
|
|
--*/
|
|
{
|
|
DWORD dwError = NO_ERROR;
|
|
CHAR szSizeOrDir[32];
|
|
DWORD cbReqd;
|
|
|
|
DBG_ASSERT(plsb != NULL && pfdInfo != NULL && pFormatInfo != NULL);
|
|
|
|
if ( IS_DIR( pfdInfo->dwFileAttributes)) {
|
|
|
|
strcpy( szSizeOrDir, "<DIR> " );
|
|
} else {
|
|
|
|
LARGE_INTEGER li;
|
|
li.HighPart = pfdInfo->nFileSizeHigh;
|
|
li.LowPart = pfdInfo->nFileSizeLow;
|
|
|
|
IsLargeIntegerToDecimalChar( &li, szSizeOrDir);
|
|
}
|
|
|
|
DBG_ASSERT( strlen(szSizeOrDir) <= 20);
|
|
|
|
cbReqd = ( 10 // size for the date field
|
|
+ 10 // size for time field
|
|
+ 20 // space for size/dir
|
|
+ strlen( pFormatInfo->pszFileName)
|
|
+ 8 // addl space + decoration ...
|
|
) * sizeof(CHAR);
|
|
|
|
DBG_ASSERT( cbReqd <= MIN_LS_BUFFER_SIZE);
|
|
|
|
if ( cbReqd < plsb->QueryRemainingCB()) {
|
|
|
|
register const SYSTEMTIME * pst = &pFormatInfo->stFile;
|
|
WORD wHour;
|
|
char * pszAmPm;
|
|
DWORD cchUsed;
|
|
|
|
wHour = pst->wHour;
|
|
pszAmPm = ( wHour < 12 ) ? "AM" : "PM";
|
|
|
|
if ( wHour == 0 ) { wHour = 12; }
|
|
else if ( wHour > 12) { wHour -= 12; }
|
|
|
|
if (pFormatInfo->fFourDigitYear) {
|
|
cchUsed = wsprintfA(plsb->QueryAppendPtr(),
|
|
"%02u-%02u-%04u %02u:%02u%s %20s %s%s\r\n",
|
|
pst->wMonth,
|
|
pst->wDay,
|
|
pst->wYear,
|
|
wHour,
|
|
pst->wMinute,
|
|
pszAmPm,
|
|
szSizeOrDir,
|
|
pFormatInfo->pszFileName,
|
|
pFormatInfo->pszDecorate);
|
|
}
|
|
else {
|
|
cchUsed = wsprintfA(plsb->QueryAppendPtr(),
|
|
"%02u-%02u-%02u %02u:%02u%s %20s %s%s\r\n",
|
|
pst->wMonth,
|
|
pst->wDay,
|
|
pst->wYear%100, //instead of wYear - 1900
|
|
wHour,
|
|
pst->wMinute,
|
|
pszAmPm,
|
|
szSizeOrDir,
|
|
pFormatInfo->pszFileName,
|
|
pFormatInfo->pszDecorate);
|
|
}
|
|
|
|
DBG_ASSERT( cchUsed * sizeof(CHAR) <= cbReqd);
|
|
plsb->IncrementCB(cchUsed * sizeof(CHAR));
|
|
|
|
} else {
|
|
|
|
dwError = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
|
|
return ( dwError);
|
|
|
|
} // FormatFileInfoLikeMsdos()
|
|
|
|
|
|
|
|
|
|
DWORD
|
|
FormatFileInfoLikeUnix(
|
|
IN OUT LS_BUFFER * plsb,
|
|
IN const WIN32_FIND_DATA * pfdInfo,
|
|
IN const LS_FORMAT_INFO * pFormatInfo
|
|
)
|
|
/*++
|
|
This function formats file information for a UNIX stle client.
|
|
|
|
Arguments:
|
|
|
|
plsb pointer to buffer into which the dir line is generated.
|
|
pfdInfo pointer to dir information element.
|
|
pFormatInfo pointer to information required for long formatting.
|
|
|
|
Returns:
|
|
Win32 error code and NO_ERROR on success.
|
|
|
|
History:
|
|
MuraliK 25-Apr-1995
|
|
--*/
|
|
{
|
|
DWORD dwError = NO_ERROR;
|
|
CHAR * pszFileOwner;
|
|
CHAR * pszFileGroup;
|
|
const SYSTEMTIME * pst;
|
|
DWORD dwMode;
|
|
DWORD cLinks;
|
|
LARGE_INTEGER li;
|
|
CHAR attrib[4];
|
|
CHAR szTimeOrYear[12];
|
|
CHAR szSize[32];
|
|
|
|
DWORD cbReqd;
|
|
|
|
static CHAR * apszMonths[] = { " ", "Jan", "Feb", "Mar", "Apr",
|
|
"May", "Jun", "Jul", "Aug", "Sep",
|
|
"Oct", "Nov", "Dec" };
|
|
|
|
DBG_ASSERT( plsb != NULL);
|
|
DBG_ASSERT( pFormatInfo != NULL );
|
|
DBG_ASSERT( pFormatInfo->hUserToken != NULL );
|
|
DBG_ASSERT( pFormatInfo->pszPathPart != NULL );
|
|
DBG_ASSERT( pfdInfo != NULL );
|
|
|
|
|
|
//
|
|
// Build the attribute triple. Note that we only build one,
|
|
// and replicate it three times for the owner/group/other fields.
|
|
//
|
|
|
|
dwMode = ComputeModeBits( pFormatInfo->hUserToken,
|
|
pFormatInfo->pszPathPart,
|
|
pfdInfo,
|
|
&cLinks,
|
|
pFormatInfo->fVolumeReadable,
|
|
pFormatInfo->fVolumeWritable );
|
|
|
|
attrib[0] = ( dwMode & FILE_MODE_R ) ? 'r' : '-';
|
|
attrib[1] = ( dwMode & FILE_MODE_W ) ? 'w' : '-';
|
|
attrib[2] = ( dwMode & FILE_MODE_X ) ? 'x' : '-';
|
|
attrib[3] = '\0';
|
|
|
|
pst = &pFormatInfo->stFile;
|
|
|
|
// NYI: can we make the following a single wsprintf call ??
|
|
if( pst->wYear == pFormatInfo->wCurrentYear ) {
|
|
|
|
//
|
|
// The file's year matches the current year, so
|
|
// display the hour & minute of the last write.
|
|
//
|
|
|
|
wsprintfA( szTimeOrYear, "%2u:%02u", pst->wHour, pst->wMinute );
|
|
} else {
|
|
|
|
//
|
|
// The file's year does not match the current
|
|
// year, so display the year of the last write.
|
|
//
|
|
|
|
wsprintfA( szTimeOrYear, "%4u", pst->wYear );
|
|
}
|
|
|
|
//
|
|
// CODEWORK: How expensive would it be do
|
|
// get the proper owner & group names?
|
|
//
|
|
|
|
pszFileOwner = "owner";
|
|
pszFileGroup = "group";
|
|
|
|
//
|
|
// Get the size in a displayable form.
|
|
//
|
|
|
|
li.HighPart = pfdInfo->nFileSizeHigh;
|
|
li.LowPart = pfdInfo->nFileSizeLow;
|
|
|
|
IsLargeIntegerToDecimalChar( &li, szSize);
|
|
|
|
//
|
|
// Dump it.
|
|
//
|
|
DBG_ASSERT( strlen(szSize) <= 12);
|
|
cbReqd = ( 3*strlen(attrib) + strlen( pszFileOwner)
|
|
+ strlen( pszFileGroup) + 12 + 20 // date
|
|
+ strlen( pFormatInfo->pszFileName)
|
|
+ strlen( pFormatInfo->pszDecorate) + 20 // 20 for spaces etc.
|
|
) * sizeof(CHAR);
|
|
DBG_ASSERT( cbReqd < MIN_LS_BUFFER_SIZE);
|
|
|
|
if ( cbReqd < plsb->QueryRemainingCB()) {
|
|
|
|
DWORD cchUsed = wsprintfA( plsb->QueryAppendPtr(),
|
|
"%c%s%s%s %3lu %-8s %-8s %12s %s %2u %5s %s%s\r\n",
|
|
(IS_DIR(pfdInfo->dwFileAttributes) ? 'd' : '-'),
|
|
attrib,
|
|
attrib,
|
|
attrib,
|
|
cLinks,
|
|
pszFileOwner,
|
|
pszFileGroup,
|
|
szSize,
|
|
apszMonths[pst->wMonth],
|
|
pst->wDay,
|
|
szTimeOrYear,
|
|
pFormatInfo->pszFileName,
|
|
pFormatInfo->pszDecorate);
|
|
|
|
DBG_ASSERT( cchUsed * sizeof(CHAR) <= cbReqd);
|
|
plsb->IncrementCB( cchUsed*sizeof(CHAR));
|
|
|
|
} else {
|
|
|
|
dwError = ERROR_INSUFFICIENT_BUFFER;
|
|
}
|
|
|
|
return ( dwError);
|
|
|
|
} // FormatFileInfoLikeUnix()
|
|
|
|
/************************ End of File ************************/
|