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.

785 lines
19 KiB

  1. /*++
  2. Copyright (c) 1998 Microsoft Corporation
  3. Module Name:
  4. expand.c
  5. Abstract:
  6. This module implements the file expand command.
  7. Author:
  8. Mike Sliger (msliger) 29-Apr-1999
  9. Revision History:
  10. --*/
  11. #include "cmdcons.h"
  12. #pragma hdrstop
  13. //
  14. // structure tunneled thru SpExpandFile to carry info to/from callback
  15. //
  16. typedef struct {
  17. LPWSTR FileSpec;
  18. BOOLEAN DisplayFiles;
  19. BOOLEAN MatchedAnyFiles;
  20. ULONG NumberOfFilesDone;
  21. BOOLEAN UserAborted;
  22. BOOLEAN OverwriteExisting;
  23. } EXPAND_CONTEXT;
  24. BOOLEAN
  25. pRcCheckForBreak( VOID );
  26. EXPAND_CALLBACK_RESULT
  27. pRcExpandCallback(
  28. EXPAND_CALLBACK_MESSAGE Message,
  29. PWSTR FileName,
  30. PLARGE_INTEGER FileSize,
  31. PLARGE_INTEGER FileTime,
  32. ULONG FileAttributes,
  33. PVOID UserData
  34. );
  35. BOOL
  36. pRcPatternMatch(
  37. LPWSTR pszString,
  38. LPWSTR pszPattern,
  39. IN BOOL fImplyDotAtEnd
  40. );
  41. ULONG
  42. RcCmdExpand(
  43. IN PTOKENIZED_LINE TokenizedLine
  44. )
  45. {
  46. PLINE_TOKEN Token;
  47. LPWSTR Arg;
  48. LPWSTR SrcFile = NULL;
  49. LPWSTR DstFile = NULL;
  50. LPWSTR FileSpec = NULL;
  51. LPWSTR SrcNtFile = NULL;
  52. LPWSTR DstNtPath = NULL;
  53. LPWSTR s;
  54. IO_STATUS_BLOCK IoStatusBlock;
  55. UNICODE_STRING UnicodeString;
  56. HANDLE Handle;
  57. OBJECT_ATTRIBUTES Obja;
  58. NTSTATUS Status;
  59. LPWSTR YesNo;
  60. WCHAR Text[3];
  61. IO_STATUS_BLOCK status_block;
  62. FILE_BASIC_INFORMATION fileInfo;
  63. WCHAR * pos;
  64. ULONG CopyFlags = 0;
  65. BOOLEAN DisplayFileList = FALSE;
  66. BOOLEAN OverwriteExisting = NoCopyPrompt;
  67. EXPAND_CONTEXT Context;
  68. ASSERT(TokenizedLine->TokenCount >= 1);
  69. if (RcCmdParseHelp( TokenizedLine, MSG_EXPAND_HELP )) {
  70. goto exit;
  71. }
  72. //
  73. // Parse command line
  74. //
  75. for( Token = TokenizedLine->Tokens->Next;
  76. Token != NULL;
  77. Token = Token->Next ) {
  78. Arg = Token->String;
  79. if(( Arg[0] == L'-' ) || ( Arg[0] == L'/' )) {
  80. switch( Arg[1] ) {
  81. case L'F':
  82. case L'f':
  83. if(( Arg[2] == L':' ) && ( FileSpec == NULL )) {
  84. FileSpec = &Arg[3];
  85. } else {
  86. RcMessageOut(MSG_SYNTAX_ERROR);
  87. goto exit;
  88. }
  89. break;
  90. case L'D':
  91. case L'd':
  92. if ( Arg[2] == L'\0' ) {
  93. DisplayFileList = TRUE;
  94. } else {
  95. RcMessageOut(MSG_SYNTAX_ERROR);
  96. goto exit;
  97. }
  98. break;
  99. case L'Y':
  100. case L'y':
  101. if ( Arg[2] == L'\0' ) {
  102. OverwriteExisting = TRUE;
  103. } else {
  104. RcMessageOut(MSG_SYNTAX_ERROR);
  105. goto exit;
  106. }
  107. break;
  108. default:
  109. RcMessageOut(MSG_SYNTAX_ERROR);
  110. goto exit;
  111. }
  112. } else if( SrcFile == NULL ) {
  113. SrcFile = Arg;
  114. } else if( DstFile == NULL ) {
  115. DstFile = SpDupStringW( Arg );
  116. } else {
  117. RcMessageOut(MSG_SYNTAX_ERROR);
  118. goto exit;
  119. }
  120. }
  121. if(( SrcFile == NULL ) ||
  122. (( DstFile != NULL ) && ( DisplayFileList == TRUE ))) {
  123. RcMessageOut(MSG_SYNTAX_ERROR);
  124. goto exit;
  125. }
  126. if ( RcDoesPathHaveWildCards( SrcFile )) {
  127. RcMessageOut(MSG_DIR_WILDCARD_NOT_SUPPORTED);
  128. goto exit;
  129. }
  130. //
  131. // Translate the source name to the NT namespace
  132. //
  133. if (!RcFormFullPath( SrcFile, _CmdConsBlock->TemporaryBuffer, TRUE )) {
  134. RcMessageOut(MSG_INVALID_PATH);
  135. goto exit;
  136. }
  137. SrcNtFile = SpDupStringW( _CmdConsBlock->TemporaryBuffer );
  138. if ( !DisplayFileList ) {
  139. //
  140. // Create a destination path name when the user did not
  141. // provide one. We use the current drive and directory.
  142. //
  143. if( DstFile == NULL ) {
  144. RcGetCurrentDriveAndDir( _CmdConsBlock->TemporaryBuffer );
  145. DstFile = SpDupStringW( _CmdConsBlock->TemporaryBuffer );
  146. }
  147. //
  148. // create the destination paths
  149. //
  150. if (!RcFormFullPath( DstFile, _CmdConsBlock->TemporaryBuffer, FALSE )) {
  151. RcMessageOut(MSG_INVALID_PATH);
  152. goto exit;
  153. }
  154. if (!RcIsPathNameAllowed(_CmdConsBlock->TemporaryBuffer,FALSE,FALSE)) {
  155. RcMessageOut(MSG_ACCESS_DENIED);
  156. goto exit;
  157. }
  158. if (!RcFormFullPath( DstFile, _CmdConsBlock->TemporaryBuffer, TRUE )) {
  159. RcMessageOut(MSG_INVALID_PATH);
  160. goto exit;
  161. }
  162. DstNtPath = SpDupStringW( _CmdConsBlock->TemporaryBuffer );
  163. //
  164. // check for removable media
  165. //
  166. if (AllowRemovableMedia == FALSE && RcIsFileOnRemovableMedia(DstNtPath) == STATUS_SUCCESS) {
  167. RcMessageOut(MSG_ACCESS_DENIED);
  168. goto exit;
  169. }
  170. }
  171. //
  172. // setup context for callbacks
  173. //
  174. RtlZeroMemory(&Context, sizeof(Context));
  175. Context.FileSpec = FileSpec;
  176. Context.DisplayFiles = DisplayFileList;
  177. Context.OverwriteExisting = OverwriteExisting;
  178. if ( DisplayFileList ) {
  179. pRcEnableMoreMode();
  180. }
  181. Status = SpExpandFile( SrcNtFile, DstNtPath, pRcExpandCallback, &Context );
  182. pRcDisableMoreMode();
  183. if( !NT_SUCCESS(Status) && !Context.UserAborted ) {
  184. RcNtError( Status, MSG_CANT_EXPAND_FILE );
  185. } else if (( Context.NumberOfFilesDone == 0 ) &&
  186. ( Context.MatchedAnyFiles == FALSE ) &&
  187. ( Context.FileSpec != NULL )) {
  188. RcMessageOut( MSG_EXPAND_NO_MATCH, Context.FileSpec, SrcFile );
  189. }
  190. if ( Context.MatchedAnyFiles ) {
  191. if ( DisplayFileList ) {
  192. RcMessageOut( MSG_EXPAND_SHOWN, Context.NumberOfFilesDone );
  193. } else {
  194. RcMessageOut( MSG_EXPAND_COUNT, Context.NumberOfFilesDone );
  195. }
  196. }
  197. exit:
  198. if( SrcNtFile ) {
  199. SpMemFree( SrcNtFile );
  200. }
  201. if( DstFile ) {
  202. SpMemFree( DstFile );
  203. }
  204. if( DstNtPath ) {
  205. SpMemFree( DstNtPath );
  206. }
  207. return 1;
  208. }
  209. EXPAND_CALLBACK_RESULT
  210. pRcExpandCallback(
  211. EXPAND_CALLBACK_MESSAGE Message,
  212. PWSTR FileName,
  213. PLARGE_INTEGER FileSize,
  214. PLARGE_INTEGER FileTime,
  215. ULONG FileAttributes,
  216. PVOID UserData
  217. )
  218. {
  219. EXPAND_CONTEXT * Context = (EXPAND_CONTEXT * ) UserData;
  220. LPWSTR YesNo;
  221. EXPAND_CALLBACK_RESULT rc;
  222. WCHAR Text[3];
  223. switch ( Message )
  224. {
  225. case EXPAND_COPY_FILE:
  226. //
  227. // Watch for Ctl-C or ESC while processing
  228. //
  229. if ( pRcCheckForBreak() ) {
  230. Context->UserAborted = TRUE;
  231. return( EXPAND_ABORT );
  232. }
  233. //
  234. // See if filename matches filespec pattern, if any
  235. //
  236. if ( Context->FileSpec != NULL ) {
  237. //
  238. // To be "*.*"-friendly, we need to know if there is a real
  239. // dot in the last element of the string to be matched
  240. //
  241. BOOL fAllowImpliedDot = TRUE;
  242. LPWSTR p;
  243. for ( p = FileName; *p != L'\0'; p++ ) {
  244. if ( *p == L'.' ) {
  245. fAllowImpliedDot = FALSE;
  246. } else if ( *p == L'\\' ) {
  247. fAllowImpliedDot = TRUE;
  248. }
  249. }
  250. if ( !pRcPatternMatch( FileName,
  251. Context->FileSpec,
  252. fAllowImpliedDot )) {
  253. //
  254. // File doesn't match given spec: skip it
  255. //
  256. return( EXPAND_SKIP_THIS_FILE );
  257. }
  258. }
  259. Context->MatchedAnyFiles = TRUE; // don't report "no matches"
  260. if ( Context->DisplayFiles ) {
  261. //
  262. // We're just listing file names, and we must do it now, because
  263. // we're going to tell ExpandFile to skip this one, so this will
  264. // be the last we here about it.
  265. //
  266. WCHAR LineOut[50];
  267. WCHAR *p;
  268. //
  269. // Format the date and time, which go first.
  270. //
  271. RcFormatDateTime(FileTime,LineOut);
  272. RcTextOut(LineOut);
  273. //
  274. // 2 spaces for separation
  275. //
  276. RcTextOut(L" ");
  277. //
  278. // File attributes.
  279. //
  280. p = LineOut;
  281. *p++ = L'-';
  282. if(FileAttributes & FILE_ATTRIBUTE_ARCHIVE) {
  283. *p++ = L'a';
  284. } else {
  285. *p++ = L'-';
  286. }
  287. if(FileAttributes & FILE_ATTRIBUTE_READONLY) {
  288. *p++ = L'r';
  289. } else {
  290. *p++ = L'-';
  291. }
  292. if(FileAttributes & FILE_ATTRIBUTE_HIDDEN) {
  293. *p++ = L'h';
  294. } else {
  295. *p++ = L'-';
  296. }
  297. if(FileAttributes & FILE_ATTRIBUTE_SYSTEM) {
  298. *p++ = L's';
  299. } else {
  300. *p++ = L'-';
  301. }
  302. *p++ = L'-';
  303. *p++ = L'-';
  304. *p++ = L'-';
  305. *p = 0;
  306. RcTextOut(LineOut);
  307. //
  308. // 2 spaces for separation
  309. //
  310. RcTextOut(L" ");
  311. //
  312. // Now, put the size in there. Right justified and space padded
  313. // up to 8 chars. Otherwise unjustified or padded.
  314. //
  315. RcFormat64BitIntForOutput(FileSize->QuadPart,LineOut,TRUE);
  316. if(FileSize->QuadPart > 99999999i64) {
  317. RcTextOut(LineOut);
  318. } else {
  319. RcTextOut(LineOut+11); // outputs 8 chars
  320. }
  321. RcTextOut(L" ");
  322. //
  323. // Finally, put the filename on the line.
  324. //
  325. if( !RcTextOut( FileName ) || !RcTextOut( L"\r\n" )) {
  326. Context->UserAborted = TRUE;
  327. return( EXPAND_ABORT ); /* user aborted display output */
  328. }
  329. Context->NumberOfFilesDone++;
  330. return( EXPAND_SKIP_THIS_FILE );
  331. } // end if DisplayFiles
  332. //
  333. // This file qualified, and we're not just displaying, so tell
  334. // ExpandFile to do it.
  335. //
  336. return( EXPAND_COPY_THIS_FILE );
  337. case EXPAND_COPIED_FILE:
  338. //
  339. // Notification that a file has been copied successfully.
  340. //
  341. RcMessageOut( MSG_EXPANDED, FileName);
  342. Context->NumberOfFilesDone++;
  343. return( EXPAND_NO_ERROR );
  344. case EXPAND_QUERY_OVERWRITE:
  345. //
  346. // Query for approval to overwrite an existing file.
  347. //
  348. if ( Context->OverwriteExisting ) {
  349. return( EXPAND_COPY_THIS_FILE );
  350. }
  351. rc = EXPAND_SKIP_THIS_FILE;
  352. YesNo = SpRetreiveMessageText( ImageBase, MSG_YESNOALLQUIT, NULL, 0 );
  353. if ( YesNo ) {
  354. RcMessageOut( MSG_COPY_OVERWRITE_QUIT, FileName );
  355. if( RcLineIn( Text, 2 ) ) {
  356. if (( Text[0] == YesNo[2] ) || ( Text[0] == YesNo[3] )) {
  357. //
  358. // Yes, we may overwrite this file
  359. //
  360. rc = EXPAND_COPY_THIS_FILE;
  361. } else if (( Text[0] == YesNo[4] ) || ( Text[0] == YesNo[5] )) {
  362. //
  363. // All, we may overwrite this file, and don't prompt again
  364. //
  365. Context->OverwriteExisting = TRUE;
  366. rc = EXPAND_COPY_THIS_FILE;
  367. } else if (( Text[0] == YesNo[6] ) || ( Text[0] == YesNo[7] )) {
  368. //
  369. // No, and stop too.
  370. //
  371. Context->UserAborted = TRUE;
  372. rc = EXPAND_ABORT;
  373. }
  374. }
  375. SpMemFree( YesNo );
  376. }
  377. return( rc );
  378. case EXPAND_NOTIFY_MULTIPLE:
  379. //
  380. // We're being advised that the source contains multiple files.
  381. // If we don't have a selective filespec, we'll abort.
  382. //
  383. if ( Context->FileSpec == NULL ) {
  384. RcMessageOut( MSG_FILESPEC_REQUIRED );
  385. Context->UserAborted = TRUE;
  386. return ( EXPAND_ABORT );
  387. }
  388. return ( EXPAND_CONTINUE );
  389. case EXPAND_NOTIFY_CANNOT_EXPAND:
  390. //
  391. // We're being advised that the source file format is not
  392. // recognized. We display the file name and abort.
  393. //
  394. RcMessageOut( MSG_CANT_EXPAND_FILE, FileName );
  395. Context->UserAborted = TRUE;
  396. return ( EXPAND_ABORT );
  397. case EXPAND_NOTIFY_CREATE_FAILED:
  398. //
  399. // We're being advised that the current target file cannot be
  400. // created. We display the file name and abort.
  401. //
  402. RcMessageOut( MSG_EXPAND_FAILED, FileName );
  403. Context->UserAborted = TRUE;
  404. return ( EXPAND_ABORT );
  405. default:
  406. //
  407. // Ignore any unexpected callback.
  408. //
  409. return( EXPAND_NO_ERROR );
  410. }
  411. }
  412. BOOLEAN
  413. pRcCheckForBreak( VOID )
  414. {
  415. while ( SpInputIsKeyWaiting() ) {
  416. ULONG Key = SpInputGetKeypress();
  417. switch ( Key ) {
  418. case ASCI_ETX:
  419. case ASCI_ESC:
  420. RcMessageOut( MSG_BREAK );
  421. return TRUE;
  422. default:
  423. break;
  424. }
  425. }
  426. return FALSE;
  427. }
  428. //
  429. // pRcPatternMatch() & helpers
  430. //
  431. #define WILDCARD L'*' /* zero or more of any character */
  432. #define WILDCHAR L'?' /* one of any character (does not match END) */
  433. #define END L'\0' /* terminal character */
  434. #define DOT L'.' /* may be implied at end ("hosts" matches "*.") */
  435. static int __inline Lower(c)
  436. {
  437. if ((c >= L'A') && (c <= L'Z'))
  438. {
  439. return(c + (L'a' - L'A'));
  440. }
  441. else
  442. {
  443. return(c);
  444. }
  445. }
  446. static int __inline CharacterMatch(WCHAR chCharacter, WCHAR chPattern)
  447. {
  448. if (Lower(chCharacter) == Lower(chPattern))
  449. {
  450. return(TRUE);
  451. }
  452. else
  453. {
  454. return(FALSE);
  455. }
  456. }
  457. BOOL
  458. pRcPatternMatch(
  459. LPWSTR pszString,
  460. LPWSTR pszPattern,
  461. IN BOOL fImplyDotAtEnd
  462. )
  463. {
  464. /* RECURSIVE */
  465. //
  466. // This function does not deal with 8.3 conventions which might
  467. // be expected for filename comparisons. (In an 8.3 environment,
  468. // "alongfilename.html" would match "alongfil.htm")
  469. //
  470. // This code is NOT MBCS-enabled
  471. //
  472. for ( ; ; )
  473. {
  474. switch (*pszPattern)
  475. {
  476. case END:
  477. //
  478. // Reached end of pattern, so we're done. Matched if
  479. // end of string, no match if more string remains.
  480. //
  481. return(*pszString == END);
  482. case WILDCHAR:
  483. //
  484. // Next in pattern is a wild character, which matches
  485. // anything except end of string. If we reach the end
  486. // of the string, the implied DOT would also match.
  487. //
  488. if (*pszString == END)
  489. {
  490. if (fImplyDotAtEnd == TRUE)
  491. {
  492. fImplyDotAtEnd = FALSE;
  493. }
  494. else
  495. {
  496. return(FALSE);
  497. }
  498. }
  499. else
  500. {
  501. pszString++;
  502. }
  503. pszPattern++;
  504. break;
  505. case WILDCARD:
  506. //
  507. // Next in pattern is a wildcard, which matches anything.
  508. // Find the required character that follows the wildcard,
  509. // and search the string for it. At each occurence of the
  510. // required character, try to match the remaining pattern.
  511. //
  512. // There are numerous equivalent patterns in which multiple
  513. // WILDCARD and WILDCHAR are adjacent. We deal with these
  514. // before our search for the required character.
  515. //
  516. // Each WILDCHAR burns one non-END from the string. An END
  517. // means we have a match. Additional WILDCARDs are ignored.
  518. //
  519. for ( ; ; )
  520. {
  521. pszPattern++;
  522. if (*pszPattern == END)
  523. {
  524. return(TRUE);
  525. }
  526. else if (*pszPattern == WILDCHAR)
  527. {
  528. if (*pszString == END)
  529. {
  530. if (fImplyDotAtEnd == TRUE)
  531. {
  532. fImplyDotAtEnd = FALSE;
  533. }
  534. else
  535. {
  536. return(FALSE);
  537. }
  538. }
  539. else
  540. {
  541. pszString++;
  542. }
  543. }
  544. else if (*pszPattern != WILDCARD)
  545. {
  546. break;
  547. }
  548. }
  549. //
  550. // Now we have a regular character to search the string for.
  551. //
  552. while (*pszString != END)
  553. {
  554. //
  555. // For each match, use recursion to see if the remainder
  556. // of the pattern accepts the remainder of the string.
  557. // If it does not, continue looking for other matches.
  558. //
  559. if (CharacterMatch(*pszString, *pszPattern) == TRUE)
  560. {
  561. if (pRcPatternMatch(pszString + 1, pszPattern + 1, fImplyDotAtEnd) == TRUE)
  562. {
  563. return(TRUE);
  564. }
  565. }
  566. pszString++;
  567. }
  568. //
  569. // Reached end of string without finding required character
  570. // which followed the WILDCARD. If the required character
  571. // is a DOT, consider matching the implied DOT.
  572. //
  573. // Since the remaining string is empty, the only pattern which
  574. // could match after the DOT would be zero or more WILDCARDs,
  575. // so don't bother with recursion.
  576. //
  577. if ((*pszPattern == DOT) && (fImplyDotAtEnd == TRUE))
  578. {
  579. pszPattern++;
  580. while (*pszPattern != END)
  581. {
  582. if (*pszPattern != WILDCARD)
  583. {
  584. return(FALSE);
  585. }
  586. pszPattern++;
  587. }
  588. return(TRUE);
  589. }
  590. //
  591. // Reached end of the string without finding required character.
  592. //
  593. return(FALSE);
  594. break;
  595. default:
  596. //
  597. // Nothing special about the pattern character, so it
  598. // must match source character.
  599. //
  600. if (CharacterMatch(*pszString, *pszPattern) == FALSE)
  601. {
  602. if ((*pszPattern == DOT) &&
  603. (*pszString == END) &&
  604. (fImplyDotAtEnd == TRUE))
  605. {
  606. fImplyDotAtEnd = FALSE;
  607. }
  608. else
  609. {
  610. return(FALSE);
  611. }
  612. }
  613. if (*pszString != END)
  614. {
  615. pszString++;
  616. }
  617. pszPattern++;
  618. }
  619. }
  620. }