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.

2238 lines
76 KiB

  1. /*++
  2. Copyright (c) 1988-1999 Microsoft Corporation
  3. Module Name:
  4. clex.c
  5. Abstract:
  6. Lexical processing support
  7. --*/
  8. #include "cmd.h"
  9. extern unsigned int DosErr;
  10. extern jmp_buf CmdJBuf2; /* Used for error handling */
  11. extern TCHAR DTNums[];
  12. extern TCHAR MsgBuf[];
  13. extern unsigned msglen; /* @@@@@@@@ */
  14. int Necho = 0; /* No echo option */
  15. extern BOOLEAN fDumpTokens;
  16. extern int KeysFlag; /* @@5 */
  17. unsigned DataFlag; /* Tells FillBuf where to get its input */
  18. UINT_PTR DataPtr; /* File handle/string ptr FillBuf... */
  19. int Ctrlc = 0; /* flag - if set print a ctrl/c before next prompt */
  20. int ExtCtrlc = 0; /* @@4 flag, if set print msg */
  21. int AtIsToken; /* @@4 flag, true if @ is a token */
  22. /***
  23. * The lex buffer is called LexBuf. It holds characters as they are
  24. * retrieved one by one by GetByte. With the advent of double byte
  25. * characters, UnGetByte may be sometimes called upon to put back
  26. * up to two characters. To facilitate this, LexBuf is really an
  27. * alias for &LexBuffer[1]. This gives an extra byte in front of the
  28. * buffer for character push back. Every time fillbuf is called, it
  29. * copies the last character of the previous buffer into the byte
  30. * preceeding the normal buffer. Thus, when UnGetByte does a
  31. * LexBufPtr-- the pointer will correctly point at the preceeding character.
  32. */
  33. TCHAR LexBuffer[LBUFLEN+3]; /* @@4 */
  34. /* ...reads from Lexer input buffer *M011*/
  35. /* LBUFLEN characters + newline + null + */
  36. /* an extra byte for UnGetByte */
  37. #define LexBuf (&LexBuffer[1])
  38. TCHAR *LexBufPtr; /* Ptr to next byte in Lex's input buffer */
  39. TCHAR *PrevLexPtr; /* M013 - New previous token pointer */
  40. TCHAR FrsBuf[LBUFLEN+1];
  41. extern CHAR AnsiBuf[];
  42. extern TCHAR Fmt27[];
  43. extern int NulNode;
  44. extern TCHAR Fmt19[];
  45. extern TCHAR DBkSpc[];
  46. #if defined(FE_SB) // DDBkSpc[]
  47. extern TCHAR DDBkSpc[];
  48. #endif // defined(FE_SB)
  49. extern unsigned global_dfvalue; /* @@4 */
  50. extern int EchoFlag;
  51. extern TCHAR PromptStr[], CurDrvDir[], Delimiters[];
  52. extern unsigned flgwd;
  53. extern BOOL CtrlCSeen;
  54. VOID SetCtrlC();
  55. VOID ResetCtrlC();
  56. //
  57. // Prompt string special characters and associated print character/flag.
  58. //
  59. // These are the flags which may be placed in the flag field of the
  60. // prompt_table structure to control PrintPrompt
  61. //
  62. #define PNULLFLAG 0
  63. #define PTIMFLAG 1
  64. #define PDATFLAG 2
  65. #define PPATFLAG 3
  66. #define PVERFLAG 4
  67. #define PBAKFLAG 5 // destructive backspace flag
  68. #define PNLNFLAG 6 // newline prompt flag
  69. #define PDRVFLAG 7
  70. #define PLITFLAG 8 // Print character in SpecialChar field
  71. #define PDPTFLAG 9 // Print depth of pushd stack
  72. #define PNETFLAG 10 // Print \\server\share or local for current drive
  73. //
  74. // Esc character used to mark a special prompt char. in prompt string
  75. //
  76. #define PROMPTESC DOLLAR
  77. //
  78. // Table of prompts for user.
  79. //
  80. typedef struct {
  81. TCHAR Char; // Used to match esc. char. in user prompt
  82. TCHAR Format; // Used to print some string that has to be computed
  83. TCHAR Literal; // When Format == PLITFLAG this is printed in prompt
  84. } PROMPT_ENTRY;
  85. PROMPT_ENTRY PromptTable[] = {
  86. { TEXT('P'),PPATFLAG, NULLC },
  87. { TEXT('E'),PLITFLAG,'\033' },
  88. { TEXT('D'),PDATFLAG, NULLC },
  89. { TEXT('T'),PTIMFLAG, NULLC },
  90. { TEXT('B'),PLITFLAG, PIPOP },
  91. { TEXT('G'),PLITFLAG, OUTOP },
  92. { TEXT('H'),PBAKFLAG, NULLC },
  93. { TEXT('L'),PLITFLAG, INOP },
  94. { TEXT('N'),PDRVFLAG, NULLC },
  95. { TEXT('S'),PLITFLAG, SPACE },
  96. { TEXT('Q'),PLITFLAG, EQ },
  97. { TEXT('V'),PVERFLAG, NULLC },
  98. { TEXT('_'),PNLNFLAG, NULLC },
  99. { DOLLAR, PLITFLAG, DOLLAR },
  100. { TEXT('A'),PLITFLAG, ANDOP },
  101. { TEXT('C'),PLITFLAG, LPOP },
  102. { TEXT('F'),PLITFLAG, RPOP },
  103. { TEXT('+'),PDPTFLAG, NULLC },
  104. { TEXT('M'),PNETFLAG, NULLC },
  105. { NULLC,PNULLFLAG, NULLC}};
  106. /*** InitLex - initialize the lexer's global variables
  107. *
  108. * Purpose:
  109. * Initialize DataFlag, DataPtr, LexBuf, and LexBufPtr.
  110. *
  111. * InitLex(unsigned dfvalue, INT_PTR dpvalue)
  112. *
  113. * Args:
  114. * dfvalue - the value to be assigned to DataFlag
  115. * dpvalue - the value to be assigned to DataPtr
  116. *
  117. */
  118. void InitLex(dfvalue, dpvalue)
  119. unsigned dfvalue;
  120. INT_PTR dpvalue;
  121. {
  122. DataFlag = dfvalue;
  123. DataPtr = dpvalue;
  124. *LexBuf = NULLC;
  125. PrevLexPtr = LexBufPtr = LexBuf; /* M013 - Init new ptr */
  126. DEBUG((PAGRP, LXLVL, "INITLEX: Dataflag = %04x DataPtr = %04x", DataFlag, DataPtr));
  127. }
  128. /*** Lex - controls data input and token lexing
  129. *
  130. * Purpose:
  131. * Read in the next token or argstring and put it in tokbuf.
  132. *
  133. * unsigned Lex(TCHAR *tokbuf, unsigned lflag)
  134. *
  135. * Args:
  136. * tokbuf - buffer used by lex to store the next token or
  137. * - M013 if zero, indicates unget last token.
  138. * lflag - bit 0 on if lex is to return an argument string, ie white space
  139. * other than NLN is not considered a token delimiter
  140. *
  141. * Returns:
  142. * If the token is an operator, EOS, or NLN ret the 1st byte of the token.
  143. * If the token is a command, REM arg or argstring, return TEXTOKEN.
  144. * If the token is longer than MAXTOKLEN or the token is illegal, LEXERROR
  145. * is returned.
  146. *
  147. * Notes:
  148. * The parser depends on the fact that the only values returned by
  149. * Lex that are greater than 0xff are TEXTOKEN and LEXERROR.
  150. *
  151. */
  152. unsigned Lex(tokbuf, lflag)
  153. TCHAR *tokbuf;
  154. unsigned lflag;
  155. {
  156. int i; /* Length of text token */
  157. TCHAR c, /* Current character */
  158. *tbcpy; /* Copy of tokbuf */
  159. if(setjmp(CmdJBuf2)) { /* M026 - Now msg printed prior */
  160. return((unsigned)LEXERROR); /* ...to arrival here */
  161. }
  162. /* M013 - This code detects request to unget last token and if so, performs
  163. that function. If not, it sets the previous token pointer to
  164. to equal the current token pointer.
  165. */
  166. if (tokbuf == LX_UNGET) { /* Unget last token? */
  167. DEBUG((PAGRP, LXLVL, "LEX: Ungetting last token."));
  168. if (fDumpTokens)
  169. cmd_printf( TEXT("Ungetting: '%s'\n"), PrevLexPtr);
  170. LexBufPtr = PrevLexPtr; /* If so, reset ptr... */
  171. return(LX_UNGET); /* ...and return */
  172. } else { /* If not, set previous... */
  173. PrevLexPtr = LexBufPtr; /* ...ptr to current... */
  174. DEBUG((PAGRP, LXLVL, "LEX: lflag = %d", lflag));
  175. } /* ...ptr and continue */
  176. /* M013 ends */
  177. tbcpy = tokbuf;
  178. /* M005 - Altered conditional below to also fail if the LX_REM bit
  179. * is set making it "If !(arg | rem), eat whtspc & delims".
  180. */
  181. if (!(lflag & (LX_ARG|LX_REM))) {
  182. DEBUG((PAGRP, LXLVL, "LEX: Trashing white space."));
  183. while (TRUE) {
  184. c = GetByte();
  185. if (((_istspace(c) && c != NLN)
  186. || (mystrchr(((lflag & LX_EQOK) ? &Delimiters[1] : Delimiters), c) && c)))
  187. ;
  188. else
  189. break;
  190. }
  191. UnGetByte();
  192. }
  193. /* As of M016, operators of more than 2 characters can be lexed. For now,
  194. * these are assumed to be specific-handle redirection operators of the form
  195. * 'n>>' or 'n<<' and always begin with a digit. TextCheck will not return
  196. * a digit as an operator unless it is preceeded by whitespace and followed
  197. * by '>' or '<'. To simplify matters, handle substitution (ie., '...&n')
  198. * is now lexed as part of a special five character operator, instead of
  199. * looking at the '&n' as an argument. ASCII filename arguments, however,
  200. * are still lexed as separate tokens via another call to Lex.
  201. */
  202. if (TextCheck(&c, &lflag) == LX_DELOP) {
  203. *tokbuf++ = c; /* The token is an operator */
  204. if (_istdigit(c)) { /* Next is '<' or '>'... */
  205. DEBUG((PAGRP, LXLVL, "LEX: Found digit operator."));
  206. c = GetByte(); /* ...by definition or ... */
  207. *tokbuf++ = c; /* ...we wouldn't be here */
  208. }
  209. if (c == PIPOP || c == ANDOP || c == OUTOP || c == INOP) {
  210. if ((c = GetByte()) == *(tokbuf-1)) {
  211. *tokbuf++ = c;
  212. c = GetByte();
  213. }
  214. if (*(tokbuf-1) == OUTOP || *(tokbuf-1) == INOP) {
  215. DEBUG((PAGRP,LXLVL, "LEX: Found redir."));
  216. if (c == CSOP) {
  217. DEBUG((PAGRP,LXLVL, "LEX: Found >&"));
  218. *tokbuf++ = c;
  219. do {
  220. c = GetByte();
  221. } while (_istspace(c) ||
  222. mystrchr(Delimiters,c));
  223. if (_istdigit(c)) {
  224. *tokbuf++ = c;
  225. c = GetByte();
  226. }
  227. }
  228. /* M016 ends */
  229. }
  230. UnGetByte();
  231. }
  232. *tokbuf = NULLC;
  233. DEBUG((PAGRP, LXLVL, "LEX: Returning op = `%ws'", tbcpy));
  234. return(*tbcpy);
  235. }
  236. DEBUG((PAGRP, LXLVL, "LEX: Found text token %04x, Getting more.", c));
  237. *tokbuf++ = c; /* Found text token, read the rest */
  238. lflag |= LX_DBLOK;
  239. AtIsToken = 0; /* @@4, treat @ as text now */
  240. for (i = (int)(tokbuf-tbcpy); TextCheck(&c, &lflag) != LX_DELOP && i < MAXTOKLEN; i++)
  241. *tokbuf++ = c;
  242. lflag &= ~LX_DBLOK;
  243. *tokbuf = NULLC;
  244. if (i < MAXTOKLEN)
  245. UnGetByte();
  246. if (i >= MAXTOKLEN && c != (TCHAR) -1) { /* Token too long, complain */
  247. /* M025 */ PutStdErr(MSG_TOKEN_TOO_LONG, ONEARG, tbcpy );
  248. return((unsigned)LEXERROR);
  249. }
  250. DEBUG((PAGRP, LXLVL, "LEX: Return text = `%ws' type = %04x", tbcpy, TEXTOKEN));
  251. return(TEXTOKEN);
  252. }
  253. /*** TextCheck - get the next character and determine its type
  254. *
  255. * Purpose:
  256. * Store the next character in LexBuf in *c. If that character is a
  257. * valid text token character, return it. Otherwise return LX_DELOP.
  258. *
  259. * int TextCheck(TCHAR *c, unsigned &lflag)
  260. *
  261. * Args:
  262. * c - the next character in the lexer input buffer is stored here.
  263. * lflag - Bit 0 = On if lex is to return an argument string, ie.,
  264. * white space other than NLN is not a token delimiter.
  265. * Bit 1 = On if a quoted string is being read, ie., only NLN
  266. * or a closing quote are delimiters.
  267. * Bit 2 = On if equalsigns are NOT to be considered delimiters.
  268. * Bit 3 = On if left parens are to be considered operators.
  269. * Bit 4 = On if right parens are to be considered operators.
  270. * Bit 5 = On if only NLN is to be a delimiter.
  271. * Bit 6 = On iff the caller is willing to accept the second
  272. * half of a double byte character
  273. *
  274. * Returns:
  275. * Next character or LX_DELOP if a delimeter/operator character is found.
  276. *
  277. */
  278. int TextCheck(c, lflag)
  279. TCHAR *c;
  280. unsigned *lflag;
  281. {
  282. TCHAR i; /* M016 - Temp byte holder */
  283. static int saw_dbcs_lead = 0; /* remember if we're in the middle
  284. of a double byte character */
  285. *c = GetByte();
  286. if (saw_dbcs_lead) {
  287. saw_dbcs_lead = 0;
  288. if (*lflag & LX_DBLOK) /* If second half of double byte is */
  289. return(*c); /* ok, return it, otherwise. . . */
  290. else
  291. *c = GetByte(); /* go on to next character */
  292. }
  293. DEBUG((PAGRP, BYLVL, "TXTCHK: c = %04x lflag = %04x", *c, *lflag));
  294. switch (*c) {
  295. case SILOP: /* M017 - New unary operator */
  296. /* ...binds like left paren */
  297. if ((*lflag & (LX_QUOTE|LX_REM))) /* M005 */
  298. break;
  299. if( !AtIsToken ) /* If @ is not to be treated */
  300. { /* as token, then indicate */
  301. return( *c ); /* such @@4 */
  302. }
  303. case LPOP: /* M002 - Moved these two cases */
  304. if ((*lflag & (LX_QUOTE|LX_REM))) /* M005 */
  305. break;
  306. if(!(*lflag & GT_LPOP)) /* ...up and break if */
  307. break; /* ...they are not to */
  308. case RPOP: /* ...be treated as ops */
  309. if ((*lflag & (LX_QUOTE|LX_REM))) /* M005 */
  310. break;
  311. if((!(*lflag & GT_RPOP)) && *c == RPOP)
  312. break; /* M002 ends */
  313. case NLN: /* M005 - NLN turns off QUOTE/REM flags */
  314. case EOS: /* @@5a - treat like NLN */
  315. *lflag &= (~LX_QUOTE & ~LX_REM); /* M005 */
  316. case CSOP:
  317. case INOP: /* M005 - Note below that LX_DELOP... */
  318. case PIPOP: /* ...QUOTE mode or REM mode is in... */
  319. case OUTOP: /* ...in effect at the time */
  320. if (!(*lflag & (LX_QUOTE|LX_REM))) /* M005 */
  321. return(LX_DELOP);
  322. }
  323. /* M003 - If the character is '^', and the QUOTE mode flag is off,
  324. * discard the current character, get the next one and return
  325. * it as text.
  326. * M005 - Extended this conditional to insure that both QUOTE and
  327. * REM flags must be off for "escape" to occur.
  328. */
  329. if (*c == ESCHAR && !(*lflag & (LX_QUOTE|LX_REM))) {
  330. *c = GetByte();
  331. if (*c == NLN) {
  332. *c = GetByte( );
  333. //
  334. // If we are at the end of the string, meaning that the
  335. // command ended with ^ and there is an empty next command
  336. // (most likely from a batch file), then we just treat
  337. // this as a standard delimeter.
  338. //
  339. if (*c == EOS) {
  340. return(LX_DELOP);
  341. }
  342. }
  343. return(*c);
  344. }
  345. /* M003/M005 end */
  346. if (*c == QUOTE) /* Flip quote mode flag bit */
  347. *lflag ^= LX_QUOTE;
  348. /* M005 - Altered conditional below to also insure that REM flag was
  349. * off before checking for any delimiters
  350. */
  351. if (!(*lflag & (LX_ARG|LX_QUOTE|LX_REM)) &&
  352. (_istspace(*c) ||
  353. mystrchr(((*lflag & LX_EQOK) ? &Delimiters[1] : Delimiters), *c)))
  354. return(LX_DELOP);
  355. /* As of M016, before accepting this character as text, it is now tested
  356. * for being a digit followed by one of the redirection operators and;
  357. * 1) is the first character on a line, 2) is preceeded by whitespace or
  358. * 3) is preceeded by a delimiter (including Command's operators). If it
  359. * meets these conditions, it is a special, specific-handle redirection
  360. * operator and TextCheck must return LX_DELOP so that Lex can build the
  361. * remainder. NOTE: LexBufPtr is advanced during GetByte, so that the
  362. * current byte is *(LexBufPtr-1).
  363. */
  364. if (_istdigit(*c)) {
  365. DEBUG((PAGRP,BYLVL,"TXTCHK: Found digit character."));
  366. if ((LexBufPtr-LexBuf) < 2 ||
  367. _istspace(i = *(LexBufPtr-2)) ||
  368. mystrchr(TEXT("()|&=,;\""), i)) {
  369. DEBUG((PAGRP,BYLVL,"TXTCHK: Digit follows delim."));
  370. if (*LexBufPtr == INOP || *LexBufPtr == OUTOP) {
  371. DEBUG((PAGRP,BYLVL,"TXTCHK: Found hdl redir"));
  372. if (!(*lflag & (LX_QUOTE|LX_REM))) /* M005 */
  373. return(LX_DELOP);
  374. }
  375. }
  376. }
  377. /* M016 ends */
  378. return(*c);
  379. }
  380. /*** GetByte - return the next byte in the buffer
  381. *
  382. * Purpose:
  383. * Get the next byte from the lexer's input buffer. If the buffer is
  384. * empty, fill it first. Update the buffer pointer.
  385. *
  386. * TCHAR GetByte()
  387. *
  388. * Return:
  389. * The next character in the buffer or EOF.
  390. *
  391. * Notes:
  392. * All three types of input STDIN, FILE and STRING are treated
  393. * the same now when it comes to dealing with CR/LF combinations.
  394. * Keyboard input is massaged to look like file input.
  395. * Invalid double byte characters are thrown away and not returned
  396. * to the caller.
  397. *
  398. */
  399. TCHAR GetByte()
  400. {
  401. static int saw_dbcs_lead = 0; /* remember if we're in the middle
  402. of a double byte character */
  403. TCHAR lead; /* variables for remember parts of
  404. double byte characters */
  405. if (!*LexBufPtr)
  406. FillBuf();
  407. DEBUG((PAGRP, BYLVL, "GTTCHAR: byte = %04x", *LexBufPtr));
  408. if (*LexBufPtr == CR && !saw_dbcs_lead) {
  409. /* M000 - removed file-only test */
  410. LexBufPtr++;
  411. return(GetByte());
  412. }
  413. /* if this is a double byte character, look ahead to the next character
  414. and check for legality */
  415. if (saw_dbcs_lead) {
  416. saw_dbcs_lead = 0;
  417. return(*LexBufPtr++);
  418. }
  419. else {
  420. lead = *LexBufPtr++;
  421. return(lead);
  422. }
  423. }
  424. /*** UnGetByte - rewind the lexer buffer pointer 1 byte
  425. *
  426. * Purpose:
  427. * Back up the lexer's buffer pointer. If the pointer already points
  428. * to the beginning of the buffer, do nothing.
  429. *
  430. * UnGetByte()
  431. *
  432. * Return:
  433. * Nothing.
  434. *
  435. */
  436. void UnGetByte()
  437. {
  438. if (!(LexBufPtr == LexBuffer))
  439. LexBufPtr--;
  440. }
  441. int
  442. EditLine(
  443. CRTHANDLE CrtHandle,
  444. TCHAR *Buffer, /* the buffer to be filled */
  445. int MaxLength, /* the Maximum length of the buffer, */
  446. /* including <CR> */
  447. int *ReturnLength /* the number of characters in the buffer, not */
  448. /* including the <CR> */
  449. )
  450. {
  451. BOOL flag;
  452. flag = ReadBufFromInput( CRTTONT(CrtHandle), (TCHAR*)Buffer, MaxLength, ReturnLength);
  453. if (flag == 0 || *ReturnLength == 0) {
  454. return(GetLastError());
  455. } else {
  456. return(0);
  457. }
  458. }
  459. /*** FillBuf - read data to fill the lexer's buffer
  460. *
  461. * Purpose:
  462. * To fill the lexer's buffer with data from whatever source is indicated
  463. * by the global variables DataFlag and DataPtr. If reading from
  464. * stdin, prompt for data.
  465. *
  466. * FillBuf()
  467. *
  468. * Notes:
  469. * - Algorithm after M021 is as follows:
  470. * copy last char of current buffer into LexBuffer[0] (which preceeds
  471. * LexBuf by one byte) so the UnGetByte can unget two bytes
  472. * If READSTDIN or READFILE
  473. * If input is STDIN
  474. * Print correct prompt
  475. * Use DOSREAD to fill primary buffer
  476. * Copy to Lexer buffer so that primary buffer is usable by F3 key.
  477. * Null terminate total input.
  478. * Scan buffer for NLN || ^Z
  479. * If none found
  480. * Error out
  481. * Else
  482. * Terminate statement at NLN or ^Z (exclude ^Z iteself)
  483. * If read was from file
  484. * Rewind to end of first statement
  485. * If file handle is STDIN
  486. * Echo statement to STDOUT
  487. * Else
  488. * Read first statement from string and reset pointer
  489. * Reset Lexer Buffer Pointer to start of buffer
  490. * Substitute for batch and environment variables (M026)
  491. * Reset Previous Lexer Buffer Pointer to start of buffer
  492. *
  493. */
  494. BOOL ReadFromStdInOkay = FALSE;
  495. void FillBuf()
  496. {
  497. long l; /* M004 - Data count in buffer */
  498. TCHAR *sptr; /* Copy of DataPtr */
  499. size_t i; /* Work variable */
  500. DWORD cnt; /* Count of input bytes */
  501. BOOL flag;
  502. //
  503. // clear this flag in case it was hit from previous command
  504. // if it is true we would not execute the next command
  505. //
  506. ResetCtrlC();
  507. LexBuffer[0] = *(LexBufPtr - 1);
  508. switch (DataFlag & FIRSTIME) {
  509. case READFILE:
  510. case READSTDIN:
  511. if ((DataFlag & FIRSTIME) == READSTDIN ||
  512. DataPtr == STDIN) {
  513. if (DataFlag & NOTFIRSTIME) {
  514. /* M025 */ PutStdOut(MSG_MS_MORE, NOARGS);
  515. } else {
  516. PrintPrompt();
  517. DataFlag |= NOTFIRSTIME;
  518. DEBUG((PAGRP, LFLVL, "FLBF: Reading stdin"));
  519. }
  520. }
  521. //
  522. // clear in case ^c seen while printing prompt
  523. //
  524. ResetCtrlC();
  525. DEBUG((PAGRP, LFLVL, "FLBF: Reading handle %d", DataPtr));
  526. //
  527. // If input is STDIN and piped or input is from a
  528. // device but not console input (flgwd == 1)
  529. //
  530. if ( ( DataPtr == STDIN ) && ( FileIsPipe( STDIN ) ||
  531. ( FileIsDevice( STDIN ) && (!(flgwd & 1)) ) ) ) {
  532. cnt = 0;
  533. while (
  534. ( cnt < LBUFLEN) && /* ##1 */
  535. ( (ReadBufFromFile(CRTTONT((CRTHANDLE)DataPtr),
  536. &FrsBuf[cnt], 1, (LPDWORD)&i)) != 0 ||
  537. GetLastError() == ERROR_MORE_DATA) &&
  538. ( i != 0 )
  539. ) {
  540. cnt++;
  541. if ( FrsBuf[cnt-1] == NLN ){
  542. break;
  543. } /* endif */
  544. }
  545. } else if ( ( DataPtr == STDIN ) &&
  546. FileIsDevice( STDIN ) &&
  547. (flgwd & 1) ) {
  548. //
  549. // Are reading from stdin and it is a device
  550. // (not a file) and it is console input
  551. //
  552. if ( KeysFlag ) {
  553. i = EditLine( (CRTHANDLE)DataPtr, FrsBuf, LBUFLEN, &cnt );
  554. }
  555. else {
  556. ResetCtrlC();
  557. if (ReadBufFromConsole(
  558. CRTTONT((CRTHANDLE)DataPtr),
  559. FrsBuf,
  560. LBUFLEN,
  561. &cnt) ) {
  562. //
  563. // Check that ^c's on the current line.
  564. // Could be the case where ^c thread
  565. // came in from a previous line
  566. //
  567. //
  568. // also if cnt is 0 then outpt crlf to
  569. // prevent two prompts on command line
  570. //
  571. if (cnt == 0) {
  572. if (GetLastError() == ERROR_OPERATION_ABORTED) {
  573. cmd_printf(CrLf);
  574. longjmp(CmdJBuf2, -1);
  575. }
  576. cmd_printf(CrLf);
  577. }
  578. i = 0;
  579. DEBUG((PAGRP, LFLVL, "FLBF: ReadFile %d bytes", cnt));
  580. } else {
  581. cnt = 0;
  582. i = GetLastError();
  583. DEBUG((PAGRP, LFLVL, "FLBF: ReadFile %d bytes and error %d", cnt, i));
  584. }
  585. }
  586. }
  587. else {
  588. flag = ReadBufFromFile(
  589. CRTTONT((CRTHANDLE)DataPtr),
  590. FrsBuf, LBUFLEN, (LPDWORD)&cnt);
  591. DEBUG((PAGRP, LFLVL, "FLBF: Read %d bytes", cnt));
  592. if (CtrlCSeen) {
  593. ResetCtrlC();
  594. longjmp(CmdJBuf2, -1);
  595. // Abort();
  596. }
  597. if (flag == 0 || (int)cnt <= 0) {
  598. cnt = 0;
  599. i = GetLastError();
  600. }
  601. else {
  602. i = 0;
  603. }
  604. }
  605. DEBUG((PAGRP, LFLVL, "FLBF: I made it here alive"));
  606. if (!cnt && DataPtr == STDIN) {
  607. DEBUG((PAGRP,LFLVL,"FLBF: ^Z from STDIN!"));
  608. DEBUG((PAGRP,LFLVL," READFILE retd %d",i));
  609. if (FileIsDevice(STDIN) && ReadFromStdInOkay) {
  610. DEBUG((PAGRP,LFLVL,"FLBF: Is device, fixing up buffer"));
  611. FrsBuf[0] = NLN;
  612. ++cnt;
  613. } else {
  614. DEBUG((PAGRP,LFLVL,"FLBF: Is file, aborting!!!"));
  615. ExitAbort(EXIT_EOF);
  616. }
  617. } else if (!ReadFromStdInOkay && cnt && DataPtr == STDIN) {
  618. ReadFromStdInOkay = TRUE;
  619. }
  620. cnt = LexCopy(LexBuf, FrsBuf, cnt);
  621. DEBUG((PAGRP, LFLVL, "FLBF: Received %d characters.", cnt));
  622. *(LexBuf+cnt) = NULLC; /* Term with NULL */
  623. /* Have total bytes read. Now scan for NLN or ^Z. Either means end of
  624. * input statement, neither in 128 bytes means buffer overflow error.
  625. */
  626. if((i = mystrcspn(LexBuf, TEXT("\n\032"))) < mystrlen(LexBuf)
  627. || cnt == 0) { /*M029*/
  628. DEBUG((PAGRP, LFLVL, "FLBF: Scan found %04x", *(LexBuf+i)));
  629. DEBUG((PAGRP, LFLVL, "FLBF: At position %d", i));
  630. sptr = LexBuf+i; /* Set pointer */
  631. if(*sptr == CTRLZ) {
  632. *sptr = NLN;
  633. }
  634. if(*sptr == NLN) { /* If \n, inc... */
  635. ++sptr; /* ...ptr & sub */
  636. l = cnt - ++i; /* ....whats used */
  637. /* M014 ends */ i = FILE_CURRENT;
  638. } else { /* If ^Z, go EOF */
  639. l = 0;
  640. i = FILE_END;
  641. }
  642. DEBUG((PAGRP,LFLVL,"FLBF: Changing %04x to NULLC",*(sptr-1)));
  643. *sptr = NULLC; /* Term valid input */
  644. if (!FileIsDevice((CRTHANDLE)DataPtr)) {
  645. SetFilePointer(CRTTONT((CRTHANDLE)DataPtr), -l, NULL, i);
  646. DEBUG((PAGRP, LFLVL, "FLBF: Rewound %ld", l));
  647. if ((DataPtr == STDIN) && (!Necho)) {
  648. CmdPutString( LexBuf );
  649. }
  650. }
  651. } else if(i >= LBUFLEN) { /*M029*/
  652. /* @@4 */ if ( global_dfvalue == READFILE )
  653. /* @@4 */ {
  654. /* @@4 */ if ( EchoFlag == E_ON )
  655. /* @@4 */ {
  656. /* @@4 */ DEBUG((PAGRP, LXLVL,
  657. /* @@4 */ "BLOOP: Displaying Statement."));
  658. /* @@4 */
  659. /* @@4 */ PrintPrompt();
  660. /* @@4 */ cmd_printf(&LexBuffer[1]);
  661. /* @@4 */ cmd_printf(CrLf);
  662. /* @@4 */ }
  663. /* @@4 */ PutStdErr(MSG_LINES_TOO_LONG, NOARGS);
  664. /* @@4 */ Abort();
  665. /* @@4 */ }
  666. PutStdErr(MSG_LINES_TOO_LONG, NOARGS);
  667. /* M028 */ if(DataPtr == STDIN && FileIsDevice((CRTHANDLE)DataPtr))
  668. FlushKB();
  669. longjmp(CmdJBuf2,-1);
  670. }
  671. break;
  672. case READSTRING:
  673. DEBUG((PAGRP, LFLVL, "FLBF: Reading string."));
  674. *(LexBuf+LBUFLEN) = NULLC; /* Term max string */
  675. _tcsncpy(LexBuf, (TCHAR *) DataPtr, LBUFLEN);
  676. DataPtr += mystrlen(LexBuf)*sizeof(TCHAR); /* Update DataPtr */
  677. DEBUG((PAGRP, LFLVL, "FLBF: New DataPtr = %ws", DataPtr));
  678. break;
  679. }
  680. LexBufPtr = LexBuf; /* M004 - Reset pointer */
  681. SubVar(); /* Sub env & batch vars (M026) */
  682. DEBUG((PAGRP, LFLVL, "FLBF: Buffer contents: `%ws'", LexBufPtr));
  683. /* Insure that when buffer is refilled, the previous token pointer is
  684. * reset to the start of the lexer buffer
  685. */
  686. PrevLexPtr = LexBufPtr;
  687. }
  688. /*** LexCopy - copy the lex buffer
  689. *
  690. * Purpose:
  691. * To copy the contents read into the dos buffer into LexBuf,
  692. * translating double byte spaces into regular spaces in the
  693. * process.
  694. * Input:
  695. * A to and from pointer and a byte count.
  696. * Returned:
  697. * A new byte count which might be smaller than that passed in.
  698. */
  699. int LexCopy(to, from, count)
  700. TCHAR *to, *from;
  701. int count;
  702. {
  703. _tcsncpy( to, from, count );
  704. return count;
  705. }
  706. BOOLEAN PromptValid;
  707. TCHAR PromptVariableBuffer[ 512 ];
  708. TCHAR PromptBuffer[ 1024 ];
  709. void
  710. PrintPrompt()
  711. /*++
  712. Routine Description:
  713. To print Command's main input prompt and to interpret the special
  714. characters in it (see MSDOS manual for list of special prompt
  715. characters).
  716. An array of PROMPT_ENTRY structures called PromptTable is searched for
  717. the special characters. If a match is found , then either print out
  718. the special character if the format field is PLITFLAG or do some
  719. sort of special processing to print out the prompt string such as
  720. get time of day etc.
  721. Arguments:
  722. Return Value:
  723. --*/
  724. {
  725. TCHAR *pszPrompt;
  726. TCHAR *s;
  727. int nLeft, nUsed;
  728. ULONG idx;
  729. #if defined(FE_SB)
  730. // This local variable is used for determine the last
  731. // character is full width character (=DBCS) or not.
  732. TCHAR chLast = NULLC;
  733. #endif
  734. if (CtrlCSeen) {
  735. PutStdOut(MSG_C, NOARGS);
  736. ResetCtrlC();
  737. // Abort();
  738. }
  739. //
  740. // The newline which must preceed the prompt is tied to prompt rather than to
  741. // command completion in Dispatch.
  742. //
  743. // Also return without newline or prompt if echo status is "echo off".
  744. //
  745. if (EchoFlag == E_OFF) {
  746. return;
  747. }
  748. if (!NulNode) {
  749. cmd_printf(CrLf);
  750. }
  751. if ( PromptValid ) {
  752. pszPrompt = PromptVariableBuffer;
  753. }
  754. else {
  755. //
  756. // Fetch user prompt string from environment (should be PROMPT)
  757. //
  758. pszPrompt = GetEnvVar(PromptStr);
  759. if ( pszPrompt ) {
  760. mystrcpy( PromptVariableBuffer, pszPrompt);
  761. pszPrompt = PromptVariableBuffer;
  762. PromptValid = TRUE;
  763. }
  764. }
  765. //
  766. // refetch the current directory, since we may have lost the
  767. // drive letter due to net disconnect
  768. //
  769. GetDir(CurDrvDir, GD_DEFAULT);
  770. DEBUG((PAGRP, LFLVL, "PRINTPROMPT: pszPrompt = `%ws'", pszPrompt));
  771. s = PromptBuffer;
  772. *s = NULLC;
  773. nLeft = sizeof(PromptBuffer) / sizeof(TCHAR);
  774. //
  775. // Check if there was a prompt string.
  776. // If there is not prompt string then just print current drive
  777. //
  778. if (!pszPrompt || !*pszPrompt) {
  779. nUsed = _sntprintf( s, nLeft, Fmt27, CurDrvDir);
  780. s += nUsed;
  781. nLeft -= nUsed;
  782. } else {
  783. //
  784. // Loop through interpreting prompt string
  785. //
  786. for (; *pszPrompt; pszPrompt++) {
  787. //
  788. // Look for the escape character in prompt for special
  789. // processing
  790. //
  791. if (*pszPrompt != PROMPTESC) {
  792. nUsed = _sntprintf( s, nLeft, Fmt19, *pszPrompt);
  793. s += nUsed;
  794. nLeft -= nUsed;
  795. #if defined(FE_SB)
  796. // If character is full width character, mark it.
  797. if (IsDBCSCodePage() &&
  798. IsFullWidth(*pszPrompt))
  799. chLast = *pszPrompt;
  800. else
  801. chLast = NULLC;
  802. #endif
  803. } else {
  804. //
  805. // There is an escape character in prompt string.
  806. // Try to find a match for next character after escape
  807. // character from prompt table.
  808. //
  809. pszPrompt++;
  810. for (idx = 0; PromptTable[idx].Char != NULLC; idx++)
  811. if (_totupper(*pszPrompt) == PromptTable[idx].Char) {
  812. break;
  813. }
  814. if (PromptTable[idx].Char == NULLC) {
  815. //
  816. // Could find no match for escape. Exit loop to print
  817. // what we have so far
  818. //
  819. break;
  820. } else {
  821. if (PromptTable[idx].Format == PLITFLAG) {
  822. nUsed = _sntprintf( s, nLeft, Fmt19, PromptTable[idx].Literal);
  823. s += nUsed;
  824. nLeft -= nUsed;
  825. } else {
  826. switch (PromptTable[idx].Format) {
  827. case PTIMFLAG:
  828. nUsed = PrintTime(NULL, PT_TIME, s, nLeft);
  829. s += nUsed;
  830. nLeft -= nUsed;
  831. break;
  832. case PDATFLAG:
  833. nUsed = PrintDate(NULL, PD_DATE, s, nLeft);
  834. s += nUsed;
  835. nLeft -= nUsed;
  836. break;
  837. case PPATFLAG:
  838. nUsed = _sntprintf( s, nLeft, TEXT( "%s" ), CurDrvDir);
  839. s += nUsed;
  840. nLeft -= nUsed;
  841. break;
  842. case PVERFLAG:
  843. {
  844. TCHAR VersionFormat[32];
  845. PTCHAR Message;
  846. GetVersionString(
  847. VersionFormat,
  848. sizeof( VersionFormat ) / sizeof( VersionFormat[0] ));
  849. Message = GetMsg( MSG_MS_DOS_VERSION, VersionFormat );
  850. nUsed = _sntprintf( s, nLeft, TEXT( "%s" ), Message );
  851. s += nUsed;
  852. nLeft -= nUsed;
  853. LocalFree( Message );
  854. }
  855. break;
  856. case PBAKFLAG:
  857. #if defined(FE_SB) // PrintPrompt()
  858. // if the last character is full width character.
  859. // we should delete 2 bytes.
  860. if (chLast != NULLC)
  861. nUsed = _sntprintf( s, nLeft, DDBkSpc);
  862. else
  863. nUsed = _sntprintf( s, nLeft, DBkSpc);
  864. #else
  865. nUsed = _sntprintf( s, nLeft, DBkSpc);
  866. #endif
  867. s += nUsed;
  868. nLeft -= nUsed;
  869. break;
  870. case PNLNFLAG:
  871. nUsed = _sntprintf( s, nLeft, CrLf);
  872. s += nUsed;
  873. nLeft -= nUsed;
  874. break;
  875. case PDPTFLAG:
  876. //
  877. // $+ generates from zero to N plus characters, depending upon
  878. // the depth of the PUSHD directory stack.
  879. //
  880. if (fEnableExtensions) {
  881. int Depth = GetDirStackDepth( );
  882. while (nLeft && Depth--) {
  883. *s++ = TEXT( '+' );
  884. nLeft--;
  885. }
  886. }
  887. break;
  888. case PNETFLAG:
  889. //
  890. // $m generates the empty string if the current drive is not a
  891. // network drive. If it is, then $m generates the \\server\share
  892. // name with a trailing space.
  893. //
  894. if (!fEnableExtensions) {
  895. break;
  896. }
  897. {
  898. TCHAR CurDrive[4];
  899. TCHAR NetPath[MAX_PATH];
  900. DWORD n, Err;
  901. _tcsncpy( CurDrive, CurDrvDir, 2 );
  902. CurDrive[2] = BSLASH;
  903. CurDrive[3] = NULLC;
  904. if (GetDriveType( CurDrive ) != DRIVE_REMOTE) {
  905. break;
  906. }
  907. CurDrive[2] = NULLC;
  908. n = MAX_PATH;
  909. try {
  910. Err = WNetGetConnection( CurDrive, NetPath, &n );
  911. } except( Err = GetExceptionCode( ), EXCEPTION_EXECUTE_HANDLER) {
  912. }
  913. switch (Err) {
  914. case NO_ERROR:
  915. nUsed = _sntprintf( s, nLeft, TEXT("%s "), NetPath);
  916. s += nUsed;
  917. nLeft -= nUsed;
  918. break;
  919. case ERROR_NOT_CONNECTED:
  920. break;
  921. default:
  922. nUsed = _sntprintf( s, nLeft, TEXT("Unknown"));
  923. s += nUsed;
  924. nLeft -= nUsed;
  925. break;
  926. }
  927. }
  928. break;
  929. default:
  930. nUsed = _sntprintf( s, nLeft, Fmt19, CurDrvDir[0]);
  931. s += nUsed;
  932. nLeft -= nUsed;
  933. }
  934. }
  935. }
  936. }
  937. } // for
  938. } // else
  939. *s = NULLC;
  940. CmdPutString( PromptBuffer );
  941. //
  942. // If ^c seen while printing prompt blow it away
  943. //
  944. if (CtrlCSeen) {
  945. ResetCtrlC();
  946. }
  947. }
  948. /*** IsData - check the input buffer
  949. *
  950. * Purpose:
  951. * Check the lexer's input buffer to see if there is data in it.
  952. *
  953. * int IsData()
  954. *
  955. * Returns:
  956. * TRUE if the buffer has data in it.
  957. * FALSE if the buffer is empty.
  958. *
  959. */
  960. int IsData()
  961. {
  962. DEBUG((PAGRP, LXLVL, "ISDATA: *LexBufPtr = %04x", *LexBufPtr));
  963. if (*LexBufPtr)
  964. return(TRUE);
  965. return(FALSE);
  966. }
  967. /*** SubVar - Substitute for environment variables. (M004)
  968. *
  969. * Purpose:
  970. * This function scans the lexer input buffer looking for percent
  971. * signs and substituting batch variables and environment variables
  972. * as they are found.
  973. *
  974. * void SubVar()
  975. *
  976. * NOTES:
  977. * - This function does not return if expansion causes length to exceed
  978. * maximum line length (LBUFLEN).
  979. * - M026 caused complete rewrite to perform batch variable substitution
  980. * at the lexer stage rather than in batch processing. Note that the
  981. * printing of error messages can now be either line too long or token
  982. * too long, so error printing occurs before the longjmp() call.
  983. */
  984. void SubVar()
  985. {
  986. PTCHAR lxtmp;
  987. int dlen; /* Temps & counters */
  988. int j, slen;
  989. TCHAR *srcp;
  990. TCHAR *substr = NULL; /* Possible Env Var pointer */
  991. TCHAR c; /* Temp byte holder */
  992. srcp = lxtmp = mkstr( (LBUFLEN + 1) * sizeof( TCHAR ));
  993. if (lxtmp == NULL) {
  994. memset( LexBuffer, 0, sizeof( LexBuffer ));
  995. LexBufPtr = LexBuf;
  996. longjmp( CmdJBuf2, -1 );
  997. }
  998. mystrcpy(srcp,LexBufPtr); /* Make a copy of the input */
  999. DEBUG((PAGRP, LXLVL, "SBENV: Copy = %ws", srcp));
  1000. dlen = j = slen = 0; /* Zero the counters */
  1001. while ((c = *srcp++) && dlen <= LBUFLEN + 1) {
  1002. if (c != PERCENT) {
  1003. *LexBufPtr++ = c;
  1004. ++dlen;
  1005. if (c == NLN) /* Stop subst. if statement end */
  1006. break;
  1007. continue;
  1008. }
  1009. DEBUG((PAGRP,LXLVL,"SBVAR: Found `%%' in input"));
  1010. DEBUG((PAGRP,LXLVL,"SBVAR: Current pair is `%c%c'",c,*srcp));
  1011. if (CurrentBatchFile && *srcp == PERCENT) {
  1012. DEBUG((PAGRP,LXLVL,"SBVAR: Found `%%%%' in batch file"));
  1013. *LexBufPtr++ = *srcp++;
  1014. ++dlen;
  1015. continue;
  1016. }
  1017. //
  1018. // If inside a command script and extensions are enabled,
  1019. // expand %* into all the arguments (%1 through %n).
  1020. //
  1021. if (CurrentBatchFile && fEnableExtensions && *srcp == STAR) {
  1022. ++srcp; /* Kick past star */
  1023. slen = mystrlen(CurrentBatchFile->orgargs);
  1024. substr = CurrentBatchFile->orgargs;
  1025. DEBUG((PAGRP,LXLVL,"SBVAR: Found batch var %*"));
  1026. DEBUG((PAGRP,LXLVL,"SBVAR: - len = %d", slen));
  1027. DEBUG((PAGRP,LXLVL,"SBVAR: - var = %ws", substr));
  1028. if (slen > 0) {
  1029. if (dlen+slen > MAXTOKLEN) {
  1030. DEBUG((PAGRP,LXLVL,"SBVAR: Too Long!"));
  1031. _tcsncpy(LexBufPtr,substr,MAXTOKLEN - dlen);
  1032. LexBuf[MAXTOKLEN] = NULLC;
  1033. PutStdErr(MSG_TOKEN_TOO_LONG, ONEARG,LexBuf);
  1034. FreeStr( lxtmp );
  1035. longjmp(CmdJBuf2,-1);
  1036. }
  1037. mystrcpy(LexBufPtr, substr);
  1038. dlen += slen;
  1039. LexBufPtr += slen;
  1040. DEBUG((PAGRP,LXLVL,"SBVAR: Subst complete; dest = `%ws'", LexBuf));
  1041. } else {
  1042. DEBUG((PAGRP,LXLVL,"SBVAR: Var %* undefined"));
  1043. }
  1044. continue;
  1045. }
  1046. //
  1047. // If inside a command script attempt to expand variable references
  1048. // of the form %n where n is a digit from 0 to 9
  1049. //
  1050. // If not in a command script or not a variable reference see if
  1051. // this is an environment variable expansion request.
  1052. //
  1053. if ((CurrentBatchFile != NULL
  1054. && (substr = MSCmdVar( &CmdJBuf2, srcp, &j, TEXT("0123456789"), CurrentBatchFile->aptrs )))
  1055. || (substr = MSEnvVar( &CmdJBuf2, srcp, &j, PERCENT )) != NULL ) {
  1056. DEBUG((PAGRP,LXLVL,"SBVAR: Found var %ws", substr));
  1057. //
  1058. // Either variable reference or environment variable reference.
  1059. // Copy the result to the input buffer
  1060. //
  1061. slen = mystrlen( substr );
  1062. dlen += slen;
  1063. if (dlen > LBUFLEN + 1) {
  1064. PutStdErr( MSG_LINES_TOO_LONG, NOARGS );
  1065. FreeStr( lxtmp );
  1066. longjmp( CmdJBuf2, -1 );
  1067. }
  1068. mystrcpy( LexBufPtr, substr );
  1069. LexBufPtr += slen;
  1070. srcp += j;
  1071. } else {
  1072. DEBUG((PAGRP,LXLVL,"SBVAR: No var found"));
  1073. //
  1074. // Variable not found. If inside of command script, toss
  1075. // the variable reference in the bit bucket. If not in a
  1076. // command script pass the characters that make up the reference
  1077. // into the input buffer. User will see their mistake shortly.
  1078. //
  1079. if (CurrentBatchFile) {
  1080. srcp += j;
  1081. } else {
  1082. *LexBufPtr++ = c;
  1083. dlen++;
  1084. }
  1085. }
  1086. }
  1087. *LexBufPtr = NULLC; /* Terminate Statement */
  1088. LexBufPtr = LexBuf; /* Reset Pointer to start */
  1089. if (dlen > LBUFLEN+1) { /* Statement too long?? */
  1090. *LexBufPtr = NULLC; /* If so, kill line, print err */
  1091. PutStdErr(MSG_LINES_TOO_LONG, NOARGS);
  1092. FreeStr( lxtmp );
  1093. longjmp(CmdJBuf2,-1);
  1094. }
  1095. FreeStr( lxtmp );
  1096. }
  1097. /*** MSEnvVar - Does environment variable substitution
  1098. *
  1099. * Purpose:
  1100. * When percent signs are found in the newly filled lexer buffer,
  1101. * this function is called to determine if there is an environment
  1102. * variable substitution possible.
  1103. *
  1104. * TCHAR *MSEnvVar(TCHAR *str, int *supdate, TCHAR delim)
  1105. *
  1106. * Args:
  1107. * errjmp - optional pointer to jmp_buf for errors
  1108. * str - pointer to a possible environment variable name
  1109. * supdate - location to place env variable name length in
  1110. * delim - delimiter character to look for (e.g. PERCENT)
  1111. *
  1112. * Returns:
  1113. * If there is no ending delim,
  1114. * set supdate to 0
  1115. * return NULL
  1116. * else
  1117. * set supdate to the enclosed string length
  1118. * if the string is not an environment variable
  1119. * return NULL
  1120. * else
  1121. * return a pointer to the replacement string
  1122. *
  1123. * Notes:
  1124. * - M026 changed the way this function works so that supdate will
  1125. * contain the string length if any string was found. This allows
  1126. * the string to be deleted if within a batch file.
  1127. *
  1128. */
  1129. TCHAR *MSEnvVar(errjmp, str, supdate, delim)
  1130. jmp_buf *errjmp;
  1131. TCHAR *str;
  1132. int *supdate;
  1133. const TCHAR delim;
  1134. {
  1135. PTCHAR TempBuf;
  1136. TCHAR *w0; /* Points to ending delim */
  1137. TCHAR *w1; /* Will hold ptr to env var value */
  1138. TCHAR *w2;
  1139. TCHAR *SearchString;
  1140. TCHAR *SearchStringEnd;
  1141. int SearchLength;
  1142. TCHAR *ReplaceString;
  1143. TCHAR *ReplaceStringEnd;
  1144. int ReplaceLength;
  1145. TCHAR TerminatingChar;
  1146. int noff, nlen;
  1147. BOOL PrefixMatch;
  1148. //
  1149. // Allocate temp buffer space
  1150. //
  1151. TempBuf = mkstr( LBUFLEN * sizeof( TCHAR ));
  1152. if (TempBuf == NULL) {
  1153. if (errjmp != NULL) {
  1154. longjmp( *errjmp, -1 );
  1155. }
  1156. *supdate = 0;
  1157. return NULL;
  1158. }
  1159. //
  1160. // Search for character that terminates the environment variable.
  1161. // This can be either the passed-in delimiter or a COLON
  1162. // (when extensions are enabled) as long as the colon isn't
  1163. // immediately followed by the delimiter. In this case, we
  1164. // treat the colon as part of the environment variable name.
  1165. //
  1166. w0 = str;
  1167. while (
  1168. // Stop at end of string
  1169. *w0 != NULLC &&
  1170. // Stop at delimiter
  1171. *w0 != delim &&
  1172. // Stop if extensions and colon not followed by delimiter
  1173. (!fEnableExtensions || w0[0] != COLON || w0[1] == delim)) {
  1174. w0++;
  1175. }
  1176. DEBUG((PAGRP, LFLVL, "MSENVVAR: *w0 = %04x", *w0));
  1177. //
  1178. // If there is no further text or if there are two delimiters
  1179. // in a row then the env var is not well formed and we terminate
  1180. //
  1181. if ( *w0 == NULLC || w0 == str) {
  1182. *supdate = 0;
  1183. FreeStr( TempBuf );
  1184. return NULL;
  1185. }
  1186. //
  1187. // We have pointers to the start and end of the environment variable name.
  1188. // Terminate the name, look it up in the environment and restore the
  1189. // original name
  1190. //
  1191. TerminatingChar = *w0;
  1192. *w0 = NULLC;
  1193. DEBUG((PAGRP, LFLVL, "MSENVVAR: Possible env var = `%ws'", str));
  1194. w1 = GetEnvVar(str);
  1195. //
  1196. // w1 points to the environment variable value in a buffer that we
  1197. // can use. This static buffer is good until the next GetEnvVar call.
  1198. //
  1199. *w0++ = TerminatingChar;
  1200. //
  1201. // If Command Extensions are enabled, then we support munging the
  1202. // output of environment variable expansion. Here is the supported
  1203. // syntax, all keyed off a trailing COLON character at the end of
  1204. // the environment variable name. Note, that %FOO:% is treated
  1205. // as it was before.
  1206. //
  1207. // Environment variable substitution has been enhanced as follows:
  1208. //
  1209. // %PATH:str1=str2%
  1210. //
  1211. // would expand the PATH environment variable, substituting each
  1212. // occurrence of "str1" in the expanded result with "str2". "str2" can
  1213. // be the empty string to effectively delete all occurrences of "str1"
  1214. // from the expanded output. Additionally:
  1215. //
  1216. // %PATH:~10,5%
  1217. //
  1218. // would expand the PATH environment variable, and then use only the 5
  1219. // characters that begin at the 11th character of the expanded result.
  1220. // If the ,5 is left off, it will take the entire remainder of the
  1221. // expanded result.
  1222. //
  1223. //
  1224. // If we aren't doing extensions or we have a simple replacement
  1225. // or the named environment variable has no value then we're done.
  1226. //
  1227. // Note that we must do this for non-existent environment variables
  1228. // since the following will generate big problems:
  1229. // for %i in (c:foo) do ...
  1230. // The % will introduce an environment variable and the : will indicate
  1231. // a substitution. If we were to delete the above text, things break.
  1232. //
  1233. if (!fEnableExtensions
  1234. || TerminatingChar != COLON
  1235. || w1 == NULL) {
  1236. *supdate = (int)(w0 - str);
  1237. FreeStr( TempBuf );
  1238. return w1;
  1239. }
  1240. if (*w0 == EQI) {
  1241. //
  1242. // We are extracting a substring %PATH:~10,5%
  1243. //
  1244. w0 += 1;
  1245. //
  1246. // Parse the offset within the environment string. A
  1247. // negative number indicates an offset from the right end of
  1248. // the string.
  1249. //
  1250. noff = _tcstol( w0, &w0, 0 );
  1251. if (noff < 0) {
  1252. noff += _tcslen( w1 );
  1253. }
  1254. noff = max( 0, min( noff, (int)_tcslen( w1 )));
  1255. //
  1256. // If a comma is present, what follows is the maximum length
  1257. // to be copied. Parse off this number, negative turns into
  1258. // an offset from the right of the string
  1259. //
  1260. if (*w0 == COMMA) {
  1261. //
  1262. // The length is specified; extract it and bound it by the
  1263. // length of the entire string
  1264. //
  1265. w0 += 1;
  1266. nlen = _tcstol( w0, &w0, 0 );
  1267. if (nlen < 0) {
  1268. nlen += _tcslen( w1 + noff );
  1269. }
  1270. nlen = max( 0, nlen );
  1271. nlen = min( nlen, (int)_tcslen( w1 + noff ));
  1272. } else {
  1273. nlen = _tcslen( w1 + noff );
  1274. }
  1275. //
  1276. // Copy from the string up to the requested length or the
  1277. // terminating NUL
  1278. //
  1279. _tcsncpy( w1, w1+noff, nlen );
  1280. //
  1281. // Terminate the string. We need to be extra careful here since the string
  1282. // may be the null string above which is in READ/ONLY memory.
  1283. //
  1284. if (w1[nlen] != NULLC) {
  1285. w1[nlen] = NULLC;
  1286. }
  1287. //
  1288. // We must have a terminating delimiter to make this a valid
  1289. // substitution.
  1290. //
  1291. if (*w0++ != delim) {
  1292. *supdate = 0;
  1293. FreeStr( TempBuf );
  1294. return NULL;
  1295. }
  1296. *supdate = (int)(w0 - str);
  1297. } else {
  1298. //
  1299. // Not extracting a string, so must be search and replacing
  1300. //
  1301. // %PATH:str1=str2%
  1302. //
  1303. // Test for asterisk and skip forward looking for = sign or
  1304. // end of string
  1305. //
  1306. if (*w0 == STAR) {
  1307. w0++;
  1308. PrefixMatch = TRUE;
  1309. } else {
  1310. PrefixMatch = FALSE;
  1311. }
  1312. SearchString = w0;
  1313. while (*w0 != NULLC && *w0 != EQ) {
  1314. w0 += 1;
  1315. }
  1316. //
  1317. // No equal sign is no substitution
  1318. //
  1319. if (*w0 == NULLC) {
  1320. *supdate = 0;
  1321. FreeStr( TempBuf );
  1322. return NULL;
  1323. }
  1324. SearchStringEnd = w0;
  1325. SearchLength = (int)(SearchStringEnd - SearchString);
  1326. //
  1327. // An empty search string is a syntax error
  1328. //
  1329. if (SearchLength == 0) {
  1330. if (errjmp != NULL) {
  1331. PutStdErr( MSG_SYNERR_GENL, ONEARG, SearchStringEnd );
  1332. FreeStr( TempBuf );
  1333. longjmp( *errjmp, -1 );
  1334. }
  1335. *supdate = 0;
  1336. FreeStr( TempBuf );
  1337. return NULL;
  1338. }
  1339. //
  1340. // Find end of replacement string
  1341. //
  1342. w0++;
  1343. ReplaceString = w0;
  1344. while (*w0 && *w0 != delim) {
  1345. w0 += 1;
  1346. }
  1347. //
  1348. // No terminating delimiter is no substitution
  1349. //
  1350. if (*w0 == NULLC) {
  1351. *supdate = 0;
  1352. FreeStr( TempBuf );
  1353. return NULL;
  1354. }
  1355. ReplaceStringEnd = w0++;
  1356. ReplaceLength = (int)(ReplaceStringEnd - ReplaceString);
  1357. //
  1358. // Everything is well-formed now. Set the correct number
  1359. // of source chars to be skipped.
  1360. //
  1361. *supdate = (int)(w0 - str);
  1362. //
  1363. // If there's actually a string to replace
  1364. //
  1365. if (*w1 != TEXT( '\0' )) {
  1366. //
  1367. // Copy env var into temp buffer so that we can
  1368. // perform a replacement copy back to original buffer
  1369. //
  1370. _tcscpy( TempBuf, w1 );
  1371. w2 = TempBuf;
  1372. w0 = w1;
  1373. //
  1374. // Walk forward through w2 trying to find instances of
  1375. // the search string and replacing them into w2
  1376. //
  1377. while (TRUE) {
  1378. if (!_tcsnicmp( w2, SearchString, SearchLength )) {
  1379. //
  1380. // A prefix match means replacing everything from the
  1381. // beginning of the string through the end of the search
  1382. // match with the replacement string and then bailing
  1383. //
  1384. if (PrefixMatch) {
  1385. _tcsncpy( w1, ReplaceString, ReplaceLength );
  1386. _tcscpy( w1 + ReplaceLength, w2 + SearchLength );
  1387. break;
  1388. }
  1389. //
  1390. // Otherwise, we copy in the replacement string and
  1391. // skip over the search string
  1392. //
  1393. _tcsncpy( w0, ReplaceString, ReplaceLength );
  1394. w0 += ReplaceLength;
  1395. w2 += SearchLength;
  1396. } else {
  1397. //
  1398. // Not a match, copy this one character
  1399. //
  1400. *w0++ = *w2++;
  1401. if (w0[-1] == NULLC) {
  1402. break;
  1403. }
  1404. }
  1405. }
  1406. }
  1407. }
  1408. FreeStr( TempBuf );
  1409. return w1;
  1410. }
  1411. struct {
  1412. ULONG dwAttributeFlag;
  1413. TCHAR chAttributeChar;
  1414. } rgAttrToCharMap[] = {
  1415. {FILE_ATTRIBUTE_DIRECTORY , TEXT('d')},
  1416. {FILE_ATTRIBUTE_READONLY , TEXT('r')},
  1417. {FILE_ATTRIBUTE_ARCHIVE , TEXT('a')},
  1418. {FILE_ATTRIBUTE_HIDDEN , TEXT('h')},
  1419. {FILE_ATTRIBUTE_SYSTEM , TEXT('s')},
  1420. {FILE_ATTRIBUTE_COMPRESSED, TEXT('c')},
  1421. {FILE_ATTRIBUTE_OFFLINE , TEXT('o')},
  1422. {FILE_ATTRIBUTE_TEMPORARY , TEXT('t')},
  1423. {FILE_ATTRIBUTE_REPARSE_POINT, TEXT('l')},
  1424. {0, NULLC}
  1425. };
  1426. /*** MSCmdVar - Does command variable substitution
  1427. *
  1428. * Purpose:
  1429. * When percent signs are found in the newly filled lexer buffer,
  1430. * this function is called to determine if there is a command processor
  1431. * variable substitution possible.
  1432. *
  1433. * TCHAR *MSCmdVar(TCHAR *srcp, int *supdate, TCHAR *vars, TCHAR *subs[])
  1434. *
  1435. * Args:
  1436. * errjmp - optional pointer to jmp_buf for errors
  1437. * srcp - pointer to a possible variable name
  1438. * supdate - location to place variable name length in
  1439. * vars - array of character variable names to look for
  1440. * subs - array of substitution strings for each variable name.
  1441. *
  1442. * Returns:
  1443. * If there is no ending delimiter
  1444. * set supdate to 0
  1445. * return NULL
  1446. * else
  1447. * set supdate to the enclosed string length
  1448. * if the string is not a variable
  1449. * return NULL
  1450. * else
  1451. * return a pointer to the replacement string
  1452. */
  1453. TCHAR *MSCmdVar(errjmp, srcp, supdate, vars, subs)
  1454. jmp_buf *errjmp;
  1455. TCHAR *srcp;
  1456. int *supdate;
  1457. TCHAR *vars;
  1458. TCHAR *subs[];
  1459. {
  1460. static TCHAR ValidModifiers[] = TEXT( "fdpnxsatz" );
  1461. TCHAR *substr;
  1462. TCHAR *s1;
  1463. substr = NULL;
  1464. *supdate = 0;
  1465. //
  1466. // If extensions are enabled, we support the following syntax for expanding
  1467. // variable values:
  1468. // %~fi - expands %i to a fully qualified path name
  1469. // %~di - expands %i to a drive letter only
  1470. // %~pi - expands %i to a path only
  1471. // %~ni - expands %i to a file name only
  1472. // %~xi - expands %i to a file extension only
  1473. // %~si - changes the meaning of n and x options to
  1474. // reference the short name instead
  1475. // %~$PATH:i - searches the directories listed in the PATH
  1476. // environment variable and expands %i to the
  1477. // fully qualified name of the first one found.
  1478. // If the environment variable name is not
  1479. // defined or the file is not found by the
  1480. // search, then this modifier expands to the
  1481. // empty string
  1482. //
  1483. // The modifiers can be combined to get compound results:
  1484. //
  1485. // %~dpi - expands %i to a drive letter and path only
  1486. // %~nxi - expands %i to a file name and extension only
  1487. // %~dp$PATH:i - searches the directories listed in the PATH
  1488. // environment variable for %i and expands to the
  1489. // drive letter and path of the first one found.
  1490. //
  1491. //
  1492. // See if new syntax is being specified
  1493. //
  1494. if (fEnableExtensions && *srcp == EQI) {
  1495. #define PATHMODIFIER 0x8000
  1496. #define ATTRMODIFIER 0x4000
  1497. #define WANTFULLPATH (0x0001)
  1498. #define WANTDRIVE (0x0002)
  1499. #define WANTPATH (0x0004)
  1500. #define WANTNAME (0x0008)
  1501. #define WANTEXTENSION (0x0010)
  1502. #define WANTSHORTNAME (0x0020)
  1503. #define PATHBITS (0x00FF)
  1504. #define WANTATTRIB (0x0100)
  1505. #define WANTTIMESTAMP (0x0200)
  1506. #define WANTSIZE (0x0400)
  1507. #define ATTRBITS (0x0F00)
  1508. ULONG Modifiers = 0;
  1509. ULONG LastModifierValue = 0;
  1510. TCHAR *LastVariablePosition;
  1511. TCHAR FullPath[ 2 * MAX_PATH], NullExt;
  1512. TCHAR *FilePart, *Extension, *StartPath, *VarName, *StartBuf;
  1513. const TCHAR *SearchVar;
  1514. DWORD FullPathLength;
  1515. FullPathLength = 0;
  1516. SearchVar = NULL;
  1517. StartBuf = srcp-1;
  1518. s1 = NULL;
  1519. srcp++;
  1520. LastVariablePosition = srcp;
  1521. //
  1522. // Walk forward through the string, remembering where we see variables
  1523. // and saving the position and status of the modifiers at the most
  1524. // recent variable. We stop when we encounter a char that cannot be a
  1525. // modifier. If that char is a variable, we use it. Otherwise, we
  1526. // reset the pointer to the most recently found variable.
  1527. //
  1528. while (*srcp != TEXT( '\0' ) &&
  1529. *srcp != TEXT( '$' ) &&
  1530. _tcsrchr( ValidModifiers, _totlower( *srcp )) != NULL) {
  1531. //
  1532. // The character we are looking at is a valid modifier. If it is
  1533. // a variable, save this location
  1534. //
  1535. if ( _tcsrchr( vars, *srcp ) != NULL) {
  1536. LastVariablePosition = srcp;
  1537. LastModifierValue = Modifiers;
  1538. }
  1539. //
  1540. // Add up the functions that this modifier demands
  1541. //
  1542. switch (_totlower( *srcp )) {
  1543. case TEXT('f'): Modifiers |= PATHMODIFIER | WANTFULLPATH; break;
  1544. case TEXT('d'): Modifiers |= PATHMODIFIER | WANTDRIVE; break;
  1545. case TEXT('p'): Modifiers |= PATHMODIFIER | WANTPATH; break;
  1546. case TEXT('n'): Modifiers |= PATHMODIFIER | WANTNAME; break;
  1547. case TEXT('x'): Modifiers |= PATHMODIFIER | WANTEXTENSION; break;
  1548. case TEXT('s'): Modifiers |= PATHMODIFIER | WANTSHORTNAME; break;
  1549. case TEXT('a'): Modifiers |= ATTRMODIFIER | WANTATTRIB; break;
  1550. case TEXT('t'): Modifiers |= ATTRMODIFIER | WANTTIMESTAMP; break;
  1551. case TEXT('z'): Modifiers |= ATTRMODIFIER | WANTSIZE; break;
  1552. default:
  1553. //
  1554. // This had better not occur
  1555. //
  1556. if (errjmp != NULL) {
  1557. PutStdErr(MSG_PATH_OPERATOR_INVALID, ONEARG, StartBuf);
  1558. longjmp(*errjmp,-1);
  1559. } else {
  1560. return NULL;
  1561. }
  1562. }
  1563. srcp++;
  1564. }
  1565. //
  1566. // *srcp is no longer pointing at a valid modifier.
  1567. // It may be:
  1568. // EOS - we back up to the previously saved variable position
  1569. // $ - perform special environment variable scanning
  1570. // some other char -
  1571. // if this is not a variable, we back up to the prevously
  1572. // saved variable position
  1573. //
  1574. if (*srcp == TEXT( '\0' )) {
  1575. //
  1576. // Restore bak to last found variable
  1577. //
  1578. srcp = LastVariablePosition;
  1579. Modifiers = LastModifierValue;
  1580. } else if (*srcp == TEXT( '$' )) {
  1581. //
  1582. // Save beginning of environment variable
  1583. //
  1584. VarName = ++srcp;
  1585. //
  1586. // Look for : If not present, then error
  1587. //
  1588. srcp = _tcschr( srcp, COLON );
  1589. if (srcp == NULL) {
  1590. if (errjmp != NULL) {
  1591. PutStdErr(MSG_PATH_OPERATOR_INVALID, ONEARG, StartBuf);
  1592. longjmp(*errjmp,-1);
  1593. } else {
  1594. return NULL;
  1595. }
  1596. }
  1597. //
  1598. // Look up environment variable to search
  1599. //
  1600. *srcp = NULLC;
  1601. SearchVar = MyGetEnvVarPtr( VarName );
  1602. if (SearchVar == NULL) {
  1603. SearchVar = (TCHAR *)-1;
  1604. }
  1605. *srcp++ = COLON;
  1606. Modifiers |= PATHMODIFIER;
  1607. } else if (_tcsrchr( vars, *srcp) == NULL) {
  1608. //
  1609. // Restore back to last found variable
  1610. //
  1611. srcp = LastVariablePosition;
  1612. Modifiers = LastModifierValue;
  1613. }
  1614. //
  1615. // If we didn't find a variable, bail out.
  1616. //
  1617. s1 = _tcsrchr( vars, *srcp );
  1618. if (s1 == NULL) {
  1619. if (errjmp != NULL) {
  1620. PutStdErr(MSG_PATH_OPERATOR_INVALID, ONEARG, StartBuf);
  1621. longjmp(*errjmp,-1);
  1622. } else {
  1623. return NULL;
  1624. }
  1625. }
  1626. //
  1627. // Get current value of variable (strip surrounding quotes)
  1628. //
  1629. substr = subs[s1 - vars];
  1630. if (substr != NULL) {
  1631. if (*substr == QUOTE) {
  1632. substr = dupstr( substr + 1 );
  1633. if (substr == NULL) {
  1634. if (errjmp != NULL) {
  1635. PutStdErr( ERROR_NOT_ENOUGH_MEMORY, NOARGS );
  1636. longjmp( *errjmp, -1 );
  1637. } else {
  1638. return NULL;
  1639. }
  1640. }
  1641. s1 = lastc( substr );
  1642. if (*s1 == QUOTE)
  1643. *s1 = NULLC;
  1644. } else if (*srcp == TEXT('0') &&
  1645. CurrentBatchFile != NULL &&
  1646. CurrentBatchFile->orgaptr0 == substr &&
  1647. SearchVar == NULL &&
  1648. (Modifiers & (PATHMODIFIER | ATTRMODIFIER)) != 0
  1649. ) {
  1650. substr = CurrentBatchFile->filespec;
  1651. }
  1652. }
  1653. //
  1654. // Skip past the variable name letter and tell caller how much of the
  1655. // source string we consumed.
  1656. //
  1657. ++srcp;
  1658. *supdate = (int)(srcp - StartBuf) - 1;
  1659. //
  1660. // If the variable has a value, then apply the modifiers to the
  1661. // value.
  1662. //
  1663. if (substr != NULL && *substr != TEXT( '\0' )) {
  1664. //
  1665. // If no path or attribute modifiers request then all done.
  1666. //
  1667. if ((Modifiers & (PATHMODIFIER | ATTRMODIFIER)) == 0) {
  1668. substr = dupstr( substr );
  1669. goto alldone;
  1670. }
  1671. //
  1672. // If requested searching an environment variable path, do that.
  1673. //
  1674. FullPath[0] = NULLC;
  1675. if (SearchVar != NULL) {
  1676. if (SearchVar != (TCHAR *)-1) {
  1677. FullPathLength = SearchPath( SearchVar,
  1678. substr,
  1679. NULL,
  1680. sizeof( FullPath ) / sizeof( FullPath[0] ),
  1681. FullPath,
  1682. &FilePart);
  1683. if (FullPathLength == 0) {
  1684. SearchVar = (TCHAR *)-1;
  1685. } else if ((Modifiers & PATHBITS) == 0) {
  1686. Modifiers |= PATHMODIFIER | WANTFULLPATH;
  1687. }
  1688. }
  1689. }
  1690. if (SearchVar == NULL) {
  1691. //
  1692. // If not searching environment variable path, start with full path.
  1693. //
  1694. FullPathLength = GetFullPathName( substr,
  1695. sizeof( FullPath ) / sizeof( FullPath[0] ),
  1696. FullPath,
  1697. &FilePart);
  1698. } else if (SearchVar == (TCHAR *)-1) {
  1699. //
  1700. // If search of environment variable path failed, result is empty string
  1701. //
  1702. substr = NULL;
  1703. }
  1704. if (FilePart == NULL)
  1705. FilePart = _tcschr( FullPath, NULLC );
  1706. //
  1707. // Fixup the path to have same case as on disk, substituting short
  1708. // names if requested.
  1709. //
  1710. FixupPath( FullPath, (Modifiers & WANTSHORTNAME) );
  1711. //
  1712. // If we have a full path, the result gets the portions requested by
  1713. // the user, unless they wanted the full path, in which case there is
  1714. // nothing more to do.
  1715. //
  1716. if (FullPathLength != 0) {
  1717. TCHAR Buffer[ 2 * MAX_PATH ];
  1718. TCHAR *s;
  1719. s = Buffer;
  1720. if ((Modifiers & ATTRMODIFIER) != 0) {
  1721. struct tm FileTime;
  1722. LARGE_INTEGER FileSize;
  1723. WIN32_FILE_ATTRIBUTE_DATA FileInfo;
  1724. int i;
  1725. BOOL bResult;
  1726. #ifdef WIN95_CMD
  1727. WIN32_FIND_DATA Win95FileInfo;
  1728. HANDLE hFind;
  1729. hFind = FindFirstFile( FullPath, &Win95FileInfo );
  1730. if (hFind != INVALID_HANDLE_VALUE) {
  1731. FileInfo.dwFileAttributes = Win95FileInfo.dwFileAttributes;
  1732. FileInfo.ftCreationTime = Win95FileInfo.ftCreationTime;
  1733. FileInfo.ftLastAccessTime = Win95FileInfo.ftLastAccessTime;
  1734. FileInfo.ftLastWriteTime = Win95FileInfo.ftLastWriteTime;
  1735. FileInfo.nFileSizeHigh = Win95FileInfo.nFileSizeHigh;
  1736. FileInfo.nFileSizeLow = Win95FileInfo.nFileSizeLow;
  1737. FindClose( hFind );
  1738. bResult = TRUE;
  1739. } else {
  1740. bResult = FALSE;
  1741. }
  1742. #else
  1743. bResult = GetFileAttributesEx( FullPath,
  1744. GetFileExInfoStandard,
  1745. &FileInfo
  1746. );
  1747. #endif
  1748. if (bResult) {
  1749. if ((Modifiers & WANTATTRIB) != 0) {
  1750. i = 0;
  1751. while (rgAttrToCharMap[i].chAttributeChar) {
  1752. if (FileInfo.dwFileAttributes & rgAttrToCharMap[i].dwAttributeFlag)
  1753. *s++ = rgAttrToCharMap[i].chAttributeChar;
  1754. else
  1755. *s++ = TEXT('-');
  1756. i += 1;
  1757. }
  1758. }
  1759. if ((Modifiers & WANTTIMESTAMP) != 0) {
  1760. ConvertFILETIMETotm( &FileInfo.ftLastWriteTime, &FileTime );
  1761. if (s != Buffer)
  1762. *s++ = SPACE;
  1763. s += PrintDate( &FileTime, PD_DIR, s, MAX_PATH );
  1764. *s++ = SPACE;
  1765. s += PrintTime( &FileTime, PT_DIR, s, MAX_PATH );
  1766. }
  1767. if ((Modifiers & WANTSIZE) != 0) {
  1768. if (s != Buffer)
  1769. *s++ = SPACE;
  1770. FileSize.LowPart = FileInfo.nFileSizeLow;
  1771. FileSize.HighPart = FileInfo.nFileSizeHigh;
  1772. // THOUSANDSEPSWITCH
  1773. s += FormatFileSize( 0, &FileSize, 0, s );
  1774. }
  1775. }
  1776. }
  1777. if ((Modifiers & PATHMODIFIER) != 0 ||
  1778. (Modifiers & ATTRMODIFIER) == 0) {
  1779. if (s != Buffer)
  1780. *s++ = SPACE;
  1781. if ((Modifiers & WANTFULLPATH) == 0 &&
  1782. ((Modifiers & WANTSHORTNAME) == 0 ||
  1783. (Modifiers & (WANTDRIVE | WANTPATH | WANTEXTENSION | WANTNAME)) != 0
  1784. )
  1785. ) {
  1786. StartPath = FullPath + 2;
  1787. if ((Modifiers & WANTDRIVE) == 0) {
  1788. StartPath = _tcscpy(FullPath, StartPath);
  1789. FilePart -= 2;
  1790. }
  1791. if ((Modifiers & WANTPATH) == 0)
  1792. FilePart = _tcscpy(StartPath, FilePart);
  1793. Extension = _tcsrchr(FilePart, DOT);
  1794. if (Extension == NULL) {
  1795. NullExt = NULLC;
  1796. Extension = &NullExt;
  1797. }
  1798. if ((Modifiers & WANTEXTENSION) == 0)
  1799. *Extension = NULLC;
  1800. if ((Modifiers & WANTNAME) == 0)
  1801. _tcscpy(FilePart, Extension);
  1802. }
  1803. _tcscpy(s, FullPath);
  1804. s += _tcslen(s);
  1805. }
  1806. *s = NULLC;
  1807. substr = dupstr( Buffer );
  1808. }
  1809. }
  1810. } else if (*srcp && (s1 = _tcsrchr(vars, *srcp))) {
  1811. //
  1812. // Old syntax. Result is value of variable
  1813. //
  1814. substr = subs[s1 - vars]; /* Found variable*/
  1815. //
  1816. // Skip past the variable name letter and tell caller how much of the
  1817. // source string we consumed.
  1818. //
  1819. ++srcp; /* Kick past name*/
  1820. *supdate += 1;
  1821. }
  1822. alldone:
  1823. //
  1824. // If result was empty, then return the null string. Otherwise return the result
  1825. //
  1826. if (!substr && *supdate != 0)
  1827. return TEXT("");
  1828. else
  1829. return substr;
  1830. }