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.

693 lines
18 KiB

  1. /*++
  2. Copyright (c) 1991-2000 Microsoft Corporation
  3. Module Name:
  4. find.cxx
  5. Abstract:
  6. This utility allows the user to search for strings in a file
  7. It is functionaly compatible with DOS 5 find utility.
  8. SYNTAX (Command line)
  9. FIND [/?][/V][/C][/N][/I] "string" [[d:][path]filename[.ext]...]
  10. where:
  11. /? - Display this help
  12. /V - Display all lines NOT containing the string
  13. /C - Display only a count of lines containing string
  14. /N - Display number of line containing string
  15. /I - Ignore case
  16. UTILITY FUNCTION:
  17. Searches the specified file(s) looking for the string the user
  18. entered from the command line. If file name(s) are specifeied,
  19. those names are displayed, and if the string is found, then the
  20. entire line containing that string will be displayed. Optional
  21. parameters modify that behavior and are described above. String
  22. arguments have to be enclosed in double quotes. (Two double quotes
  23. if a double quote is to be included). Only one string argument is
  24. presently allowed. The maximum line size is determined by buffer
  25. size. Bigger lines will bomb the program. If no file name is given
  26. then it will asssume the input is coming from the standard Input.
  27. No errors are reported when reading from standard Input.
  28. EXIT:
  29. The program returns errorlevel:
  30. 0 - OK, and some matches
  31. 1 -
  32. 2 - Some Error
  33. Author:
  34. Bruce Wilson (w-wilson) 08-May-1991
  35. Environment:
  36. ULIB, User Mode
  37. Revision History:
  38. 08-May-1991 w-wilson
  39. created
  40. -----------------------------------------------------------
  41. 8/5/96 t-reagjo
  42. Unicode re-enabled. This was broken, presumably by some changes to ulib.
  43. Non-Unicode files searched will now be converted to Unicode using the CP of the
  44. console.
  45. Changed the string comparison to use the NLSAPI call CompareString.
  46. -----------------------------------------------------------
  47. --*/
  48. #include "ulib.hxx"
  49. #include "ulibcl.hxx"
  50. #include "arg.hxx"
  51. #include "array.hxx"
  52. #include "mbstr.hxx"
  53. #include "path.hxx"
  54. #include "wstring.hxx"
  55. #include "substrng.hxx"
  56. #include "filestrm.hxx"
  57. #include "file.hxx"
  58. #include "system.hxx"
  59. #include "arrayit.hxx"
  60. #include "smsg.hxx"
  61. #include "rtmsg.h"
  62. #include "find.hxx"
  63. #include "dir.hxx"
  64. #include <winnls.h>
  65. extern "C" {
  66. #include <stdio.h>
  67. #include <string.h>
  68. }
  69. #define MAX_LINE_LEN 1024
  70. static STR TmpBuf[MAX_LINE_LEN];
  71. DEFINE_CONSTRUCTOR( FIND, PROGRAM );
  72. VOID
  73. FIND::DisplayMessageAndExit(
  74. IN MSGID MsgId,
  75. IN ULONG ExitCode,
  76. IN MESSAGE_TYPE Type,
  77. IN PWSTRING String
  78. )
  79. /*++
  80. Routine Description:
  81. Display an error message and exit.
  82. Arguments:
  83. MsgId - Supplies the Id of the message to display.
  84. String - Supplies a string parameter for the message.
  85. ExitCode - Supplies the exit code to use for exit.
  86. Return Value:
  87. N/A
  88. --*/
  89. {
  90. if (MsgId != 0) {
  91. if (String)
  92. DisplayMessage(MsgId, Type, "%W", String);
  93. else
  94. DisplayMessage(MsgId, Type);
  95. }
  96. exit( ExitCode );
  97. }
  98. BOOLEAN
  99. FIND::Initialize(
  100. )
  101. /*++
  102. Routine Description:
  103. Initializes an FIND class.
  104. Arguments:
  105. None.
  106. Return Value:
  107. BOOLEAN - Indicates if the initialization succeeded.
  108. --*/
  109. {
  110. ARGUMENT_LEXEMIZER ArgLex;
  111. ARRAY LexArray;
  112. ARRAY ArgumentArray;
  113. STRING_ARGUMENT ProgramNameArgument;
  114. FLAG_ARGUMENT FlagCaseInsensitive;
  115. FLAG_ARGUMENT FlagNegativeSearch;
  116. FLAG_ARGUMENT FlagCountLines;
  117. FLAG_ARGUMENT FlagDisplayNumbers;
  118. FLAG_ARGUMENT FlagIncludeOfflineFiles;
  119. FLAG_ARGUMENT FlagIncludeOfflineFiles2;
  120. FLAG_ARGUMENT FlagDisplayHelp;
  121. FLAG_ARGUMENT FlagInvalid;
  122. STRING_ARGUMENT StringPattern;
  123. PROGRAM::Initialize();
  124. // An Error Level return of 1 indicates that no match was
  125. // found; a return of 0 indicates that a match was found.
  126. // Set it at 1 until a match is found.
  127. //
  128. _ErrorLevel = 1;
  129. if( !SYSTEM::IsCorrectVersion() ) {
  130. DisplayMessageAndExit(MSG_FIND_INCORRECT_VERSION, 2);
  131. }
  132. //
  133. // - init the array that will contain the command-line args
  134. //
  135. if ( !ArgumentArray.Initialize() ) {
  136. DisplayMessageAndExit(MSG_FIND_OUT_OF_MEMORY, 2);
  137. }
  138. //
  139. // - init the individual arguments
  140. //
  141. if( !ProgramNameArgument.Initialize("*")
  142. || !FlagCaseInsensitive.Initialize( "/I" )
  143. || !FlagNegativeSearch.Initialize( "/V" )
  144. || !FlagCountLines.Initialize( "/C" )
  145. || !FlagDisplayNumbers.Initialize( "/N" )
  146. || !FlagIncludeOfflineFiles.Initialize( "/OFFLINE" )
  147. || !FlagIncludeOfflineFiles2.Initialize( "/OFF" )
  148. || !FlagDisplayHelp.Initialize( "/?" )
  149. || !FlagInvalid.Initialize( "/*" ) // comment */
  150. || !StringPattern.Initialize( "\"*\"" )
  151. || !_PathArguments.Initialize( "*", FALSE, TRUE ) ) {
  152. DisplayMessageAndExit(MSG_FIND_OUT_OF_MEMORY, 2);
  153. }
  154. //
  155. // - put the arguments in the array
  156. //
  157. if( !ArgumentArray.Put( &ProgramNameArgument )
  158. || !ArgumentArray.Put( &FlagCaseInsensitive )
  159. || !ArgumentArray.Put( &FlagNegativeSearch )
  160. || !ArgumentArray.Put( &FlagCountLines )
  161. || !ArgumentArray.Put( &FlagDisplayNumbers )
  162. || !ArgumentArray.Put( &FlagIncludeOfflineFiles )
  163. || !ArgumentArray.Put( &FlagIncludeOfflineFiles2 )
  164. || !ArgumentArray.Put( &FlagDisplayHelp )
  165. || !ArgumentArray.Put( &FlagInvalid )
  166. || !ArgumentArray.Put( &StringPattern )
  167. || !ArgumentArray.Put( &_PathArguments ) ) {
  168. DisplayMessageAndExit(MSG_FIND_OUT_OF_MEMORY, 2);
  169. }
  170. //
  171. // - init the lexemizer
  172. //
  173. if ( !LexArray.Initialize() ) {
  174. DisplayMessageAndExit(MSG_FIND_OUT_OF_MEMORY, 2);
  175. }
  176. if ( !ArgLex.Initialize( &LexArray ) ) {
  177. DisplayMessageAndExit(MSG_FIND_OUT_OF_MEMORY, 2);
  178. }
  179. //
  180. // - set up the defaults
  181. //
  182. ArgLex.PutSwitches( "/" );
  183. ArgLex.PutStartQuotes( "\"" );
  184. ArgLex.PutEndQuotes( "\"" );
  185. ArgLex.PutSeparators( " \"\t" );
  186. ArgLex.SetCaseSensitive( FALSE );
  187. if( !ArgLex.PrepareToParse() ) {
  188. //
  189. // invalid format
  190. //
  191. DisplayMessageAndExit(MSG_FIND_INVALID_FORMAT, 2);
  192. }
  193. //
  194. // - now parse the command line. The args in the array will be set
  195. // if they are found on the command line.
  196. //
  197. if( !ArgLex.DoParsing( &ArgumentArray ) ) {
  198. if( FlagInvalid.QueryFlag() ) {
  199. //
  200. // invalid switch
  201. //
  202. DisplayMessageAndExit(MSG_FIND_INVALID_SWITCH, 2);
  203. } else {
  204. //
  205. // invalid format
  206. //
  207. DisplayMessageAndExit(MSG_FIND_INVALID_FORMAT, 2);
  208. }
  209. } else if ( _PathArguments.WildCardExpansionFailed() ) {
  210. //
  211. // No files matched
  212. //
  213. DisplayMessageAndExit(MSG_FIND_FILE_NOT_FOUND,
  214. 2,
  215. ERROR_MESSAGE,
  216. (PWSTRING)_PathArguments.GetLexemeThatFailed());
  217. }
  218. if( FlagInvalid.QueryFlag() ) {
  219. //
  220. // invalid switch
  221. //
  222. DisplayMessageAndExit(MSG_FIND_INVALID_SWITCH, 2);
  223. }
  224. //
  225. // - now do semantic checking/processing
  226. // - if they ask for help, do it right away and return
  227. // - set flags
  228. //
  229. if( FlagDisplayHelp.QueryFlag() ) {
  230. DisplayMessageAndExit(MSG_FIND_USAGE, 0, NORMAL_MESSAGE);
  231. }
  232. if( !StringPattern.IsValueSet() ) {
  233. DisplayMessageAndExit(MSG_FIND_INVALID_FORMAT, 2);
  234. } else {
  235. //
  236. // - keep a copy of the pattern string
  237. //
  238. DebugAssert(StringPattern.GetString());
  239. _PatternString.Initialize(StringPattern.GetString());
  240. }
  241. _CaseSensitive = (BOOLEAN)!FlagCaseInsensitive.QueryFlag();
  242. _LinesContainingPattern = (BOOLEAN)!FlagNegativeSearch.QueryFlag();
  243. _OutputLines = (BOOLEAN)!FlagCountLines.QueryFlag();
  244. _OutputLineNumbers = (BOOLEAN)FlagDisplayNumbers.QueryFlag();
  245. _SkipOfflineFiles = ( (BOOLEAN)!FlagIncludeOfflineFiles.QueryFlag() ) &&
  246. ( (BOOLEAN)!FlagIncludeOfflineFiles2.QueryFlag() );
  247. return( TRUE );
  248. }
  249. BOOLEAN
  250. FIND::IsDos5CompatibleFileName(
  251. IN PCPATH Path
  252. )
  253. /*++
  254. Routine Description:
  255. Parses the path string and returns FALSE if DOS5 would reject
  256. the path.
  257. Arguments:
  258. Path - Supplies the path
  259. Return Value:
  260. BOOLEAN - Returns FALSE if DOS5 would reject the path,
  261. TRUE otherwise
  262. --*/
  263. {
  264. PWSTRING String;
  265. DebugPtrAssert( Path );
  266. String = (PWSTRING)Path->GetPathString();
  267. DebugPtrAssert( String );
  268. if ( String->QueryChCount() > 0 ) {
  269. if ( String->QueryChAt(0) == '\"' ) {
  270. return FALSE;
  271. }
  272. }
  273. return TRUE;
  274. }
  275. ULONG
  276. FIND::SearchStream(
  277. PSTREAM StreamToSearch
  278. )
  279. /*++
  280. Routine Description:
  281. Does the search on an open file_stream.
  282. Arguments:
  283. None.
  284. Return Value:
  285. Number of lines found/not found.
  286. --*/
  287. {
  288. ULONG LineCount;
  289. ULONG FoundCount;
  290. DWORD PatternLen;
  291. DWORD LineLen;
  292. DWORD LastPosInLine;
  293. BOOLEAN Found;
  294. WCHAR c;
  295. PWSTR pLine;
  296. PWSTR p;
  297. WCHAR CurrentLine[MAX_LINE_LEN];
  298. WCHAR PatternString[MAX_LINE_LEN];
  299. DSTRING dstring;
  300. DSTRING _String;
  301. DWORD CompareFlags;
  302. LineCount = FoundCount = 0;
  303. PatternLen = _PatternString.QueryChCount() ;
  304. _PatternString.QueryWSTR(0,TO_END,PatternString,MAX_LINE_LEN,TRUE);
  305. if ( !_String.Initialize() ) {
  306. DisplayMessageAndExit(MSG_FIND_OUT_OF_MEMORY, 2);
  307. }
  308. //
  309. // - converting to Unicode from the current console code page
  310. // (say -that- ten times fast...)
  311. //
  312. _String.SetConsoleConversions();
  313. //
  314. // - for each line from stream
  315. // - do strstr to see if pattern string is in line
  316. // - if -ve search and not in line || +ve search and in line
  317. // - output line and number or inc counter appropriately
  318. //
  319. while( !StreamToSearch->IsAtEnd() ) {
  320. if ( !StreamToSearch->ReadLine( &_String)) {
  321. DisplayMessageAndExit(MSG_FIND_UNABLE_TO_READ_FILE, 2);
  322. }
  323. if (_String.QueryChCount() == 0 &&
  324. StreamToSearch->IsAtEnd()) {
  325. break;
  326. }
  327. _String.QueryWSTR(0,TO_END,CurrentLine,MAX_LINE_LEN,TRUE);
  328. LineLen = min ( MAX_LINE_LEN - 1, _String.QueryChCount());
  329. LineCount++;
  330. //
  331. // - look for pattern string in the current line
  332. // - note: a 0-length pattern ("") never matches a line.
  333. // A 0-length pattern can produce output with the /v
  334. // switch.
  335. // - start at the end (saves a var)
  336. //
  337. Found = FALSE;
  338. if ( PatternLen && LineLen >= PatternLen ) {
  339. CompareFlags = 0;
  340. if ( !_CaseSensitive ) {
  341. CompareFlags |= NORM_IGNORECASE;
  342. }
  343. LastPosInLine = LineLen - PatternLen + 1;
  344. pLine = CurrentLine + LastPosInLine;
  345. while ( pLine >= CurrentLine && !Found ) {
  346. if ( CompareString ( LOCALE_USER_DEFAULT, CompareFlags, PatternString, PatternLen, pLine, PatternLen ) == 2 ) {
  347. Found = TRUE;
  348. }
  349. pLine--;
  350. }
  351. }
  352. //
  353. // - if either (search is +ve and found a match)
  354. // or (search is -ve and no match found)
  355. // then print line/line number based on options
  356. //
  357. if( (_LinesContainingPattern && Found)
  358. || (!_LinesContainingPattern && !Found) ) {
  359. FoundCount++;
  360. if( _OutputLines ) {
  361. dstring.Initialize(CurrentLine);
  362. if( _OutputLineNumbers ) {
  363. DisplayMessage( MSG_FIND_LINE_AND_NUMBER, NORMAL_MESSAGE, "%d%W", LineCount, &dstring);
  364. } else {
  365. DisplayMessage( MSG_FIND_LINEONLY, NORMAL_MESSAGE, "%W", &dstring);
  366. }
  367. }
  368. }
  369. }
  370. if (FoundCount) {
  371. // Set _ErrorLevel to zero to indicate that at least
  372. // one match has been found.
  373. //
  374. _ErrorLevel = 0;
  375. }
  376. return(FoundCount);
  377. }
  378. VOID
  379. FIND::SearchFiles(
  380. )
  381. /*++
  382. Routine Description:
  383. Does the search on the files specified on the command line.
  384. Arguments:
  385. None.
  386. Return Value:
  387. None.
  388. --*/
  389. {
  390. PARRAY PathArray;
  391. PARRAY_ITERATOR PIterator;
  392. PPATH CurrentPath;
  393. PFSN_FILE CurrentFSNode = NULL;
  394. PFSN_DIRECTORY CurrentFSDir;
  395. PFILE_STREAM CurrentFile = NULL;
  396. DSTRING CurrentPathString;
  397. ULONG LinesFound;
  398. BOOLEAN PrintSkipWarning = FALSE;
  399. BOOLEAN OfflineSkipped;
  400. //
  401. // - if 0 paths on cmdline then open stdin
  402. // - if more than one path set OutputName flag
  403. //
  404. if( (_PathArguments.QueryPathCount() == 0) ) {
  405. // use stdin
  406. LinesFound = SearchStream( Get_Standard_Input_Stream() );
  407. if( !_OutputLines ) {
  408. DisplayMessage(MSG_FIND_COUNT, NORMAL_MESSAGE, "%d", LinesFound);
  409. }
  410. return;
  411. }
  412. PathArray = _PathArguments.GetPathArray();
  413. PIterator = (PARRAY_ITERATOR)PathArray->QueryIterator();
  414. //
  415. // - for each path specified on the command line
  416. // - open a stream for the path
  417. // - print filename if supposed to
  418. // - call SearchStream
  419. //
  420. while( (CurrentPath = (PPATH)PIterator->GetNext()) != NULL ) {
  421. CurrentPathString.Initialize( CurrentPath->GetPathString() );
  422. CurrentPathString.Strupr();
  423. // if the system object can return a FSN_DIRECTORY for this
  424. // path then the user is trying to 'find' on a dir so print
  425. // access denied and skip this file
  426. if( CurrentFSDir = SYSTEM::QueryDirectory(CurrentPath) ) {
  427. if (CurrentPath->IsDrive()) {
  428. DisplayMessage(MSG_FIND_FILE_NOT_FOUND, ERROR_MESSAGE, "%W", &CurrentPathString);
  429. } else {
  430. DisplayMessage( MSG_ACCESS_DENIED, ERROR_MESSAGE, "%W", &CurrentPathString);
  431. }
  432. DELETE( CurrentFSDir );
  433. continue;
  434. }
  435. if( !(CurrentFSNode = SYSTEM::QueryFile(CurrentPath, _SkipOfflineFiles, &OfflineSkipped)) ||
  436. !(CurrentFile = CurrentFSNode->QueryStream(READ_ACCESS, FILE_FLAG_OPEN_NO_RECALL)) ) {
  437. //
  438. // If the file name is "", DOS5 prints an invalid parameter
  439. // format message. There is no clean way to filter this
  440. // kind of stuff in the ULIB library, so we will have to
  441. // parse the path ourselves.
  442. //
  443. if ( IsDos5CompatibleFileName( CurrentPath ) ) {
  444. //
  445. // Track whether an offline file was skipped:
  446. // We don't want to print an error message for each file,
  447. // just a one warning at the end
  448. //
  449. if (OfflineSkipped) {
  450. PrintSkipWarning = TRUE;
  451. } else {
  452. DisplayMessage(MSG_FIND_FILE_NOT_FOUND, ERROR_MESSAGE, "%W", &CurrentPathString);
  453. }
  454. } else {
  455. DisplayMessage(MSG_FIND_INVALID_FORMAT, ERROR_MESSAGE );
  456. break;
  457. }
  458. DELETE( CurrentFile );
  459. DELETE( CurrentFSNode );
  460. CurrentFile = NULL;
  461. CurrentFSNode = NULL;
  462. continue;
  463. }
  464. if( _OutputLines ) {
  465. DisplayMessage( MSG_FIND_BANNER, NORMAL_MESSAGE, "%W", &CurrentPathString);
  466. }
  467. LinesFound = SearchStream( CurrentFile );
  468. if( !_OutputLines ) {
  469. DisplayMessage(MSG_FIND_COUNT_BANNER, NORMAL_MESSAGE, "%W%d", &CurrentPathString, LinesFound);
  470. }
  471. DELETE( CurrentFSNode );
  472. DELETE( CurrentFile );
  473. CurrentFSNode = NULL;
  474. CurrentFile = NULL;
  475. }
  476. //
  477. // Print warning message if offline files were skipped
  478. //
  479. if(PrintSkipWarning) {
  480. DisplayMessage(MSG_FIND_OFFLINE_FILES_SKIPPED, ERROR_MESSAGE);
  481. }
  482. return;
  483. }
  484. VOID
  485. FIND::Terminate(
  486. )
  487. /*++
  488. Routine Description:
  489. Deletes objects created during initialization.
  490. Arguments:
  491. None.
  492. Return Value:
  493. None.
  494. --*/
  495. {
  496. exit(_ErrorLevel);
  497. }
  498. VOID __cdecl
  499. main()
  500. {
  501. DEFINE_CLASS_DESCRIPTOR( FIND );
  502. {
  503. FIND Find;
  504. if( Find.Initialize() ) {
  505. Find.SearchFiles();
  506. }
  507. Find.Terminate();
  508. }
  509. }