Leaked source code of windows server 2003
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

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