Source code of Windows XP (NT5)
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.

1717 lines
48 KiB

  1. /**********************************************************************/
  2. /** Microsoft Windows NT **/
  3. /** Copyright(c) Microsoft Corp., 1995 **/
  4. /**********************************************************************/
  5. /*
  6. newls.cxx
  7. Implements a simulated "ls" command for the FTP Server service,
  8. with buffering and possibly caching of the results generated.
  9. Functions exported by this module:
  10. SimulateLs()
  11. SpecialLs()
  12. FILE HISTORY:
  13. MuraliK 19-April-1995 Created.
  14. */
  15. # include "ftpdp.hxx"
  16. # include "tsunami.hxx"
  17. # include "lsaux.hxx"
  18. # include <mbstring.h>
  19. /**********************************************************************
  20. * Private Globals
  21. **********************************************************************/
  22. // Following message is required to send error msg when the file
  23. // or directory is absent.
  24. extern CHAR * p_NoFileOrDirectory; // This lives in engine.c.
  25. static const char * PSZ_DEFAULT_SEARCH_PATH = "";
  26. static const char * PSZ_WILD_CHARACTERS = "*?<>"; // include DOS wilds!
  27. /**********************************************************************
  28. * Prototypes of Functions
  29. **********************************************************************/
  30. DWORD
  31. FormatFileInfoLikeMsdos(
  32. IN OUT LS_BUFFER * plsb,
  33. IN const WIN32_FIND_DATA * pfdInfo,
  34. IN const LS_FORMAT_INFO * pFormatInfo
  35. );
  36. DWORD
  37. FormatFileInfoLikeUnix(
  38. IN OUT LS_BUFFER * plsb,
  39. IN const WIN32_FIND_DATA * pfdInfo,
  40. IN const LS_FORMAT_INFO * pFormatInfo
  41. );
  42. APIERR
  43. SimulateLsWorker(
  44. IN USER_DATA * pUserData,
  45. IN BOOL fUseDataSocket,
  46. IN CHAR * pszSearchPath,
  47. IN const LS_OPTIONS * pOptions,
  48. IN BOOL fSendHeader = FALSE,
  49. IN BOOL fSendBlank = FALSE
  50. );
  51. APIERR
  52. SpecialLsWorker(
  53. IN USER_DATA *pUserData,
  54. IN BOOL fUseDataSocket,
  55. CHAR * pszSearchPath,
  56. BOOL fShowDirectories,
  57. IN OUT LS_BUFFER * plsb
  58. );
  59. //
  60. // The following is a table consisting of the sort methods used
  61. // for generating various dir listing. The table is indexed by LS_SORT.
  62. // This is used for finding the appropriate compare function for
  63. // any given sort method.
  64. // THE ORDER OF FUNCTIONS IN THIS ARRAY MUST MATCH THE ORDER IN LS_SORT!
  65. //
  66. static PFN_CMP_WIN32_FIND_DATA CompareRoutines[] = {
  67. CompareNamesInFileInfo, // Normal sort order.
  68. CompareWriteTimesInFileInfo,
  69. CompareCreationTimesInFileInfo,
  70. CompareAccessTimesInFileInfo,
  71. CompareNamesRevInFileInfo, // Reversed sort order.
  72. CompareWriteTimesRevInFileInfo,
  73. CompareCreationTimesRevInFileInfo,
  74. CompareAccessTimesRevInFileInfo
  75. };
  76. // method,direction are used for indexing.
  77. #define SORT_INDEX(method, dirn) ((INT)(method) + \
  78. ((dirn) ? (INT)MaxLsSort : 0))
  79. /**********************************************************************
  80. * Functions
  81. **********************************************************************/
  82. static
  83. BOOL
  84. SeparateOutFilterSpec( IN OUT CHAR * szPath, IN BOOL fHasWildCards,
  85. OUT LPCSTR * ppszFilterSpec)
  86. /*++
  87. The path has the form c:\ftppath\foo\bar\*.*
  88. Check to see if the path is already a directory.
  89. If so set filter as nothing.
  90. This function identifies the last "\" and terminates the
  91. path at that point. The remaining forms a filter (here: *.*)
  92. --*/
  93. {
  94. char * pszFilter;
  95. BOOL fDir = FALSE;
  96. IF_DEBUG( DIR_LIST) {
  97. DBGPRINTF((DBG_CONTEXT, "SeparateOutFilter( %s, %d)\n",
  98. szPath, fHasWildCards));
  99. }
  100. DBG_ASSERT( ppszFilterSpec != NULL);
  101. *ppszFilterSpec = NULL; // initialize.
  102. if ( !fHasWildCards) {
  103. // Identify if the path is a directory
  104. DWORD dwAttribs = GetFileAttributes( szPath);
  105. if ( dwAttribs == INVALID_FILE_ATTRIBUTES) {
  106. return ( FALSE);
  107. } else {
  108. fDir = ( IS_DIR(dwAttribs));
  109. }
  110. }
  111. if ( !fDir ) {
  112. pszFilter = (PCHAR)_mbsrchr( (PUCHAR)szPath, '\\');
  113. //This has to exist, since valid path was supplied.
  114. DBG_ASSERT( pszFilter != NULL);
  115. *pszFilter = '\0'; // terminate the old path.
  116. pszFilter++; // skip past the terminating null character.
  117. *ppszFilterSpec = (*pszFilter == '\0') ? NULL : pszFilter;
  118. IF_DEBUG(DIR_LIST) {
  119. DBGPRINTF((DBG_CONTEXT, "Path = %s; Filter = %s\n",
  120. szPath, *ppszFilterSpec));
  121. }
  122. }
  123. return (TRUE);
  124. } // SeparateOutFilterSpec()
  125. APIERR
  126. SimulateLs(
  127. IN USER_DATA * pUserData,
  128. IN OUT CHAR * pszArg,
  129. IN BOOL fUseDataSocket,
  130. IN BOOL fDefaultLong
  131. )
  132. /*++
  133. This function simulates an LS command. This simulated ls command supports
  134. the following switches:
  135. -C = Multi column, sorted down.
  136. -l = Long format output.
  137. -1 = One entry per line (default).
  138. -F = Directories have '/' appended.
  139. -t = Sort by time of last write.
  140. -c = Sort by time of creation.
  141. -u = Sort by time of last access.
  142. -r = Reverse sort direction.
  143. -a = Show all files (including .*).
  144. -A = Show all files (except . and ..).
  145. -R = Recursive listing.
  146. Arguments:
  147. pUserData -- the user initiating the request.
  148. pszArg -- contains the search path, preceded by switches.
  149. Note: The argument is destroyed during processing!!!!
  150. fUseDataSocket -- if TRUE use Data socket, else use control socket.
  151. fDefaultLong -- should the default be long ? ( if TRUE)
  152. Returns:
  153. APIERR, 0 on success.
  154. --*/
  155. {
  156. APIERR serr = 0;
  157. LS_OPTIONS options;
  158. CHAR * pszToken = pszArg;
  159. CHAR * pszDelimiters = " \t";
  160. DBG_ASSERT( pUserData != NULL );
  161. //
  162. // Setup default ls options.
  163. //
  164. options.OutputFormat = (( fDefaultLong) ?
  165. LsOutputLongFormat : LsOutputSingleColumn);
  166. options.SortMethod = LsSortByName;
  167. options.fReverseSort = FALSE;
  168. options.fDecorate = FALSE;
  169. options.fShowAll = FALSE;
  170. options.fShowDotDot = FALSE;
  171. options.fRecursive = FALSE;
  172. options.lsStyle = ( TEST_UF( pUserData, MSDOS_DIR_OUTPUT)
  173. ? LsStyleMsDos
  174. : LsStyleUnix
  175. );
  176. options.fFourDigitYear= TEST_UF( pUserData, 4_DIGIT_YEAR);
  177. //
  178. // Process switches in the input, if any
  179. //
  180. // simplify things by skipping whitespace...
  181. if (pszArg && isspace(*pszArg)) {
  182. while (isspace(*pszArg))
  183. pszArg++;
  184. }
  185. // now we should be pointing to the options, or the filename
  186. if (pszArg && (*pszArg == '-')) {
  187. for( pszToken = strtok( pszArg, pszDelimiters ); // getfirst Tok.
  188. ( ( pszToken != NULL ) && ( *pszToken == '-' ) );
  189. pszToken = strtok( NULL, pszDelimiters) // get next token
  190. ) {
  191. DBG_ASSERT( *pszToken == '-' );
  192. // process all the switches in single token
  193. // for( pszToken++; *pszToken; pszToken++) is written as follows
  194. while ( *++pszToken) {
  195. switch( *pszToken ) {
  196. case 'C' :
  197. case '1' :
  198. options.OutputFormat = LsOutputSingleColumn;
  199. break;
  200. case 'l' :
  201. options.OutputFormat = LsOutputLongFormat;
  202. break;
  203. case 'F' :
  204. options.fDecorate = TRUE;
  205. break;
  206. case 'r' :
  207. options.fReverseSort = TRUE;
  208. break;
  209. case 't' :
  210. options.SortMethod = LsSortByWriteTime;
  211. break;
  212. case 'c' :
  213. options.SortMethod = LsSortByCreationTime;
  214. break;
  215. case 'u' :
  216. options.SortMethod = LsSortByAccessTime;
  217. break;
  218. case 'a' :
  219. options.fShowAll = TRUE;
  220. options.fShowDotDot = TRUE;
  221. break;
  222. case 'A' :
  223. options.fShowAll = TRUE;
  224. options.fShowDotDot = FALSE;
  225. break;
  226. case 'R' :
  227. options.fRecursive = TRUE;
  228. break;
  229. default:
  230. IF_DEBUG( COMMANDS ) {
  231. DBGPRINTF(( DBG_CONTEXT,
  232. "ls: skipping unsupported option '%c'\n",
  233. *pszToken ));
  234. }
  235. break;
  236. } // switch()
  237. } // process all switches in a token
  238. } // for
  239. }
  240. //
  241. // If the user is requesting an MSDOS-style long-format listing,
  242. // then enable display of "." and "..". This will make the MSDOS-style
  243. // long-format output look a little more like MSDOS.
  244. //
  245. options.fShowDotDot = ( options.fShowDotDot ||
  246. ( options.lsStyle == LsStyleMsDos &&
  247. ( options.OutputFormat == LsOutputLongFormat ))
  248. );
  249. //
  250. // since LIST is sent out synchronously, bump up thread count
  251. // before beginning to send out the response for LIST
  252. //
  253. // A better method:
  254. // Make LIST generate response in a buffer and use async IO
  255. // operations for sending response.
  256. // TBD (To Be Done)
  257. //
  258. AtqSetInfo( AtqIncMaxPoolThreads, 0);
  259. //
  260. // At this point, pszToken is either NULL or points
  261. // to the first (of potentially many) LS search paths.
  262. //
  263. serr = SimulateLsWorker(pUserData, fUseDataSocket, pszToken, &options);
  264. #if 0
  265. // the following code supported handling a list of files to send info
  266. // back for - delimited by spaces. However, the spec doesn't support
  267. // multiple arguments on an LS call.
  268. if( pszToken == NULL ) {
  269. //
  270. // Get the directory listing for current directory.
  271. // Send in NULL for the path to be listed.
  272. //
  273. serr = SimulateLsWorker(pUserData, fUseDataSocket, NULL, &options);
  274. } else {
  275. //
  276. // There is a sequence of tokens on the command line.
  277. // Process them all.
  278. //
  279. BOOL fSendHeader = FALSE;
  280. while( pszToken != NULL ) {
  281. CHAR * pszNextToken = strtok( NULL, pszDelimiters );
  282. //
  283. // Send the directory.
  284. //
  285. serr = SimulateLsWorker(pUserData,
  286. fUseDataSocket,
  287. pszToken,
  288. &options,
  289. fSendHeader || (pszNextToken != NULL),
  290. fSendHeader);
  291. //
  292. // If there are more directories to send,
  293. // send a blank line as a separator.
  294. //
  295. pszToken = pszNextToken;
  296. fSendHeader = TRUE; // turn on for subsequent sends
  297. if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) )
  298. {
  299. break;
  300. }
  301. } // while
  302. } // multiple arguments
  303. #endif
  304. //
  305. // bring down the thread count when response is completed
  306. // TBD: Use Async send reponse()
  307. //
  308. AtqSetInfo( AtqDecMaxPoolThreads, 0);
  309. return ( serr);
  310. } // SimulateLs()
  311. APIERR
  312. SpecialLs(
  313. USER_DATA * pUserData,
  314. CHAR * pszArg,
  315. IN BOOL fUseDataSocket
  316. )
  317. /*++
  318. This produces a special form of the directory listing that is required
  319. when an NLST command is received with no switches. Most of the FTP clients
  320. require this special form in order to get the MGET and MDEL commands
  321. to work. This produces atmost one level of directory information.
  322. Arguments:
  323. pUserData - the user initiating the request.
  324. pszArg - pointer to null-terminated string containing the argument.
  325. NULL=current directory for UserData.
  326. fUseDataSocket - if TRUE use Data Socket, else use the ContorlSocket.
  327. Returns:
  328. APIERR - 0 if successful, !0 if not.
  329. --*/
  330. {
  331. APIERR dwError = 0;
  332. LS_BUFFER lsb;
  333. DBG_ASSERT( pUserData != NULL );
  334. DBG_ASSERT( ( pszArg == NULL ) || ( *pszArg != '-' ) ); // No options
  335. if ((dwError = lsb.AllocateBuffer( DEFAULT_LS_BUFFER_ALLOC_SIZE))
  336. != NO_ERROR) {
  337. IF_DEBUG(ERROR) {
  338. DBGPRINTF((DBG_CONTEXT, "Buffer allocation(%d bytes) failed.\n",
  339. DEFAULT_LS_BUFFER_ALLOC_SIZE));
  340. }
  341. return (dwError);
  342. }
  343. //
  344. // since LIST is sent out synchronously, bump up thread count
  345. // before beginning to send out the response for LIST
  346. //
  347. // A better method:
  348. // Make LIST generate response in a buffer and use async IO
  349. // operations for sending response.
  350. // TBD (To Be Done)
  351. //
  352. AtqSetInfo( AtqIncMaxPoolThreads, 0);
  353. //
  354. // Let the worker do the dirty work.
  355. //
  356. dwError = SpecialLsWorker(pUserData,
  357. fUseDataSocket,
  358. pszArg, // search path (no switches)
  359. TRUE, // show directories
  360. &lsb);
  361. #if 0
  362. // the following code supported handling a list of files to send info
  363. // back for - delimited by spaces. However, the spec doesn't support
  364. // multiple arguments on an LS call.
  365. if( pszArg == NULL )
  366. {
  367. dwError = SpecialLsWorker(pUserData,
  368. fUseDataSocket,
  369. pszArg, // search path (no switches)
  370. TRUE, // show directories
  371. &lsb);
  372. }
  373. else
  374. {
  375. CHAR * pszToken;
  376. CHAR * pszDelimiters = " \t";
  377. dwError = NO_ERROR;
  378. for( pszToken = strtok( pszArg, pszDelimiters ); // get first token
  379. pszToken != NULL && dwError == NO_ERROR;
  380. pszToken = strtok( NULL, pszDelimiters) // get next token
  381. ) {
  382. dwError = SpecialLsWorker(pUserData,
  383. fUseDataSocket,
  384. pszToken, // search path (no switches)
  385. TRUE, // show directories
  386. &lsb);
  387. if( TEST_UF( pUserData, OOB_DATA )) {
  388. break;
  389. }
  390. } // for
  391. }
  392. #endif
  393. if ( dwError == NO_ERROR) {
  394. // send all the remaining bytes in the buffer and then free memory.
  395. if ( lsb.QueryCB() != 0) {
  396. SOCKET sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() :
  397. pUserData->QueryControlSocket());
  398. dwError = SockSend(pUserData, sock,
  399. lsb.QueryBuffer(),
  400. lsb.QueryCB()/sizeof(CHAR));
  401. }
  402. lsb.FreeBuffer();
  403. }
  404. //
  405. // bring down the thread count when response is completed
  406. // TBD: Use Async send reponse()
  407. //
  408. AtqSetInfo( AtqDecMaxPoolThreads, 0);
  409. return ( dwError);
  410. } // SpecialLs()
  411. //
  412. // Private functions.
  413. //
  414. APIERR
  415. SimulateLsWorker(
  416. USER_DATA * pUserData,
  417. IN BOOL fUseDataSocket,
  418. IN CHAR * pszSearchPath,
  419. IN const LS_OPTIONS * pOptions,
  420. IN BOOL fSendHeader,
  421. IN BOOL fSendBlank
  422. )
  423. /*++
  424. Worker function for SimulateLs function, forms directory listing
  425. for requested directory, formats the directory listing and
  426. sends it to the client.
  427. Arguments:
  428. pUserData - The user initiating the request.
  429. pszSearchPath - Search directory, NULL = current dir.
  430. pOptions - LS options set by command line switches.
  431. fSendHeader - if TRUE send header with directory name in it.
  432. fSendBlank - also add a blank if there is one that has to be sent.
  433. Returns:
  434. APIERR - 0 if successful, !0 if not.
  435. HISTORY:
  436. MuraliK 24-Apr-1995 ReCreated.
  437. --*/
  438. {
  439. SOCKET sock;
  440. BOOL fLikeMsdos;
  441. CHAR szSearch[MAX_PATH];
  442. CHAR * pszFilePart;
  443. CHAR rgchLowFileName[MAX_PATH]; // used for lower casing filename
  444. BOOL fMapToLowerCase = FALSE;
  445. DWORD dwAccessMask = 0;
  446. BOOL fImpersonated = FALSE;
  447. LS_BUFFER lsb;
  448. LS_FORMAT_INFO lsfi; // required only for long formatting.
  449. BOOL fHasWildCards = FALSE;
  450. BOOL fHasTrailingDot = FALSE;
  451. DWORD dwError = NO_ERROR;
  452. APIERR serr = 0;
  453. TS_DIRECTORY_INFO tsDirInfo( pUserData->QueryInstance()->GetTsvcCache());
  454. IF_DEBUG( DIR_LIST) {
  455. DBGPRINTF((DBG_CONTEXT, " SimulateLsWorker( %08x, %d, %s)\n",
  456. pUserData, fUseDataSocket, pszSearchPath));
  457. }
  458. DBG_ASSERT( pUserData != NULL && pOptions != NULL);
  459. //
  460. // Check for emptiness of path or wildcards in search path.
  461. // We are only concerned about wild cards in user input. The reason
  462. // is all trailing '.' will be removed when we canonicalize
  463. // the path ( which user may not appreciate).
  464. //
  465. if ( IS_EMPTY_PATH(pszSearchPath)) {
  466. // we know pszSearchPath will not change the buffer!
  467. pszSearchPath = (char *) PSZ_DEFAULT_SEARCH_PATH;
  468. } else if (( pszSearchPath != NULL) &&
  469. ( strpbrk( pszSearchPath, PSZ_WILD_CHARACTERS ) != NULL)
  470. ){
  471. //
  472. // Search path contains wildcards.
  473. //
  474. fHasWildCards = TRUE;
  475. }
  476. //
  477. // Canonicalize the search path.
  478. //
  479. DWORD cbSize = MAX_PATH;
  480. dwError = pUserData->VirtualCanonicalize(szSearch, &cbSize,
  481. pszSearchPath,
  482. AccessTypeRead,
  483. &dwAccessMask);
  484. DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
  485. if( dwError == NO_ERROR ) {
  486. FTP_LS_FILTER_INFO fls; // required for generating directory listing
  487. PFN_CMP_WIN32_FIND_DATA pfnCompare;
  488. //
  489. // VirtualCanonicalize() when sanitizing the path removes
  490. // trailing dots from the path. Replace them here
  491. //
  492. DBG_ASSERT( !fHasWildCards || strlen(pszSearchPath) >= 1);
  493. if( fHasWildCards && (pszSearchPath[strlen(pszSearchPath)-1] == '.')) {
  494. DBG_ASSERT( strlen(szSearch) < MAX_PATH - 1);
  495. strcat( szSearch, "." );
  496. }
  497. //
  498. // Build the directory list.
  499. //
  500. pfnCompare = CompareRoutines[SORT_INDEX(pOptions->SortMethod,
  501. pOptions->fReverseSort)
  502. ];
  503. // Separate the filter out ( that is the last component)
  504. if (pUserData->ImpersonateUser() &&
  505. SeparateOutFilterSpec( szSearch, fHasWildCards, &fls.pszExpression)
  506. ) {
  507. fls.fFilterHidden = !pOptions->fShowAll;
  508. fls.fFilterSystem = !pOptions->fShowAll;
  509. fls.fFilterDotDot = !pOptions->fShowDotDot;
  510. fls.fRegExpression = ( fls.pszExpression != NULL && fHasWildCards);
  511. fls.fIgnoreCase = pUserData->QueryInstance()->QueryLowercaseFiles();
  512. dwError = GetDirectoryInfo(pUserData,
  513. &tsDirInfo,
  514. szSearch,
  515. &fls,
  516. pfnCompare);
  517. pUserData->RevertToSelf();
  518. } else {
  519. dwError = GetLastError();
  520. }
  521. }
  522. //
  523. // If there were any errors, tell them the bad news now.
  524. //
  525. if( dwError != NO_ERROR ) {
  526. return (dwError);
  527. }
  528. sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() :
  529. pUserData->QueryControlSocket());
  530. DBG_ASSERT( tsDirInfo.IsValid());
  531. int cDirEntries = tsDirInfo.QueryFilesCount();
  532. if ( cDirEntries > 0) {
  533. //
  534. // put out the header block before starting dir listing
  535. //
  536. if( fSendHeader ) {
  537. serr = SockPrintf2( pUserData, sock,
  538. "%s%s:",
  539. (fSendBlank)? "\r\n" : "", // send \r\n
  540. pszSearchPath);
  541. if ( serr != 0) {
  542. return (serr);
  543. }
  544. }
  545. }
  546. fLikeMsdos = (pOptions->lsStyle == LsStyleMsDos);
  547. lsfi.fFourDigitYear = pOptions->fFourDigitYear;
  548. if( !fLikeMsdos ) {
  549. //
  550. // Initialize the information in lsfi if we are doing
  551. // long format output.
  552. //
  553. if ( pOptions->OutputFormat == LsOutputLongFormat) {
  554. SYSTEMTIME timeNow;
  555. BOOL fUserRead, fUserWrite;
  556. //
  557. // Obtain the current time.
  558. // The Unix-like output requires current year
  559. //
  560. GetLocalTime( &timeNow );
  561. lsfi.wCurrentYear = timeNow.wYear;
  562. lsfi.hUserToken = TsTokenToImpHandle(pUserData->QueryUserToken());
  563. //
  564. // Since szSearch contains the complete path, we call
  565. // PathAccessCheck directly without resolving
  566. // from absolute to virtual
  567. //
  568. fUserRead = TEST_UF( pUserData, READ_ACCESS);
  569. fUserWrite = TEST_UF( pUserData, WRITE_ACCESS);
  570. lsfi.fVolumeReadable =
  571. PathAccessCheck(AccessTypeRead,
  572. dwAccessMask,
  573. fUserRead,
  574. fUserWrite);
  575. lsfi.fVolumeWritable =
  576. PathAccessCheck(AccessTypeWrite,
  577. dwAccessMask,
  578. fUserRead,
  579. fUserWrite);
  580. lsfi.pszPathPart = szSearch;
  581. lsfi.pszFileName = NULL;
  582. lsfi.pszDecorate = NULL;
  583. } // if ( long format output)
  584. //
  585. // We need to be impersonated only for UNIX-style listing.
  586. // For UNIX style listing, we make some NTsecurity queries
  587. // and they work only under the context of an impersonation.
  588. //
  589. if ( !(fImpersonated = pUserData->ImpersonateUser())) {
  590. dwError = GetLastError();
  591. }
  592. }
  593. //
  594. // Loop for each directory entry
  595. //
  596. if (dwError != NO_ERROR ||
  597. (dwError = lsb.AllocateBuffer( DEFAULT_LS_BUFFER_ALLOC_SIZE))
  598. != NO_ERROR) {
  599. IF_DEBUG(ERROR) {
  600. DBGPRINTF((DBG_CONTEXT,
  601. "Impersonation or Buffer allocation(%d bytes)",
  602. " failed.\n",
  603. DEFAULT_LS_BUFFER_ALLOC_SIZE));
  604. }
  605. if ( fImpersonated) {
  606. pUserData->RevertToSelf();
  607. }
  608. return (dwError);
  609. }
  610. //
  611. // Only map to lower case if not a remote drive AND the lower-case file
  612. // names flag is set AND this is not a case perserving file system.
  613. //
  614. if (*szSearch != '\\') {
  615. fMapToLowerCase = pUserData->QueryInstance()->QueryLowercaseFiles();
  616. }
  617. for( int idx = 0; serr == 0 && idx < cDirEntries; idx++) {
  618. const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx];
  619. DBG_ASSERT( pfdInfo != NULL);
  620. const CHAR * pszFileName = pfdInfo->cFileName;
  621. DWORD dwAttribs = pfdInfo->dwFileAttributes;
  622. //
  623. // Dump it.
  624. //
  625. // We may need to convert all filenames to lower case if so desired!!
  626. if( fMapToLowerCase ) {
  627. //
  628. // copy file name to local scratch and change the ptr pszFileName
  629. // because we cannot destroy pfdInfo->cFileName
  630. //
  631. strcpy( rgchLowFileName, pszFileName);
  632. CharLower( rgchLowFileName);
  633. pszFileName = rgchLowFileName;
  634. }
  635. IF_DEBUG( DIR_LIST) {
  636. DBGPRINTF((DBG_CONTEXT, "Dir list for %s\n",
  637. pszFileName));
  638. }
  639. //
  640. // Send the partial data obtained so far.
  641. // Use buffering to minimize number of sends occuring
  642. //
  643. if ( dwError == NO_ERROR) {
  644. if ( lsb.QueryRemainingCB() < MIN_LS_BUFFER_SIZE) {
  645. // send the bytes available in buffer and reset the buffer
  646. serr = SockSend(pUserData, sock,
  647. lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR));
  648. lsb.ResetAppendPtr();
  649. }
  650. } else {
  651. serr = dwError;
  652. }
  653. //
  654. // Check for socket errors on send or pending OOB data.
  655. //
  656. if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) )
  657. {
  658. break;
  659. }
  660. CHAR * pszDecorate = ( (pOptions->fDecorate && IS_DIR(dwAttribs) )
  661. ? "/" : "");
  662. if( pOptions->OutputFormat == LsOutputLongFormat )
  663. {
  664. FILETIME ftLocal;
  665. //
  666. // Long format output. Just send the file/dir info.
  667. //
  668. //
  669. // Map the file's last write time to (local) system time.
  670. //
  671. if ( !FileTimeToLocalFileTime(
  672. PickFileTime( pfdInfo, pOptions),
  673. &ftLocal) ||
  674. ! FileTimeToSystemTime(
  675. &ftLocal,
  676. &lsfi.stFile)
  677. ) {
  678. dwError = GetLastError();
  679. IF_DEBUG( ERROR) {
  680. DBGPRINTF(( DBG_CONTEXT,
  681. "Error in converting largeintger time %lu\n",
  682. dwError));
  683. }
  684. } else {
  685. lsfi.pszDecorate = pszDecorate;
  686. lsfi.pszFileName = pszFileName;
  687. if( fLikeMsdos ) {
  688. dwError = FormatFileInfoLikeMsdos(&lsb,
  689. pfdInfo,
  690. &lsfi);
  691. DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
  692. } else {
  693. dwError = FormatFileInfoLikeUnix(&lsb,
  694. pfdInfo,
  695. &lsfi);
  696. DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
  697. }
  698. }
  699. } else {
  700. //
  701. // Short format output.
  702. //
  703. DWORD cchSize = wsprintfA(lsb.QueryAppendPtr(), "%s%s\r\n",
  704. pszFileName, pszDecorate);
  705. lsb.IncrementCB( cchSize * sizeof(CHAR));
  706. }
  707. } // for()
  708. //
  709. // Get out of being impersonated.
  710. //
  711. if ( fImpersonated) {
  712. pUserData->RevertToSelf();
  713. }
  714. if ( dwError == NO_ERROR) {
  715. // send all the remaining bytes in the buffer and then free memory.
  716. if ( lsb.QueryCB() != 0) {
  717. serr = SockSend(pUserData, sock,
  718. lsb.QueryBuffer(), lsb.QueryCB()/sizeof(CHAR));
  719. }
  720. lsb.FreeBuffer();
  721. } else {
  722. return ( dwError); // an error has occured. stop processing
  723. }
  724. if( serr == 0 && !TEST_UF( pUserData, OOB_DATA) && pOptions->fRecursive )
  725. {
  726. //
  727. // The user want's a recursive directory search...
  728. //
  729. CHAR szOriginal[ MAX_PATH*2];
  730. CHAR * pszOriginalFilePart;
  731. // Obtain a copy of the path in the szOriginal so that we
  732. // can change it while recursively calling ourselves.
  733. if ( pszSearchPath == PSZ_DEFAULT_SEARCH_PATH) {
  734. // means that we had all files/dir of current directory.
  735. strcpy( szOriginal, fLikeMsdos ? ".\\" : "./" );
  736. } else {
  737. DBG_ASSERT( strlen(pszSearchPath) < MAX_PATH);
  738. strcpy( szOriginal, pszSearchPath );
  739. // strip off the wild cards if any present
  740. if( fHasWildCards )
  741. {
  742. CHAR * pszTmp;
  743. pszTmp = (PCHAR)_mbsrchr( (PUCHAR)szOriginal, '\\');
  744. pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, '/' );
  745. pszTmp = pszTmp ? pszTmp : strrchr( szOriginal, ':' );
  746. pszTmp = ( pszTmp) ? pszTmp+1 : szOriginal;
  747. *pszTmp = '\0';
  748. } else {
  749. CHAR ch;
  750. int cb = strlen( szOriginal);
  751. DBG_ASSERT( cb > 0);
  752. ch = *CharPrev( szOriginal, szOriginal + cb );
  753. if( !IS_PATH_SEP( ch ) ) {
  754. // to add "/"
  755. DBG_ASSERT( strlen( szOriginal) + 2 < MAX_PATH);
  756. strcat( szOriginal, fLikeMsdos ? "\\" : "/" );
  757. }
  758. }
  759. }
  760. pszOriginalFilePart = szOriginal + strlen(szOriginal);
  761. DBG_ASSERT( tsDirInfo.IsValid());
  762. DBG_ASSERT( cDirEntries == tsDirInfo.QueryFilesCount());
  763. for( int idx = 0; serr == 0 && idx < cDirEntries; idx++) {
  764. const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx];
  765. DBG_ASSERT( pfdInfo != NULL);
  766. const char * pszFileName = pfdInfo->cFileName;
  767. DWORD dwAttribs = pfdInfo->dwFileAttributes;
  768. //
  769. // Filter out non-directories.
  770. //
  771. if( !IS_DIR( dwAttribs) ) {
  772. continue;
  773. }
  774. //
  775. // Dump it.
  776. //
  777. DBG_ASSERT( strlen( pszOriginalFilePart) + strlen(pszFileName)
  778. < MAX_PATH * 2);
  779. strcpy( pszOriginalFilePart, pszFileName );
  780. serr = SimulateLsWorker(pUserData,
  781. fUseDataSocket,
  782. szOriginal,
  783. pOptions,
  784. TRUE, TRUE);
  785. //
  786. // Check for socket errors on send or pending OOB data.
  787. //
  788. if( TEST_UF( pUserData, OOB_DATA ) || ( serr != 0 ) )
  789. {
  790. break;
  791. }
  792. } // for( directory looping)
  793. } // if ( fRecursive)
  794. // At the end of directory listing. Return back.
  795. IF_DEBUG( DIR_LIST) {
  796. DBGPRINTF((DBG_CONTEXT,
  797. "SimulateLsWorker() for User %08x, Dir %s returns %d\n",
  798. pUserData, pszSearchPath, serr));
  799. }
  800. return serr;
  801. } // SimulateLsWorker()
  802. APIERR
  803. SpecialLsWorker(
  804. USER_DATA * pUserData,
  805. IN BOOL fUseDataSocket,
  806. CHAR * pszSearchPath,
  807. BOOL fShowDirectories,
  808. IN OUT LS_BUFFER * plsb
  809. )
  810. /*++
  811. This is the worker function for Special Ls function. It is similar to
  812. the the SimulateLsWorker, only in that it shows directory if the
  813. fShowDirectories flag is set.
  814. The reason for this comes from a special FTP command which inquires about
  815. all the files in the first level and second level of current directory,
  816. which is not a recursive listing at all. This function when it recursively
  817. calls itself, always sets the fShowDirectories as FALSE.
  818. Arguments:
  819. pUserData pointer to user data object that initiated the request.
  820. fUseDataSocket if TRUE use DataSocket of UserData else
  821. use the control socket of UserData.
  822. pszSearchPath pointer to null-terminated string for requested directory.
  823. NULL means use current directory.
  824. fShowDirectories only show directories if TRUE.
  825. plsb pointer to buffer to accumulate the data generated and send
  826. it out in a single bulk.
  827. Returns:
  828. APIERR 0 if successful.
  829. History:
  830. KeithMo 17-Mar-1993 Created.
  831. MuraliK 26-Apr-1995 ReCreated to use new way of generation.
  832. --*/
  833. {
  834. CHAR chSeparator;
  835. CHAR * pszRecurse;
  836. SOCKET sock;
  837. BOOL fHasWildCards = FALSE;
  838. DWORD dwError = NO_ERROR;
  839. TS_DIRECTORY_INFO tsDirInfo( pUserData->QueryInstance()->GetTsvcCache());
  840. CHAR szSearch[MAX_PATH];
  841. CHAR szRecurse[MAX_PATH];
  842. BOOL fMapToLowerCase = FALSE;
  843. CHAR rgchLowFileName[MAX_PATH]; // used for lower casing filename
  844. BOOL fHadOneComponent = FALSE;
  845. DBG_ASSERT( pUserData != NULL);
  846. IF_DEBUG( DIR_LIST) {
  847. DBGPRINTF((DBG_CONTEXT,
  848. "Entering SpecialLsWorker( %08x, %s)\n",
  849. pUserData, pszSearchPath));
  850. }
  851. chSeparator = TEST_UF( pUserData, MSDOS_DIR_OUTPUT ) ? '\\' : '/';
  852. //
  853. // Check for wildcards in search path.
  854. //
  855. if( ( pszSearchPath != NULL ) && ( *pszSearchPath != '\0' ) )
  856. {
  857. //
  858. // Setup for recursive directory search. We'll set things up
  859. // so we can strcpy a new directory to pszRecurse, then
  860. // recursively call ourselves with szRecurse as the search
  861. // path.
  862. //
  863. // We also use szRecurse as a "prefix" to display before each
  864. // file/directory. The FTP Client software needs this for the
  865. // MDEL & MGET commands.
  866. //
  867. if ( strlen(pszSearchPath) >= MAX_PATH -1 )
  868. {
  869. return ERROR_BUFFER_OVERFLOW;
  870. }
  871. strcpy( szRecurse, pszSearchPath );
  872. // get slash.
  873. pszRecurse = (PCHAR)_mbsrchr( (PUCHAR)szRecurse, '\\');
  874. pszRecurse = ((pszRecurse == NULL) ?
  875. strrchr( szRecurse, '/') : pszRecurse);
  876. fHadOneComponent = (pszRecurse == NULL);
  877. if( strpbrk( szRecurse, PSZ_WILD_CHARACTERS) != NULL )
  878. {
  879. //
  880. // Search path contains wildcards.
  881. //
  882. fHasWildCards = TRUE;
  883. // we do not care about components when wild card is present
  884. fHadOneComponent = FALSE;
  885. //
  886. // Strip the wildcard pattern from the search path.
  887. // look for both kind of slashes ( since precanonicalized)
  888. //
  889. //
  890. // If we found right-most dir component, skip path separator
  891. // else set it to start of search path.
  892. //
  893. pszRecurse = ( ( pszRecurse != NULL)
  894. ? pszRecurse + 1
  895. : szRecurse
  896. );
  897. } else {
  898. //
  899. // No wildcards, so the argument must be a path.
  900. // Ensure it is terminated with a path separator.
  901. //
  902. pszRecurse = CharPrev( szRecurse, szRecurse + strlen(szRecurse) );
  903. if( !IS_PATH_SEP( *pszRecurse ) )
  904. {
  905. *++pszRecurse = chSeparator;
  906. }
  907. pszRecurse++; // skip the path separator
  908. }
  909. } else {
  910. //
  911. // No arguments.
  912. //
  913. pszRecurse = szRecurse;
  914. //
  915. // Munge the arguments around a bit. NULL = *.* in current
  916. // directory. If the user specified a directory (like d:\foo)
  917. // then append *.*.
  918. //
  919. pszSearchPath = (char *) PSZ_DEFAULT_SEARCH_PATH;
  920. }
  921. *pszRecurse = '\0';
  922. //
  923. // Canonicalize the search path.
  924. //
  925. DWORD cbSize = MAX_PATH;
  926. dwError = pUserData->VirtualCanonicalize(szSearch, &cbSize,
  927. pszSearchPath,
  928. AccessTypeRead);
  929. DBG_ASSERT( dwError != ERROR_INSUFFICIENT_BUFFER);
  930. if( dwError == NO_ERROR ) {
  931. FTP_LS_FILTER_INFO fls; // required for generating directory listing
  932. LPCSTR pszFilter = NULL;
  933. //
  934. // VirtualCanonicalize() when sanitizing the path removes
  935. // trailing dots from the path. Replace them here
  936. //
  937. if( fHasWildCards && (pszSearchPath[strlen(pszSearchPath)-1] == '.')) {
  938. strcat( szSearch, "." );
  939. }
  940. //
  941. // Build the directory list.
  942. //
  943. if (pUserData->ImpersonateUser() &&
  944. SeparateOutFilterSpec( szSearch, fHasWildCards, &fls.pszExpression)
  945. ) {
  946. fls.fFilterHidden = TRUE;
  947. fls.fFilterSystem = TRUE;
  948. fls.fFilterDotDot = TRUE;
  949. fls.fRegExpression = ( fls.pszExpression != NULL && fHasWildCards);
  950. fls.fIgnoreCase = pUserData->QueryInstance()->QueryLowercaseFiles();
  951. dwError = GetDirectoryInfo(pUserData,
  952. &tsDirInfo,
  953. szSearch,
  954. &fls,
  955. NULL); // unsorted list
  956. pUserData->RevertToSelf();
  957. } else {
  958. dwError = GetLastError();
  959. }
  960. }
  961. //
  962. // If there were any errors, tell them the bad news now.
  963. //
  964. if( dwError != NO_ERROR ) {
  965. return ( dwError);
  966. }
  967. if ( fHadOneComponent) {
  968. // HARD CODE! Spend some time and understand this....
  969. //
  970. // Adjust the szRecurse buffer to contain appropriate path
  971. // such that in presence of one component we generate proper
  972. // result.
  973. //
  974. // the given path is either invalid or non-directory
  975. // so reset the string stored in szRecurse.
  976. szRecurse[0] = '\0';
  977. pszRecurse = szRecurse;
  978. }
  979. //
  980. // Only map to lower case if not a remote drive AND the lower-case file
  981. // names flag is set AND this is not a case perserving file system.
  982. //
  983. if (*szSearch != '\\') {
  984. fMapToLowerCase = pUserData->QueryInstance()->QueryLowercaseFiles();
  985. }
  986. //
  987. // Loop until we're out of files to find.
  988. //
  989. sock = ((fUseDataSocket) ? pUserData->QueryDataSocket() :
  990. pUserData->QueryControlSocket());
  991. int cDirEntries = tsDirInfo.QueryFilesCount();
  992. for( int idx = 0; dwError == NO_ERROR && idx < cDirEntries; idx++) {
  993. const WIN32_FIND_DATA * pfdInfo = tsDirInfo[idx];
  994. DBG_ASSERT( pfdInfo != NULL);
  995. const CHAR * pszFileName = pfdInfo->cFileName;
  996. DWORD dwAttribs = pfdInfo->dwFileAttributes;
  997. if ( !fShowDirectories && IS_DIR( dwAttribs)) {
  998. continue;
  999. }
  1000. //
  1001. // Dump it.
  1002. //
  1003. // We may need to convert all filenames to lower case if so desired!!
  1004. if( fMapToLowerCase ) {
  1005. //
  1006. // copy file name to local scratch and change the ptr pszFileName
  1007. // because we cannot destroy pfdInfo->cFileName
  1008. //
  1009. strcpy( rgchLowFileName, pszFileName);
  1010. CharLower( rgchLowFileName );
  1011. pszFileName = rgchLowFileName;
  1012. }
  1013. //
  1014. // Send the partial data obtained so far.
  1015. // Use buffering to minimize number of sends occuring
  1016. //
  1017. if ( dwError == NO_ERROR) {
  1018. if ( plsb->QueryRemainingCB() < MIN_LS_BUFFER_SIZE) {
  1019. // send the bytes available in buffer and reset the buffer
  1020. dwError = SockSend(pUserData, sock,
  1021. plsb->QueryBuffer(),
  1022. plsb->QueryCB()/sizeof(CHAR));
  1023. plsb->ResetAppendPtr();
  1024. }
  1025. }
  1026. //
  1027. // Test for aborted directory listing or socket error.
  1028. //
  1029. if( TEST_UF( pUserData, OOB_DATA ) || ( dwError != NO_ERROR ) )
  1030. {
  1031. break;
  1032. }
  1033. //
  1034. // If no wildcards were given, then just dump out the
  1035. // file/directory. If wildcards were given, AND this
  1036. // is a directory, then recurse (one level only) into
  1037. // the directory. The mere fact that we don't append
  1038. // any wildcards to the recursed search path will
  1039. // prevent a full depth-first recursion of the file system.
  1040. //
  1041. if( fHasWildCards && IS_DIR(dwAttribs) ) {
  1042. DBG_ASSERT(strcmp( pszFileName, "." ) != 0);
  1043. DBG_ASSERT(strcmp( pszFileName, "..") != 0);
  1044. DBG_ASSERT(strlen(szRecurse)+strlen( pszFileName) < MAX_PATH);
  1045. strcpy( pszRecurse, pszFileName );
  1046. strcat( pszRecurse, "/"); // indicating this is a directory
  1047. dwError = SpecialLsWorker(pUserData,
  1048. fUseDataSocket,
  1049. szRecurse,
  1050. FALSE,
  1051. plsb);
  1052. } else {
  1053. DWORD cchSize;
  1054. *pszRecurse = '\0'; // as a side effect this terminates szRecurse.
  1055. //
  1056. // Short format output.
  1057. //
  1058. cchSize = wsprintfA(plsb->QueryAppendPtr(),
  1059. "%s%s\r\n",
  1060. szRecurse,
  1061. pszFileName);
  1062. plsb->IncrementCB( cchSize*sizeof(CHAR));
  1063. }
  1064. } // for
  1065. IF_DEBUG( DIR_LIST) {
  1066. DBGPRINTF((DBG_CONTEXT,
  1067. "Leaving SpecialLsWorker() with Error = %d\n",
  1068. dwError));
  1069. }
  1070. return (dwError);
  1071. } // SpecialLsWorker()
  1072. /**************************************************
  1073. * Formatting functions.
  1074. **************************************************/
  1075. DWORD
  1076. FormatFileInfoLikeMsdos(
  1077. IN OUT LS_BUFFER * plsb,
  1078. IN const WIN32_FIND_DATA * pfdInfo,
  1079. IN const LS_FORMAT_INFO * pFormatInfo
  1080. )
  1081. /*++
  1082. Forms an MSDOS like directory entry for the given dir info object.
  1083. Arguments:
  1084. plsb pointer to buffer into which the dir line is generated.
  1085. pfdInfo pointer to dir information element.
  1086. pFormatInfo pointer to information required for formatting.
  1087. ( use the file name in pFormatInfo, becauze it may have been
  1088. made into lower case if necessary)
  1089. Returns:
  1090. Win32 error code and NO_ERROR on success.
  1091. History:
  1092. MuraliK 25-Apr-1995
  1093. --*/
  1094. {
  1095. DWORD dwError = NO_ERROR;
  1096. CHAR szSizeOrDir[32];
  1097. BOOL fDir;
  1098. DWORD cbReqd;
  1099. DBG_ASSERT(plsb != NULL && pfdInfo != NULL && pFormatInfo != NULL);
  1100. if ( IS_DIR( pfdInfo->dwFileAttributes)) {
  1101. strcpy( szSizeOrDir, "<DIR> " );
  1102. } else {
  1103. LARGE_INTEGER li;
  1104. li.HighPart = pfdInfo->nFileSizeHigh;
  1105. li.LowPart = pfdInfo->nFileSizeLow;
  1106. IsLargeIntegerToDecimalChar( &li, szSizeOrDir);
  1107. }
  1108. DBG_ASSERT( strlen(szSizeOrDir) <= 20);
  1109. cbReqd = ( 10 // size for the date field
  1110. + 10 // size for time field
  1111. + 20 // space for size/dir
  1112. + strlen( pFormatInfo->pszFileName)
  1113. + 8 // addl space + decoration ...
  1114. ) * sizeof(CHAR);
  1115. DBG_ASSERT( cbReqd <= MIN_LS_BUFFER_SIZE);
  1116. if ( cbReqd < plsb->QueryRemainingCB()) {
  1117. register const SYSTEMTIME * pst = &pFormatInfo->stFile;
  1118. WORD wHour;
  1119. char * pszAmPm;
  1120. DWORD cchUsed;
  1121. wHour = pst->wHour;
  1122. pszAmPm = ( wHour < 12 ) ? "AM" : "PM";
  1123. if ( wHour == 0 ) { wHour = 12; }
  1124. else if ( wHour > 12) { wHour -= 12; }
  1125. if (pFormatInfo->fFourDigitYear) {
  1126. cchUsed = wsprintfA(plsb->QueryAppendPtr(),
  1127. "%02u-%02u-%04u %02u:%02u%s %20s %s%s\r\n",
  1128. pst->wMonth,
  1129. pst->wDay,
  1130. pst->wYear,
  1131. wHour,
  1132. pst->wMinute,
  1133. pszAmPm,
  1134. szSizeOrDir,
  1135. pFormatInfo->pszFileName,
  1136. pFormatInfo->pszDecorate);
  1137. }
  1138. else {
  1139. cchUsed = wsprintfA(plsb->QueryAppendPtr(),
  1140. "%02u-%02u-%02u %02u:%02u%s %20s %s%s\r\n",
  1141. pst->wMonth,
  1142. pst->wDay,
  1143. pst->wYear%100, //instead of wYear - 1900
  1144. wHour,
  1145. pst->wMinute,
  1146. pszAmPm,
  1147. szSizeOrDir,
  1148. pFormatInfo->pszFileName,
  1149. pFormatInfo->pszDecorate);
  1150. }
  1151. DBG_ASSERT( cchUsed * sizeof(CHAR) <= cbReqd);
  1152. plsb->IncrementCB(cchUsed * sizeof(CHAR));
  1153. } else {
  1154. dwError = ERROR_INSUFFICIENT_BUFFER;
  1155. }
  1156. return ( dwError);
  1157. } // FormatFileInfoLikeMsdos()
  1158. DWORD
  1159. FormatFileInfoLikeUnix(
  1160. IN OUT LS_BUFFER * plsb,
  1161. IN const WIN32_FIND_DATA * pfdInfo,
  1162. IN const LS_FORMAT_INFO * pFormatInfo
  1163. )
  1164. /*++
  1165. This function formats file information for a UNIX stle client.
  1166. Arguments:
  1167. plsb pointer to buffer into which the dir line is generated.
  1168. pfdInfo pointer to dir information element.
  1169. pFormatInfo pointer to information required for long formatting.
  1170. Returns:
  1171. Win32 error code and NO_ERROR on success.
  1172. History:
  1173. MuraliK 25-Apr-1995
  1174. --*/
  1175. {
  1176. DWORD dwError = NO_ERROR;
  1177. CHAR * pszFileOwner;
  1178. CHAR * pszFileGroup;
  1179. const SYSTEMTIME * pst;
  1180. DWORD dwMode;
  1181. DWORD cLinks;
  1182. NTSTATUS status;
  1183. LARGE_INTEGER li;
  1184. CHAR attrib[4];
  1185. CHAR szTimeOrYear[12];
  1186. CHAR szSize[32];
  1187. DWORD cbReqd;
  1188. static CHAR * apszMonths[] = { " ", "Jan", "Feb", "Mar", "Apr",
  1189. "May", "Jun", "Jul", "Aug", "Sep",
  1190. "Oct", "Nov", "Dec" };
  1191. DBG_ASSERT( plsb != NULL);
  1192. DBG_ASSERT( pFormatInfo != NULL );
  1193. DBG_ASSERT( pFormatInfo->hUserToken != NULL );
  1194. DBG_ASSERT( pFormatInfo->pszPathPart != NULL );
  1195. DBG_ASSERT( pfdInfo != NULL );
  1196. //
  1197. // Build the attribute triple. Note that we only build one,
  1198. // and replicate it three times for the owner/group/other fields.
  1199. //
  1200. dwMode = ComputeModeBits( pFormatInfo->hUserToken,
  1201. pFormatInfo->pszPathPart,
  1202. pfdInfo,
  1203. &cLinks,
  1204. pFormatInfo->fVolumeReadable,
  1205. pFormatInfo->fVolumeWritable );
  1206. attrib[0] = ( dwMode & FILE_MODE_R ) ? 'r' : '-';
  1207. attrib[1] = ( dwMode & FILE_MODE_W ) ? 'w' : '-';
  1208. attrib[2] = ( dwMode & FILE_MODE_X ) ? 'x' : '-';
  1209. attrib[3] = '\0';
  1210. pst = &pFormatInfo->stFile;
  1211. // NYI: can we make the following a single wsprintf call ??
  1212. if( pst->wYear == pFormatInfo->wCurrentYear ) {
  1213. //
  1214. // The file's year matches the current year, so
  1215. // display the hour & minute of the last write.
  1216. //
  1217. wsprintfA( szTimeOrYear, "%2u:%02u", pst->wHour, pst->wMinute );
  1218. } else {
  1219. //
  1220. // The file's year does not match the current
  1221. // year, so display the year of the last write.
  1222. //
  1223. wsprintfA( szTimeOrYear, "%4u", pst->wYear );
  1224. }
  1225. //
  1226. // CODEWORK: How expensive would it be do
  1227. // get the proper owner & group names?
  1228. //
  1229. pszFileOwner = "owner";
  1230. pszFileGroup = "group";
  1231. //
  1232. // Get the size in a displayable form.
  1233. //
  1234. li.HighPart = pfdInfo->nFileSizeHigh;
  1235. li.LowPart = pfdInfo->nFileSizeLow;
  1236. IsLargeIntegerToDecimalChar( &li, szSize);
  1237. //
  1238. // Dump it.
  1239. //
  1240. DBG_ASSERT( strlen(szSize) <= 12);
  1241. cbReqd = ( 3*strlen(attrib) + strlen( pszFileOwner)
  1242. + strlen( pszFileGroup) + 12 + 20 // date
  1243. + strlen( pFormatInfo->pszFileName)
  1244. + strlen( pFormatInfo->pszDecorate) + 20 // 20 for spaces etc.
  1245. ) * sizeof(CHAR);
  1246. DBG_ASSERT( cbReqd < MIN_LS_BUFFER_SIZE);
  1247. if ( cbReqd < plsb->QueryRemainingCB()) {
  1248. DWORD cchUsed = wsprintfA( plsb->QueryAppendPtr(),
  1249. "%c%s%s%s %3lu %-8s %-8s %12s %s %2u %5s %s%s\r\n",
  1250. (IS_DIR(pfdInfo->dwFileAttributes) ? 'd' : '-'),
  1251. attrib,
  1252. attrib,
  1253. attrib,
  1254. cLinks,
  1255. pszFileOwner,
  1256. pszFileGroup,
  1257. szSize,
  1258. apszMonths[pst->wMonth],
  1259. pst->wDay,
  1260. szTimeOrYear,
  1261. pFormatInfo->pszFileName,
  1262. pFormatInfo->pszDecorate);
  1263. DBG_ASSERT( cchUsed * sizeof(CHAR) <= cbReqd);
  1264. plsb->IncrementCB( cchUsed*sizeof(CHAR));
  1265. } else {
  1266. dwError = ERROR_INSUFFICIENT_BUFFER;
  1267. }
  1268. return ( dwError);
  1269. } // FormatFileInfoLikeUnix()
  1270. /************************ End of File ************************/