wcmdmain.c 89 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767
  1. /*
  2. * CMD - Wine-compatible command line interface.
  3. *
  4. * Copyright (C) 1999 - 2001 D A Pickles
  5. * Copyright (C) 2007 J Edmeades
  6. *
  7. * This library is free software; you can redistribute it and/or
  8. * modify it under the terms of the GNU Lesser General Public
  9. * License as published by the Free Software Foundation; either
  10. * version 2.1 of the License, or (at your option) any later version.
  11. *
  12. * This library is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  15. * Lesser General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Lesser General Public
  18. * License along with this library; if not, write to the Free Software
  19. * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
  20. */
  21. /*
  22. * FIXME:
  23. * - Cannot handle parameters in quotes
  24. * - Lots of functionality missing from builtins
  25. */
  26. #include <time.h>
  27. #include "wcmd.h"
  28. #include "shellapi.h"
  29. #include "wine/debug.h"
  30. WINE_DEFAULT_DEBUG_CHANNEL(cmd);
  31. extern const WCHAR inbuilt[][10];
  32. extern struct env_stack *pushd_directories;
  33. BATCH_CONTEXT *context = NULL;
  34. DWORD errorlevel;
  35. WCHAR quals[MAXSTRING], param1[MAXSTRING], param2[MAXSTRING];
  36. BOOL interactive;
  37. FOR_CONTEXT forloopcontext; /* The 'for' loop context */
  38. BOOL delayedsubst = FALSE; /* The current delayed substitution setting */
  39. int defaultColor = 7;
  40. BOOL echo_mode = TRUE;
  41. WCHAR anykey[100], version_string[100];
  42. static BOOL opt_c, opt_k, opt_s, unicodeOutput = FALSE;
  43. /* Variables pertaining to paging */
  44. static BOOL paged_mode;
  45. static const WCHAR *pagedMessage = NULL;
  46. static int line_count;
  47. static int max_height;
  48. static int max_width;
  49. static int numChars;
  50. #define MAX_WRITECONSOLE_SIZE 65535
  51. /*
  52. * Returns a buffer for reading from/writing to file
  53. * Never freed
  54. */
  55. static char *get_file_buffer(void)
  56. {
  57. static char *output_bufA = NULL;
  58. if (!output_bufA)
  59. output_bufA = heap_xalloc(MAX_WRITECONSOLE_SIZE);
  60. return output_bufA;
  61. }
  62. /*******************************************************************
  63. * WCMD_output_asis_len - send output to current standard output
  64. *
  65. * Output a formatted unicode string. Ideally this will go to the console
  66. * and hence required WriteConsoleW to output it, however if file i/o is
  67. * redirected, it needs to be WriteFile'd using OEM (not ANSI) format
  68. */
  69. static void WCMD_output_asis_len(const WCHAR *message, DWORD len, HANDLE device)
  70. {
  71. DWORD nOut= 0;
  72. DWORD res = 0;
  73. /* If nothing to write, return (MORE does this sometimes) */
  74. if (!len) return;
  75. /* Try to write as unicode assuming it is to a console */
  76. res = WriteConsoleW(device, message, len, &nOut, NULL);
  77. /* If writing to console fails, assume it's file
  78. i/o so convert to OEM codepage and output */
  79. if (!res) {
  80. BOOL usedDefaultChar = FALSE;
  81. DWORD convertedChars;
  82. char *buffer;
  83. if (!unicodeOutput) {
  84. if (!(buffer = get_file_buffer()))
  85. return;
  86. /* Convert to OEM, then output */
  87. convertedChars = WideCharToMultiByte(GetConsoleOutputCP(), 0, message,
  88. len, buffer, MAX_WRITECONSOLE_SIZE,
  89. "?", &usedDefaultChar);
  90. WriteFile(device, buffer, convertedChars,
  91. &nOut, FALSE);
  92. } else {
  93. WriteFile(device, message, len*sizeof(WCHAR),
  94. &nOut, FALSE);
  95. }
  96. }
  97. return;
  98. }
  99. /*******************************************************************
  100. * WCMD_output - send output to current standard output device.
  101. *
  102. */
  103. void WINAPIV WCMD_output (const WCHAR *format, ...) {
  104. va_list ap;
  105. WCHAR* string;
  106. DWORD len;
  107. va_start(ap,format);
  108. string = NULL;
  109. len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
  110. format, 0, 0, (LPWSTR)&string, 0, &ap);
  111. va_end(ap);
  112. if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
  113. WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  114. else
  115. {
  116. WCMD_output_asis_len(string, len, GetStdHandle(STD_OUTPUT_HANDLE));
  117. LocalFree(string);
  118. }
  119. }
  120. /*******************************************************************
  121. * WCMD_output_stderr - send output to current standard error device.
  122. *
  123. */
  124. void WINAPIV WCMD_output_stderr (const WCHAR *format, ...) {
  125. va_list ap;
  126. WCHAR* string;
  127. DWORD len;
  128. va_start(ap,format);
  129. string = NULL;
  130. len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
  131. format, 0, 0, (LPWSTR)&string, 0, &ap);
  132. va_end(ap);
  133. if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE)
  134. WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  135. else
  136. {
  137. WCMD_output_asis_len(string, len, GetStdHandle(STD_ERROR_HANDLE));
  138. LocalFree(string);
  139. }
  140. }
  141. /*******************************************************************
  142. * WCMD_format_string - allocate a buffer and format a string
  143. *
  144. */
  145. WCHAR* WINAPIV WCMD_format_string (const WCHAR *format, ...)
  146. {
  147. va_list ap;
  148. WCHAR* string;
  149. DWORD len;
  150. va_start(ap,format);
  151. len = FormatMessageW(FORMAT_MESSAGE_FROM_STRING|FORMAT_MESSAGE_ALLOCATE_BUFFER,
  152. format, 0, 0, (LPWSTR)&string, 0, &ap);
  153. va_end(ap);
  154. if (len == 0 && GetLastError() != ERROR_NO_WORK_DONE) {
  155. WINE_FIXME("Could not format string: le=%u, fmt=%s\n", GetLastError(), wine_dbgstr_w(format));
  156. string = (WCHAR*)LocalAlloc(LMEM_FIXED, 2);
  157. *string = 0;
  158. }
  159. return string;
  160. }
  161. void WCMD_enter_paged_mode(const WCHAR *msg)
  162. {
  163. CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
  164. if (GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &consoleInfo)) {
  165. max_height = consoleInfo.dwSize.Y;
  166. max_width = consoleInfo.dwSize.X;
  167. } else {
  168. max_height = 25;
  169. max_width = 80;
  170. }
  171. paged_mode = TRUE;
  172. line_count = 0;
  173. numChars = 0;
  174. pagedMessage = (msg==NULL)? anykey : msg;
  175. }
  176. void WCMD_leave_paged_mode(void)
  177. {
  178. paged_mode = FALSE;
  179. pagedMessage = NULL;
  180. }
  181. /***************************************************************************
  182. * WCMD_ReadFile
  183. *
  184. * Read characters in from a console/file, returning result in Unicode
  185. */
  186. BOOL WCMD_ReadFile(const HANDLE hIn, WCHAR *intoBuf, const DWORD maxChars, LPDWORD charsRead)
  187. {
  188. DWORD numRead;
  189. char *buffer;
  190. /* Try to read from console as Unicode */
  191. if (ReadConsoleW(hIn, intoBuf, maxChars, charsRead, NULL)) return TRUE;
  192. /* We assume it's a file handle and read then convert from assumed OEM codepage */
  193. if (!(buffer = get_file_buffer()))
  194. return FALSE;
  195. if (!ReadFile(hIn, buffer, maxChars, &numRead, NULL))
  196. return FALSE;
  197. *charsRead = MultiByteToWideChar(GetConsoleCP(), 0, buffer, numRead, intoBuf, maxChars);
  198. return TRUE;
  199. }
  200. /*******************************************************************
  201. * WCMD_output_asis_handle
  202. *
  203. * Send output to specified handle without formatting e.g. when message contains '%'
  204. */
  205. static void WCMD_output_asis_handle (DWORD std_handle, const WCHAR *message) {
  206. DWORD count;
  207. const WCHAR* ptr;
  208. WCHAR string[1024];
  209. HANDLE handle = GetStdHandle(std_handle);
  210. if (paged_mode) {
  211. do {
  212. ptr = message;
  213. while (*ptr && *ptr!='\n' && (numChars < max_width)) {
  214. numChars++;
  215. ptr++;
  216. };
  217. if (*ptr == '\n') ptr++;
  218. WCMD_output_asis_len(message, ptr - message, handle);
  219. numChars = 0;
  220. if (++line_count >= max_height - 1) {
  221. line_count = 0;
  222. WCMD_output_asis_len(pagedMessage, lstrlenW(pagedMessage), handle);
  223. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
  224. }
  225. } while (((message = ptr) != NULL) && (*ptr));
  226. } else {
  227. WCMD_output_asis_len(message, lstrlenW(message), handle);
  228. }
  229. }
  230. /*******************************************************************
  231. * WCMD_output_asis
  232. *
  233. * Send output to current standard output device, without formatting
  234. * e.g. when message contains '%'
  235. */
  236. void WCMD_output_asis (const WCHAR *message) {
  237. WCMD_output_asis_handle(STD_OUTPUT_HANDLE, message);
  238. }
  239. /*******************************************************************
  240. * WCMD_output_asis_stderr
  241. *
  242. * Send output to current standard error device, without formatting
  243. * e.g. when message contains '%'
  244. */
  245. void WCMD_output_asis_stderr (const WCHAR *message) {
  246. WCMD_output_asis_handle(STD_ERROR_HANDLE, message);
  247. }
  248. /****************************************************************************
  249. * WCMD_print_error
  250. *
  251. * Print the message for GetLastError
  252. */
  253. void WCMD_print_error (void) {
  254. LPVOID lpMsgBuf;
  255. DWORD error_code;
  256. int status;
  257. error_code = GetLastError ();
  258. status = FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM,
  259. NULL, error_code, 0, (LPWSTR) &lpMsgBuf, 0, NULL);
  260. if (!status) {
  261. WINE_FIXME ("Cannot display message for error %d, status %d\n",
  262. error_code, GetLastError());
  263. return;
  264. }
  265. WCMD_output_asis_len(lpMsgBuf, lstrlenW(lpMsgBuf),
  266. GetStdHandle(STD_ERROR_HANDLE));
  267. LocalFree (lpMsgBuf);
  268. WCMD_output_asis_len(L"\r\n", lstrlenW(L"\r\n"), GetStdHandle(STD_ERROR_HANDLE));
  269. return;
  270. }
  271. /******************************************************************************
  272. * WCMD_show_prompt
  273. *
  274. * Display the prompt on STDout
  275. *
  276. */
  277. static void WCMD_show_prompt (BOOL newLine) {
  278. int status;
  279. WCHAR out_string[MAX_PATH], curdir[MAX_PATH], prompt_string[MAX_PATH];
  280. WCHAR *p, *q;
  281. DWORD len;
  282. len = GetEnvironmentVariableW(L"PROMPT", prompt_string, ARRAY_SIZE(prompt_string));
  283. if ((len == 0) || (len >= ARRAY_SIZE(prompt_string))) {
  284. lstrcpyW(prompt_string, L"$P$G");
  285. }
  286. p = prompt_string;
  287. q = out_string;
  288. if (newLine) {
  289. *q++ = '\r';
  290. *q++ = '\n';
  291. }
  292. *q = '\0';
  293. while (*p != '\0') {
  294. if (*p != '$') {
  295. *q++ = *p++;
  296. *q = '\0';
  297. }
  298. else {
  299. p++;
  300. switch (toupper(*p)) {
  301. case '$':
  302. *q++ = '$';
  303. break;
  304. case 'A':
  305. *q++ = '&';
  306. break;
  307. case 'B':
  308. *q++ = '|';
  309. break;
  310. case 'C':
  311. *q++ = '(';
  312. break;
  313. case 'D':
  314. GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL, NULL, q, MAX_PATH - (q - out_string));
  315. while (*q) q++;
  316. break;
  317. case 'E':
  318. *q++ = '\x1b';
  319. break;
  320. case 'F':
  321. *q++ = ')';
  322. break;
  323. case 'G':
  324. *q++ = '>';
  325. break;
  326. case 'H':
  327. *q++ = '\b';
  328. break;
  329. case 'L':
  330. *q++ = '<';
  331. break;
  332. case 'N':
  333. status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
  334. if (status) {
  335. *q++ = curdir[0];
  336. }
  337. break;
  338. case 'P':
  339. status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
  340. if (status) {
  341. lstrcatW (q, curdir);
  342. while (*q) q++;
  343. }
  344. break;
  345. case 'Q':
  346. *q++ = '=';
  347. break;
  348. case 'S':
  349. *q++ = ' ';
  350. break;
  351. case 'T':
  352. GetTimeFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, q, MAX_PATH);
  353. while (*q) q++;
  354. break;
  355. case 'V':
  356. lstrcatW (q, version_string);
  357. while (*q) q++;
  358. break;
  359. case '_':
  360. *q++ = '\n';
  361. break;
  362. case '+':
  363. if (pushd_directories) {
  364. memset(q, '+', pushd_directories->u.stackdepth);
  365. q = q + pushd_directories->u.stackdepth;
  366. }
  367. break;
  368. }
  369. p++;
  370. *q = '\0';
  371. }
  372. }
  373. WCMD_output_asis (out_string);
  374. }
  375. void *heap_xalloc(size_t size)
  376. {
  377. void *ret;
  378. ret = heap_alloc(size);
  379. if(!ret) {
  380. ERR("Out of memory\n");
  381. ExitProcess(1);
  382. }
  383. return ret;
  384. }
  385. /*************************************************************************
  386. * WCMD_strsubstW
  387. * Replaces a portion of a Unicode string with the specified string.
  388. * It's up to the caller to ensure there is enough space in the
  389. * destination buffer.
  390. */
  391. void WCMD_strsubstW(WCHAR *start, const WCHAR *next, const WCHAR *insert, int len) {
  392. if (len < 0)
  393. len=insert ? lstrlenW(insert) : 0;
  394. if (start+len != next)
  395. memmove(start+len, next, (lstrlenW(next) + 1) * sizeof(*next));
  396. if (insert)
  397. memcpy(start, insert, len * sizeof(*insert));
  398. }
  399. /***************************************************************************
  400. * WCMD_skip_leading_spaces
  401. *
  402. * Return a pointer to the first non-whitespace character of string.
  403. * Does not modify the input string.
  404. */
  405. WCHAR *WCMD_skip_leading_spaces (WCHAR *string) {
  406. WCHAR *ptr;
  407. ptr = string;
  408. while (*ptr == ' ' || *ptr == '\t') ptr++;
  409. return ptr;
  410. }
  411. /***************************************************************************
  412. * WCMD_keyword_ws_found
  413. *
  414. * Checks if the string located at ptr matches a keyword (of length len)
  415. * followed by a whitespace character (space or tab)
  416. */
  417. BOOL WCMD_keyword_ws_found(const WCHAR *keyword, const WCHAR *ptr) {
  418. const int len = lstrlenW(keyword);
  419. return (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  420. ptr, len, keyword, len) == CSTR_EQUAL)
  421. && ((*(ptr + len) == ' ') || (*(ptr + len) == '\t'));
  422. }
  423. /*************************************************************************
  424. * WCMD_strip_quotes
  425. *
  426. * Remove first and last quote WCHARacters, preserving all other text
  427. * Returns the location of the final quote
  428. */
  429. WCHAR *WCMD_strip_quotes(WCHAR *cmd) {
  430. WCHAR *src = cmd + 1, *dest = cmd, *lastq = NULL, *lastquote;
  431. while((*dest=*src) != '\0') {
  432. if (*src=='\"')
  433. lastq=dest;
  434. dest++; src++;
  435. }
  436. lastquote = lastq;
  437. if (lastq) {
  438. dest=lastq++;
  439. while ((*dest++=*lastq++) != 0)
  440. ;
  441. }
  442. return lastquote;
  443. }
  444. /*************************************************************************
  445. * WCMD_is_magic_envvar
  446. * Return TRUE if s is '%'magicvar'%'
  447. * and is not masked by a real environment variable.
  448. */
  449. static inline BOOL WCMD_is_magic_envvar(const WCHAR *s, const WCHAR *magicvar)
  450. {
  451. int len;
  452. if (s[0] != '%')
  453. return FALSE; /* Didn't begin with % */
  454. len = lstrlenW(s);
  455. if (len < 2 || s[len-1] != '%')
  456. return FALSE; /* Didn't end with another % */
  457. if (CompareStringW(LOCALE_USER_DEFAULT,
  458. NORM_IGNORECASE | SORT_STRINGSORT,
  459. s+1, len-2, magicvar, -1) != CSTR_EQUAL) {
  460. /* Name doesn't match. */
  461. return FALSE;
  462. }
  463. if (GetEnvironmentVariableW(magicvar, NULL, 0) > 0) {
  464. /* Masked by real environment variable. */
  465. return FALSE;
  466. }
  467. return TRUE;
  468. }
  469. /*************************************************************************
  470. * WCMD_expand_envvar
  471. *
  472. * Expands environment variables, allowing for WCHARacter substitution
  473. */
  474. static WCHAR *WCMD_expand_envvar(WCHAR *start, WCHAR startchar)
  475. {
  476. WCHAR *endOfVar = NULL, *s;
  477. WCHAR *colonpos = NULL;
  478. WCHAR thisVar[MAXSTRING];
  479. WCHAR thisVarContents[MAXSTRING];
  480. WCHAR savedchar = 0x00;
  481. int len;
  482. WCHAR Delims[] = L"%:"; /* First char gets replaced appropriately */
  483. WINE_TRACE("Expanding: %s (%c)\n", wine_dbgstr_w(start), startchar);
  484. /* Find the end of the environment variable, and extract name */
  485. Delims[0] = startchar;
  486. endOfVar = wcspbrk(start+1, Delims);
  487. if (endOfVar == NULL || *endOfVar==' ') {
  488. /* In batch program, missing terminator for % and no following
  489. ':' just removes the '%' */
  490. if (context) {
  491. WCMD_strsubstW(start, start + 1, NULL, 0);
  492. return start;
  493. } else {
  494. /* In command processing, just ignore it - allows command line
  495. syntax like: for %i in (a.a) do echo %i */
  496. return start+1;
  497. }
  498. }
  499. /* If ':' found, process remaining up until '%' (or stop at ':' if
  500. a missing '%' */
  501. if (*endOfVar==':') {
  502. WCHAR *endOfVar2 = wcschr(endOfVar+1, startchar);
  503. if (endOfVar2 != NULL) endOfVar = endOfVar2;
  504. }
  505. memcpy(thisVar, start, ((endOfVar - start) + 1) * sizeof(WCHAR));
  506. thisVar[(endOfVar - start)+1] = 0x00;
  507. colonpos = wcschr(thisVar+1, ':');
  508. /* If there's complex substitution, just need %var% for now
  509. to get the expanded data to play with */
  510. if (colonpos) {
  511. *colonpos = startchar;
  512. savedchar = *(colonpos+1);
  513. *(colonpos+1) = 0x00;
  514. }
  515. /* By now, we know the variable we want to expand but it may be
  516. surrounded by '!' if we are in delayed expansion - if so convert
  517. to % signs. */
  518. if (startchar=='!') {
  519. thisVar[0] = '%';
  520. thisVar[(endOfVar - start)] = '%';
  521. }
  522. WINE_TRACE("Retrieving contents of %s\n", wine_dbgstr_w(thisVar));
  523. /* Expand to contents, if unchanged, return */
  524. /* Handle DATE, TIME, ERRORLEVEL and CD replacements allowing */
  525. /* override if existing env var called that name */
  526. if (WCMD_is_magic_envvar(thisVar, L"ERRORLEVEL")) {
  527. wsprintfW(thisVarContents, L"%d", errorlevel);
  528. len = lstrlenW(thisVarContents);
  529. } else if (WCMD_is_magic_envvar(thisVar, L"DATE")) {
  530. GetDateFormatW(LOCALE_USER_DEFAULT, DATE_SHORTDATE, NULL,
  531. NULL, thisVarContents, MAXSTRING);
  532. len = lstrlenW(thisVarContents);
  533. } else if (WCMD_is_magic_envvar(thisVar, L"TIME")) {
  534. GetTimeFormatW(LOCALE_USER_DEFAULT, TIME_NOSECONDS, NULL,
  535. NULL, thisVarContents, MAXSTRING);
  536. len = lstrlenW(thisVarContents);
  537. } else if (WCMD_is_magic_envvar(thisVar, L"CD")) {
  538. GetCurrentDirectoryW(MAXSTRING, thisVarContents);
  539. len = lstrlenW(thisVarContents);
  540. } else if (WCMD_is_magic_envvar(thisVar, L"RANDOM")) {
  541. wsprintfW(thisVarContents, L"%d", rand() % 32768);
  542. len = lstrlenW(thisVarContents);
  543. } else {
  544. len = ExpandEnvironmentStringsW(thisVar, thisVarContents, ARRAY_SIZE(thisVarContents));
  545. }
  546. if (len == 0)
  547. return endOfVar+1;
  548. /* In a batch program, unknown env vars are replaced with nothing,
  549. note syntax %garbage:1,3% results in anything after the ':'
  550. except the %
  551. From the command line, you just get back what you entered */
  552. if (lstrcmpiW(thisVar, thisVarContents) == 0) {
  553. /* Restore the complex part after the compare */
  554. if (colonpos) {
  555. *colonpos = ':';
  556. *(colonpos+1) = savedchar;
  557. }
  558. /* Command line - just ignore this */
  559. if (context == NULL) return endOfVar+1;
  560. /* Batch - replace unknown env var with nothing */
  561. if (colonpos == NULL) {
  562. WCMD_strsubstW(start, endOfVar + 1, NULL, 0);
  563. } else {
  564. len = lstrlenW(thisVar);
  565. thisVar[len-1] = 0x00;
  566. /* If %:...% supplied, : is retained */
  567. if (colonpos == thisVar+1) {
  568. WCMD_strsubstW(start, endOfVar + 1, colonpos, -1);
  569. } else {
  570. WCMD_strsubstW(start, endOfVar + 1, colonpos + 1, -1);
  571. }
  572. }
  573. return start;
  574. }
  575. /* See if we need to do complex substitution (any ':'s), if not
  576. then our work here is done */
  577. if (colonpos == NULL) {
  578. WCMD_strsubstW(start, endOfVar + 1, thisVarContents, -1);
  579. return start;
  580. }
  581. /* Restore complex bit */
  582. *colonpos = ':';
  583. *(colonpos+1) = savedchar;
  584. /*
  585. Handle complex substitutions:
  586. xxx=yyy (replace xxx with yyy)
  587. *xxx=yyy (replace up to and including xxx with yyy)
  588. ~x (from x WCHARs in)
  589. ~-x (from x WCHARs from the end)
  590. ~x,y (from x WCHARs in for y WCHARacters)
  591. ~x,-y (from x WCHARs in until y WCHARacters from the end)
  592. */
  593. /* ~ is substring manipulation */
  594. if (savedchar == '~') {
  595. int substrposition, substrlength = 0;
  596. WCHAR *commapos = wcschr(colonpos+2, ',');
  597. WCHAR *startCopy;
  598. substrposition = wcstol(colonpos+2, NULL, 10);
  599. if (commapos) substrlength = wcstol(commapos+1, NULL, 10);
  600. /* Check bounds */
  601. if (substrposition >= 0) {
  602. startCopy = &thisVarContents[min(substrposition, len)];
  603. } else {
  604. startCopy = &thisVarContents[max(0, len+substrposition-1)];
  605. }
  606. if (commapos == NULL) {
  607. /* Copy the lot */
  608. WCMD_strsubstW(start, endOfVar + 1, startCopy, -1);
  609. } else if (substrlength < 0) {
  610. int copybytes = (len+substrlength-1)-(startCopy-thisVarContents);
  611. if (copybytes > len) copybytes = len;
  612. else if (copybytes < 0) copybytes = 0;
  613. WCMD_strsubstW(start, endOfVar + 1, startCopy, copybytes);
  614. } else {
  615. substrlength = min(substrlength, len - (startCopy- thisVarContents + 1));
  616. WCMD_strsubstW(start, endOfVar + 1, startCopy, substrlength);
  617. }
  618. /* search and replace manipulation */
  619. } else {
  620. WCHAR *equalspos = wcsstr(colonpos, L"=");
  621. WCHAR *replacewith = equalspos+1;
  622. WCHAR *found = NULL;
  623. WCHAR *searchIn;
  624. WCHAR *searchFor;
  625. if (equalspos == NULL) return start+1;
  626. s = heap_strdupW(endOfVar + 1);
  627. /* Null terminate both strings */
  628. thisVar[lstrlenW(thisVar)-1] = 0x00;
  629. *equalspos = 0x00;
  630. /* Since we need to be case insensitive, copy the 2 buffers */
  631. searchIn = heap_strdupW(thisVarContents);
  632. CharUpperBuffW(searchIn, lstrlenW(thisVarContents));
  633. searchFor = heap_strdupW(colonpos+1);
  634. CharUpperBuffW(searchFor, lstrlenW(colonpos+1));
  635. /* Handle wildcard case */
  636. if (*(colonpos+1) == '*') {
  637. /* Search for string to replace */
  638. found = wcsstr(searchIn, searchFor+1);
  639. if (found) {
  640. /* Do replacement */
  641. lstrcpyW(start, replacewith);
  642. lstrcatW(start, thisVarContents + (found-searchIn) + lstrlenW(searchFor+1));
  643. lstrcatW(start, s);
  644. } else {
  645. /* Copy as is */
  646. lstrcpyW(start, thisVarContents);
  647. lstrcatW(start, s);
  648. }
  649. } else {
  650. /* Loop replacing all instances */
  651. WCHAR *lastFound = searchIn;
  652. WCHAR *outputposn = start;
  653. *start = 0x00;
  654. while ((found = wcsstr(lastFound, searchFor))) {
  655. lstrcpynW(outputposn,
  656. thisVarContents + (lastFound-searchIn),
  657. (found - lastFound)+1);
  658. outputposn = outputposn + (found - lastFound);
  659. lstrcatW(outputposn, replacewith);
  660. outputposn = outputposn + lstrlenW(replacewith);
  661. lastFound = found + lstrlenW(searchFor);
  662. }
  663. lstrcatW(outputposn,
  664. thisVarContents + (lastFound-searchIn));
  665. lstrcatW(outputposn, s);
  666. }
  667. heap_free(s);
  668. heap_free(searchIn);
  669. heap_free(searchFor);
  670. }
  671. return start;
  672. }
  673. /*****************************************************************************
  674. * Expand the command. Native expands lines from batch programs as they are
  675. * read in and not again, except for 'for' variable substitution.
  676. * eg. As evidence, "echo %1 && shift && echo %1" or "echo %%path%%"
  677. * atExecute is TRUE when the expansion is occurring as the command is executed
  678. * rather than at parse time, i.e. delayed expansion and for loops need to be
  679. * processed
  680. */
  681. static void handleExpansion(WCHAR *cmd, BOOL atExecute, BOOL delayed) {
  682. /* For commands in a context (batch program): */
  683. /* Expand environment variables in a batch file %{0-9} first */
  684. /* including support for any ~ modifiers */
  685. /* Additionally: */
  686. /* Expand the DATE, TIME, CD, RANDOM and ERRORLEVEL special */
  687. /* names allowing environment variable overrides */
  688. /* NOTE: To support the %PATH:xxx% syntax, also perform */
  689. /* manual expansion of environment variables here */
  690. WCHAR *p = cmd;
  691. WCHAR *t;
  692. int i;
  693. WCHAR *delayedp = NULL;
  694. WCHAR startchar = '%';
  695. WCHAR *normalp;
  696. /* Display the FOR variables in effect */
  697. for (i=0;i<52;i++) {
  698. if (forloopcontext.variable[i]) {
  699. WINE_TRACE("FOR variable context: %c = '%s'\n",
  700. i<26?i+'a':(i-26)+'A',
  701. wine_dbgstr_w(forloopcontext.variable[i]));
  702. }
  703. }
  704. /* Find the next environment variable delimiter */
  705. normalp = wcschr(p, '%');
  706. if (delayed) delayedp = wcschr(p, '!');
  707. if (!normalp) p = delayedp;
  708. else if (!delayedp) p = normalp;
  709. else p = min(p,delayedp);
  710. if (p) startchar = *p;
  711. while (p) {
  712. WINE_TRACE("Translate command:%s %d (at: %s)\n",
  713. wine_dbgstr_w(cmd), atExecute, wine_dbgstr_w(p));
  714. i = *(p+1) - '0';
  715. /* Don't touch %% unless it's in Batch */
  716. if (!atExecute && *(p+1) == startchar) {
  717. if (context) {
  718. WCMD_strsubstW(p, p+1, NULL, 0);
  719. }
  720. p+=1;
  721. /* Replace %~ modifications if in batch program */
  722. } else if (*(p+1) == '~') {
  723. WCMD_HandleTildeModifiers(&p, atExecute);
  724. p++;
  725. /* Replace use of %0...%9 if in batch program*/
  726. } else if (!atExecute && context && (i >= 0) && (i <= 9) && startchar == '%') {
  727. t = WCMD_parameter(context -> command, i + context -> shift_count[i],
  728. NULL, TRUE, TRUE);
  729. WCMD_strsubstW(p, p+2, t, -1);
  730. /* Replace use of %* if in batch program*/
  731. } else if (!atExecute && context && *(p+1)=='*' && startchar == '%') {
  732. WCHAR *startOfParms = NULL;
  733. WCHAR *thisParm = WCMD_parameter(context -> command, 0, &startOfParms, TRUE, TRUE);
  734. if (startOfParms != NULL) {
  735. startOfParms += lstrlenW(thisParm);
  736. while (*startOfParms==' ' || *startOfParms == '\t') startOfParms++;
  737. WCMD_strsubstW(p, p+2, startOfParms, -1);
  738. } else
  739. WCMD_strsubstW(p, p+2, NULL, 0);
  740. } else {
  741. int forvaridx = FOR_VAR_IDX(*(p+1));
  742. if (startchar == '%' && forvaridx != -1 && forloopcontext.variable[forvaridx]) {
  743. /* Replace the 2 characters, % and for variable character */
  744. WCMD_strsubstW(p, p + 2, forloopcontext.variable[forvaridx], -1);
  745. } else if (!atExecute || startchar == '!') {
  746. p = WCMD_expand_envvar(p, startchar);
  747. /* In a FOR loop, see if this is the variable to replace */
  748. } else { /* Ignore %'s on second pass of batch program */
  749. p++;
  750. }
  751. }
  752. /* Find the next environment variable delimiter */
  753. normalp = wcschr(p, '%');
  754. if (delayed) delayedp = wcschr(p, '!');
  755. if (!normalp) p = delayedp;
  756. else if (!delayedp) p = normalp;
  757. else p = min(p,delayedp);
  758. if (p) startchar = *p;
  759. }
  760. return;
  761. }
  762. /*******************************************************************
  763. * WCMD_parse - parse a command into parameters and qualifiers.
  764. *
  765. * On exit, all qualifiers are concatenated into q, the first string
  766. * not beginning with "/" is in p1 and the
  767. * second in p2. Any subsequent non-qualifier strings are lost.
  768. * Parameters in quotes are handled.
  769. */
  770. static void WCMD_parse (const WCHAR *s, WCHAR *q, WCHAR *p1, WCHAR *p2)
  771. {
  772. int p = 0;
  773. *q = *p1 = *p2 = '\0';
  774. while (TRUE) {
  775. switch (*s) {
  776. case '/':
  777. *q++ = *s++;
  778. while ((*s != '\0') && (*s != ' ') && *s != '/') {
  779. *q++ = towupper (*s++);
  780. }
  781. *q = '\0';
  782. break;
  783. case ' ':
  784. case '\t':
  785. s++;
  786. break;
  787. case '"':
  788. s++;
  789. while ((*s != '\0') && (*s != '"')) {
  790. if (p == 0) *p1++ = *s++;
  791. else if (p == 1) *p2++ = *s++;
  792. else s++;
  793. }
  794. if (p == 0) *p1 = '\0';
  795. if (p == 1) *p2 = '\0';
  796. p++;
  797. if (*s == '"') s++;
  798. break;
  799. case '\0':
  800. return;
  801. default:
  802. while ((*s != '\0') && (*s != ' ') && (*s != '\t')
  803. && (*s != '=') && (*s != ',') ) {
  804. if (p == 0) *p1++ = *s++;
  805. else if (p == 1) *p2++ = *s++;
  806. else s++;
  807. }
  808. /* Skip concurrent parms */
  809. while ((*s == ' ') || (*s == '\t') || (*s == '=') || (*s == ',') ) s++;
  810. if (p == 0) *p1 = '\0';
  811. if (p == 1) *p2 = '\0';
  812. p++;
  813. }
  814. }
  815. }
  816. static void init_msvcrt_io_block(STARTUPINFOW* st)
  817. {
  818. STARTUPINFOW st_p;
  819. /* fetch the parent MSVCRT info block if any, so that the child can use the
  820. * same handles as its grand-father
  821. */
  822. st_p.cb = sizeof(STARTUPINFOW);
  823. GetStartupInfoW(&st_p);
  824. st->cbReserved2 = st_p.cbReserved2;
  825. st->lpReserved2 = st_p.lpReserved2;
  826. if (st_p.cbReserved2 && st_p.lpReserved2)
  827. {
  828. unsigned num = *(unsigned*)st_p.lpReserved2;
  829. char* flags;
  830. HANDLE* handles;
  831. BYTE *ptr;
  832. size_t sz;
  833. /* Override the entries for fd 0,1,2 if we happened
  834. * to change those std handles (this depends on the way cmd sets
  835. * its new input & output handles)
  836. */
  837. sz = max(sizeof(unsigned) + (sizeof(char) + sizeof(HANDLE)) * 3, st_p.cbReserved2);
  838. ptr = heap_xalloc(sz);
  839. flags = (char*)(ptr + sizeof(unsigned));
  840. handles = (HANDLE*)(flags + num * sizeof(char));
  841. memcpy(ptr, st_p.lpReserved2, st_p.cbReserved2);
  842. st->cbReserved2 = sz;
  843. st->lpReserved2 = ptr;
  844. #define WX_OPEN 0x01 /* see dlls/msvcrt/file.c */
  845. if (num <= 0 || (flags[0] & WX_OPEN))
  846. {
  847. handles[0] = GetStdHandle(STD_INPUT_HANDLE);
  848. flags[0] |= WX_OPEN;
  849. }
  850. if (num <= 1 || (flags[1] & WX_OPEN))
  851. {
  852. handles[1] = GetStdHandle(STD_OUTPUT_HANDLE);
  853. flags[1] |= WX_OPEN;
  854. }
  855. if (num <= 2 || (flags[2] & WX_OPEN))
  856. {
  857. handles[2] = GetStdHandle(STD_ERROR_HANDLE);
  858. flags[2] |= WX_OPEN;
  859. }
  860. #undef WX_OPEN
  861. }
  862. }
  863. /******************************************************************************
  864. * WCMD_run_program
  865. *
  866. * Execute a command line as an external program. Must allow recursion.
  867. *
  868. * Precedence:
  869. * Manual testing under windows shows PATHEXT plays a key part in this,
  870. * and the search algorithm and precedence appears to be as follows.
  871. *
  872. * Search locations:
  873. * If directory supplied on command, just use that directory
  874. * If extension supplied on command, look for that explicit name first
  875. * Otherwise, search in each directory on the path
  876. * Precedence:
  877. * If extension supplied on command, look for that explicit name first
  878. * Then look for supplied name .* (even if extension supplied, so
  879. * 'garbage.exe' will match 'garbage.exe.cmd')
  880. * If any found, cycle through PATHEXT looking for name.exe one by one
  881. * Launching
  882. * Once a match has been found, it is launched - Code currently uses
  883. * findexecutable to achieve this which is left untouched.
  884. * If an executable has not been found, and we were launched through
  885. * a call, we need to check if the command is an internal command,
  886. * so go back through wcmd_execute.
  887. */
  888. void WCMD_run_program (WCHAR *command, BOOL called)
  889. {
  890. WCHAR temp[MAX_PATH];
  891. WCHAR pathtosearch[MAXSTRING];
  892. WCHAR *pathposn;
  893. WCHAR stemofsearch[MAX_PATH]; /* maximum allowed executable name is
  894. MAX_PATH, including null character */
  895. WCHAR *lastSlash;
  896. WCHAR pathext[MAXSTRING];
  897. WCHAR *firstParam;
  898. BOOL extensionsupplied = FALSE;
  899. BOOL explicit_path = FALSE;
  900. BOOL status;
  901. DWORD len;
  902. /* Quick way to get the filename is to extract the first argument. */
  903. WINE_TRACE("Running '%s' (%d)\n", wine_dbgstr_w(command), called);
  904. firstParam = WCMD_parameter(command, 0, NULL, FALSE, TRUE);
  905. if (!firstParam) return;
  906. if (!firstParam[0]) {
  907. errorlevel = 0;
  908. return;
  909. }
  910. /* Calculate the search path and stem to search for */
  911. if (wcspbrk(firstParam, L"/\\:") == NULL) { /* No explicit path given, search path */
  912. lstrcpyW(pathtosearch, L".;");
  913. len = GetEnvironmentVariableW(L"PATH", &pathtosearch[2], ARRAY_SIZE(pathtosearch)-2);
  914. if ((len == 0) || (len >= ARRAY_SIZE(pathtosearch) - 2)) {
  915. lstrcpyW(pathtosearch, L".");
  916. }
  917. if (wcschr(firstParam, '.') != NULL) extensionsupplied = TRUE;
  918. if (lstrlenW(firstParam) >= MAX_PATH)
  919. {
  920. WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_LINETOOLONG));
  921. return;
  922. }
  923. lstrcpyW(stemofsearch, firstParam);
  924. } else {
  925. /* Convert eg. ..\fred to include a directory by removing file part */
  926. GetFullPathNameW(firstParam, ARRAY_SIZE(pathtosearch), pathtosearch, NULL);
  927. lastSlash = wcsrchr(pathtosearch, '\\');
  928. if (lastSlash && wcschr(lastSlash, '.') != NULL) extensionsupplied = TRUE;
  929. lstrcpyW(stemofsearch, lastSlash+1);
  930. /* Reduce pathtosearch to a path with trailing '\' to support c:\a.bat and
  931. c:\windows\a.bat syntax */
  932. if (lastSlash) *(lastSlash + 1) = 0x00;
  933. explicit_path = TRUE;
  934. }
  935. /* Now extract PATHEXT */
  936. len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
  937. if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
  938. lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
  939. }
  940. /* Loop through the search path, dir by dir */
  941. pathposn = pathtosearch;
  942. WINE_TRACE("Searching in '%s' for '%s'\n", wine_dbgstr_w(pathtosearch),
  943. wine_dbgstr_w(stemofsearch));
  944. while (pathposn) {
  945. WCHAR thisDir[MAX_PATH] = {'\0'};
  946. int length = 0;
  947. WCHAR *pos = NULL;
  948. BOOL found = FALSE;
  949. BOOL inside_quotes = FALSE;
  950. if (explicit_path)
  951. {
  952. lstrcpyW(thisDir, pathposn);
  953. pathposn = NULL;
  954. }
  955. else
  956. {
  957. /* Work on the next directory on the search path */
  958. pos = pathposn;
  959. while ((inside_quotes || *pos != ';') && *pos != 0)
  960. {
  961. if (*pos == '"')
  962. inside_quotes = !inside_quotes;
  963. pos++;
  964. }
  965. if (*pos) /* Reached semicolon */
  966. {
  967. memcpy(thisDir, pathposn, (pos-pathposn) * sizeof(WCHAR));
  968. thisDir[(pos-pathposn)] = 0x00;
  969. pathposn = pos+1;
  970. }
  971. else /* Reached string end */
  972. {
  973. lstrcpyW(thisDir, pathposn);
  974. pathposn = NULL;
  975. }
  976. /* Remove quotes */
  977. length = lstrlenW(thisDir);
  978. if (thisDir[length - 1] == '"')
  979. thisDir[length - 1] = 0;
  980. if (*thisDir != '"')
  981. lstrcpyW(temp, thisDir);
  982. else
  983. lstrcpyW(temp, thisDir + 1);
  984. /* Since you can have eg. ..\.. on the path, need to expand
  985. to full information */
  986. GetFullPathNameW(temp, MAX_PATH, thisDir, NULL);
  987. }
  988. /* 1. If extension supplied, see if that file exists */
  989. lstrcatW(thisDir, L"\\");
  990. lstrcatW(thisDir, stemofsearch);
  991. pos = &thisDir[lstrlenW(thisDir)]; /* Pos = end of name */
  992. /* 1. If extension supplied, see if that file exists */
  993. if (extensionsupplied) {
  994. if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
  995. found = TRUE;
  996. }
  997. }
  998. /* 2. Any .* matches? */
  999. if (!found) {
  1000. HANDLE h;
  1001. WIN32_FIND_DATAW finddata;
  1002. lstrcatW(thisDir, L".*");
  1003. h = FindFirstFileW(thisDir, &finddata);
  1004. FindClose(h);
  1005. if (h != INVALID_HANDLE_VALUE) {
  1006. WCHAR *thisExt = pathext;
  1007. /* 3. Yes - Try each path ext */
  1008. while (thisExt) {
  1009. WCHAR *nextExt = wcschr(thisExt, ';');
  1010. if (nextExt) {
  1011. memcpy(pos, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
  1012. pos[(nextExt-thisExt)] = 0x00;
  1013. thisExt = nextExt+1;
  1014. } else {
  1015. lstrcpyW(pos, thisExt);
  1016. thisExt = NULL;
  1017. }
  1018. if (GetFileAttributesW(thisDir) != INVALID_FILE_ATTRIBUTES) {
  1019. found = TRUE;
  1020. thisExt = NULL;
  1021. }
  1022. }
  1023. }
  1024. }
  1025. /* Once found, launch it */
  1026. if (found) {
  1027. STARTUPINFOW st;
  1028. PROCESS_INFORMATION pe;
  1029. SHFILEINFOW psfi;
  1030. DWORD console;
  1031. HINSTANCE hinst;
  1032. WCHAR *ext = wcsrchr( thisDir, '.' );
  1033. WINE_TRACE("Found as %s\n", wine_dbgstr_w(thisDir));
  1034. /* Special case BAT and CMD */
  1035. if (ext && (!wcsicmp(ext, L".bat") || !wcsicmp(ext, L".cmd"))) {
  1036. BOOL oldinteractive = interactive;
  1037. interactive = FALSE;
  1038. WCMD_batch (thisDir, command, called, NULL, INVALID_HANDLE_VALUE);
  1039. interactive = oldinteractive;
  1040. return;
  1041. } else {
  1042. /* thisDir contains the file to be launched, but with what?
  1043. eg. a.exe will require a.exe to be launched, a.html may be iexplore */
  1044. hinst = FindExecutableW (thisDir, NULL, temp);
  1045. if ((INT_PTR)hinst < 32)
  1046. console = 0;
  1047. else
  1048. console = SHGetFileInfoW(temp, 0, &psfi, sizeof(psfi), SHGFI_EXETYPE);
  1049. ZeroMemory (&st, sizeof(STARTUPINFOW));
  1050. st.cb = sizeof(STARTUPINFOW);
  1051. init_msvcrt_io_block(&st);
  1052. /* Launch the process and if a CUI wait on it to complete
  1053. Note: Launching internal wine processes cannot specify a full path to exe */
  1054. status = CreateProcessW(thisDir,
  1055. command, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pe);
  1056. heap_free(st.lpReserved2);
  1057. if ((opt_c || opt_k) && !opt_s && !status
  1058. && GetLastError()==ERROR_FILE_NOT_FOUND && command[0]=='\"') {
  1059. /* strip first and last quote WCHARacters and try again */
  1060. WCMD_strip_quotes(command);
  1061. opt_s = TRUE;
  1062. WCMD_run_program(command, called);
  1063. return;
  1064. }
  1065. if (!status)
  1066. break;
  1067. /* Always wait when non-interactive (cmd /c or in batch program),
  1068. or for console applications */
  1069. if (!interactive || (console && !HIWORD(console)))
  1070. WaitForSingleObject (pe.hProcess, INFINITE);
  1071. GetExitCodeProcess (pe.hProcess, &errorlevel);
  1072. if (errorlevel == STILL_ACTIVE) errorlevel = 0;
  1073. CloseHandle(pe.hProcess);
  1074. CloseHandle(pe.hThread);
  1075. return;
  1076. }
  1077. }
  1078. }
  1079. /* Not found anywhere - were we called? */
  1080. if (called) {
  1081. CMD_LIST *toExecute = NULL; /* Commands left to be executed */
  1082. /* Parse the command string, without reading any more input */
  1083. WCMD_ReadAndParseLine(command, &toExecute, INVALID_HANDLE_VALUE);
  1084. WCMD_process_commands(toExecute, FALSE, called);
  1085. WCMD_free_commands(toExecute);
  1086. toExecute = NULL;
  1087. return;
  1088. }
  1089. /* Not found anywhere - give up */
  1090. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NO_COMMAND_FOUND), command);
  1091. /* If a command fails to launch, it sets errorlevel 9009 - which
  1092. does not seem to have any associated constant definition */
  1093. errorlevel = 9009;
  1094. return;
  1095. }
  1096. /*****************************************************************************
  1097. * Process one command. If the command is EXIT this routine does not return.
  1098. * We will recurse through here executing batch files.
  1099. * Note: If call is used to a non-existing program, we reparse the line and
  1100. * try to run it as an internal command. 'retrycall' represents whether
  1101. * we are attempting this retry.
  1102. */
  1103. void WCMD_execute (const WCHAR *command, const WCHAR *redirects,
  1104. CMD_LIST **cmdList, BOOL retrycall)
  1105. {
  1106. WCHAR *cmd, *parms_start, *redir;
  1107. WCHAR *pos;
  1108. int status, i, cmd_index;
  1109. DWORD count, creationDisposition;
  1110. HANDLE h;
  1111. WCHAR *whichcmd;
  1112. SECURITY_ATTRIBUTES sa;
  1113. WCHAR *new_cmd = NULL;
  1114. WCHAR *new_redir = NULL;
  1115. HANDLE old_stdhandles[3] = {GetStdHandle (STD_INPUT_HANDLE),
  1116. GetStdHandle (STD_OUTPUT_HANDLE),
  1117. GetStdHandle (STD_ERROR_HANDLE)};
  1118. DWORD idx_stdhandles[3] = {STD_INPUT_HANDLE,
  1119. STD_OUTPUT_HANDLE,
  1120. STD_ERROR_HANDLE};
  1121. BOOL prev_echo_mode, piped = FALSE;
  1122. WINE_TRACE("command on entry:%s (%p)\n",
  1123. wine_dbgstr_w(command), cmdList);
  1124. /* Move copy of the command onto the heap so it can be expanded */
  1125. new_cmd = heap_xalloc(MAXSTRING * sizeof(WCHAR));
  1126. lstrcpyW(new_cmd, command);
  1127. cmd = new_cmd;
  1128. /* Move copy of the redirects onto the heap so it can be expanded */
  1129. new_redir = heap_xalloc(MAXSTRING * sizeof(WCHAR));
  1130. redir = new_redir;
  1131. /* Strip leading whitespaces, and a '@' if supplied */
  1132. whichcmd = WCMD_skip_leading_spaces(cmd);
  1133. WINE_TRACE("Command: '%s'\n", wine_dbgstr_w(cmd));
  1134. if (whichcmd[0] == '@') whichcmd++;
  1135. /* Check if the command entered is internal, and identify which one */
  1136. count = 0;
  1137. while (IsCharAlphaNumericW(whichcmd[count])) {
  1138. count++;
  1139. }
  1140. for (i=0; i<=WCMD_EXIT; i++) {
  1141. if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  1142. whichcmd, count, inbuilt[i], -1) == CSTR_EQUAL) break;
  1143. }
  1144. cmd_index = i;
  1145. parms_start = WCMD_skip_leading_spaces (&whichcmd[count]);
  1146. /* If the next command is a pipe then we implement pipes by redirecting
  1147. the output from this command to a temp file and input into the
  1148. next command from that temp file.
  1149. Note: Do not do this for a for or if statement as the pipe is for
  1150. the individual statements, not the for or if itself.
  1151. FIXME: Use of named pipes would make more sense here as currently this
  1152. process has to finish before the next one can start but this requires
  1153. a change to not wait for the first app to finish but rather the pipe */
  1154. if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF) &&
  1155. cmdList && (*cmdList)->nextcommand &&
  1156. (*cmdList)->nextcommand->prevDelim == CMD_PIPE) {
  1157. WCHAR temp_path[MAX_PATH];
  1158. /* Remember piping is in action */
  1159. WINE_TRACE("Output needs to be piped\n");
  1160. piped = TRUE;
  1161. /* Generate a unique temporary filename */
  1162. GetTempPathW(ARRAY_SIZE(temp_path), temp_path);
  1163. GetTempFileNameW(temp_path, L"CMD", 0, (*cmdList)->nextcommand->pipeFile);
  1164. WINE_TRACE("Using temporary file of %s\n",
  1165. wine_dbgstr_w((*cmdList)->nextcommand->pipeFile));
  1166. }
  1167. /* If piped output, send stdout to the pipe by appending >filename to redirects */
  1168. if (piped) {
  1169. wsprintfW (new_redir, L"%s > %s", redirects, (*cmdList)->nextcommand->pipeFile);
  1170. WINE_TRACE("Redirects now %s\n", wine_dbgstr_w(new_redir));
  1171. } else {
  1172. lstrcpyW(new_redir, redirects);
  1173. }
  1174. /* Expand variables in command line mode only (batch mode will
  1175. be expanded as the line is read in, except for 'for' loops) */
  1176. handleExpansion(new_cmd, (context != NULL), delayedsubst);
  1177. handleExpansion(new_redir, (context != NULL), delayedsubst);
  1178. /*
  1179. * Changing default drive has to be handled as a special case, anything
  1180. * else if it exists after whitespace is ignored
  1181. */
  1182. if ((cmd[1] == ':') && IsCharAlphaW(cmd[0]) &&
  1183. (!cmd[2] || cmd[2] == ' ' || cmd[2] == '\t')) {
  1184. WCHAR envvar[5];
  1185. WCHAR dir[MAX_PATH];
  1186. /* Ignore potential garbage on the same line */
  1187. cmd[2]=0x00;
  1188. /* According to MSDN CreateProcess docs, special env vars record
  1189. the current directory on each drive, in the form =C:
  1190. so see if one specified, and if so go back to it */
  1191. lstrcpyW(envvar, L"=");
  1192. lstrcatW(envvar, cmd);
  1193. if (GetEnvironmentVariableW(envvar, dir, MAX_PATH) == 0) {
  1194. wsprintfW(cmd, L"%s\\", cmd);
  1195. WINE_TRACE("No special directory settings, using dir of %s\n", wine_dbgstr_w(cmd));
  1196. }
  1197. WINE_TRACE("Got directory %s as %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(cmd));
  1198. status = SetCurrentDirectoryW(cmd);
  1199. if (!status) WCMD_print_error ();
  1200. heap_free(cmd );
  1201. heap_free(new_redir);
  1202. return;
  1203. }
  1204. sa.nLength = sizeof(sa);
  1205. sa.lpSecurityDescriptor = NULL;
  1206. sa.bInheritHandle = TRUE;
  1207. /*
  1208. * Redirect stdin, stdout and/or stderr if required.
  1209. * Note: Do not do this for a for or if statement as the pipe is for
  1210. * the individual statements, not the for or if itself.
  1211. */
  1212. if (!(cmd_index == WCMD_FOR || cmd_index == WCMD_IF)) {
  1213. /* STDIN could come from a preceding pipe, so delete on close if it does */
  1214. if (cmdList && (*cmdList)->pipeFile[0] != 0x00) {
  1215. WINE_TRACE("Input coming from %s\n", wine_dbgstr_w((*cmdList)->pipeFile));
  1216. h = CreateFileW((*cmdList)->pipeFile, GENERIC_READ,
  1217. FILE_SHARE_READ | FILE_SHARE_WRITE, &sa, OPEN_EXISTING,
  1218. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
  1219. if (h == INVALID_HANDLE_VALUE) {
  1220. WCMD_print_error ();
  1221. heap_free(cmd);
  1222. heap_free(new_redir);
  1223. return;
  1224. }
  1225. SetStdHandle (STD_INPUT_HANDLE, h);
  1226. /* No need to remember the temporary name any longer once opened */
  1227. (*cmdList)->pipeFile[0] = 0x00;
  1228. /* Otherwise STDIN could come from a '<' redirect */
  1229. } else if ((pos = wcschr(new_redir,'<')) != NULL) {
  1230. h = CreateFileW(WCMD_parameter(++pos, 0, NULL, FALSE, FALSE), GENERIC_READ, FILE_SHARE_READ,
  1231. &sa, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  1232. if (h == INVALID_HANDLE_VALUE) {
  1233. WCMD_print_error ();
  1234. heap_free(cmd);
  1235. heap_free(new_redir);
  1236. return;
  1237. }
  1238. SetStdHandle (STD_INPUT_HANDLE, h);
  1239. }
  1240. /* Scan the whole command looking for > and 2> */
  1241. while (redir != NULL && ((pos = wcschr(redir,'>')) != NULL)) {
  1242. int handle = 0;
  1243. if (pos > redir && (*(pos-1)=='2'))
  1244. handle = 2;
  1245. else
  1246. handle = 1;
  1247. pos++;
  1248. if ('>' == *pos) {
  1249. creationDisposition = OPEN_ALWAYS;
  1250. pos++;
  1251. }
  1252. else {
  1253. creationDisposition = CREATE_ALWAYS;
  1254. }
  1255. /* Add support for 2>&1 */
  1256. redir = pos;
  1257. if (*pos == '&') {
  1258. int idx = *(pos+1) - '0';
  1259. if (DuplicateHandle(GetCurrentProcess(),
  1260. GetStdHandle(idx_stdhandles[idx]),
  1261. GetCurrentProcess(),
  1262. &h,
  1263. 0, TRUE, DUPLICATE_SAME_ACCESS) == 0) {
  1264. WINE_FIXME("Duplicating handle failed with gle %d\n", GetLastError());
  1265. }
  1266. WINE_TRACE("Redirect %d (%p) to %d (%p)\n", handle, GetStdHandle(idx_stdhandles[idx]), idx, h);
  1267. } else {
  1268. WCHAR *param = WCMD_parameter(pos, 0, NULL, FALSE, FALSE);
  1269. h = CreateFileW(param, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_DELETE,
  1270. &sa, creationDisposition, FILE_ATTRIBUTE_NORMAL, NULL);
  1271. if (h == INVALID_HANDLE_VALUE) {
  1272. WCMD_print_error ();
  1273. heap_free(cmd);
  1274. heap_free(new_redir);
  1275. return;
  1276. }
  1277. if (SetFilePointer (h, 0, NULL, FILE_END) ==
  1278. INVALID_SET_FILE_POINTER) {
  1279. WCMD_print_error ();
  1280. }
  1281. WINE_TRACE("Redirect %d to '%s' (%p)\n", handle, wine_dbgstr_w(param), h);
  1282. }
  1283. SetStdHandle (idx_stdhandles[handle], h);
  1284. }
  1285. } else {
  1286. WINE_TRACE("Not touching redirects for a FOR or IF command\n");
  1287. }
  1288. WCMD_parse (parms_start, quals, param1, param2);
  1289. WINE_TRACE("param1: %s, param2: %s\n", wine_dbgstr_w(param1), wine_dbgstr_w(param2));
  1290. if (i <= WCMD_EXIT && (parms_start[0] == '/') && (parms_start[1] == '?')) {
  1291. /* this is a help request for a builtin program */
  1292. i = WCMD_HELP;
  1293. memcpy(parms_start, whichcmd, count * sizeof(WCHAR));
  1294. parms_start[count] = '\0';
  1295. }
  1296. switch (i) {
  1297. case WCMD_CALL:
  1298. WCMD_call (parms_start);
  1299. break;
  1300. case WCMD_CD:
  1301. case WCMD_CHDIR:
  1302. WCMD_setshow_default (parms_start);
  1303. break;
  1304. case WCMD_CLS:
  1305. WCMD_clear_screen ();
  1306. break;
  1307. case WCMD_COPY:
  1308. WCMD_copy (parms_start);
  1309. break;
  1310. case WCMD_CTTY:
  1311. WCMD_change_tty ();
  1312. break;
  1313. case WCMD_DATE:
  1314. WCMD_setshow_date ();
  1315. break;
  1316. case WCMD_DEL:
  1317. case WCMD_ERASE:
  1318. WCMD_delete (parms_start);
  1319. break;
  1320. case WCMD_DIR:
  1321. WCMD_directory (parms_start);
  1322. break;
  1323. case WCMD_ECHO:
  1324. WCMD_echo(&whichcmd[count]);
  1325. break;
  1326. case WCMD_GOTO:
  1327. WCMD_goto (cmdList);
  1328. break;
  1329. case WCMD_HELP:
  1330. WCMD_give_help (parms_start);
  1331. break;
  1332. case WCMD_LABEL:
  1333. WCMD_volume (TRUE, parms_start);
  1334. break;
  1335. case WCMD_MD:
  1336. case WCMD_MKDIR:
  1337. WCMD_create_dir (parms_start);
  1338. break;
  1339. case WCMD_MOVE:
  1340. WCMD_move ();
  1341. break;
  1342. case WCMD_PATH:
  1343. WCMD_setshow_path (parms_start);
  1344. break;
  1345. case WCMD_PAUSE:
  1346. WCMD_pause ();
  1347. break;
  1348. case WCMD_PROMPT:
  1349. WCMD_setshow_prompt ();
  1350. break;
  1351. case WCMD_REM:
  1352. break;
  1353. case WCMD_REN:
  1354. case WCMD_RENAME:
  1355. WCMD_rename ();
  1356. break;
  1357. case WCMD_RD:
  1358. case WCMD_RMDIR:
  1359. WCMD_remove_dir (parms_start);
  1360. break;
  1361. case WCMD_SETLOCAL:
  1362. WCMD_setlocal(parms_start);
  1363. break;
  1364. case WCMD_ENDLOCAL:
  1365. WCMD_endlocal();
  1366. break;
  1367. case WCMD_SET:
  1368. WCMD_setshow_env (parms_start);
  1369. break;
  1370. case WCMD_SHIFT:
  1371. WCMD_shift (parms_start);
  1372. break;
  1373. case WCMD_START:
  1374. WCMD_start (parms_start);
  1375. break;
  1376. case WCMD_TIME:
  1377. WCMD_setshow_time ();
  1378. break;
  1379. case WCMD_TITLE:
  1380. if (lstrlenW(&whichcmd[count]) > 0)
  1381. WCMD_title(&whichcmd[count+1]);
  1382. break;
  1383. case WCMD_TYPE:
  1384. WCMD_type (parms_start);
  1385. break;
  1386. case WCMD_VER:
  1387. WCMD_output_asis(L"\r\n");
  1388. WCMD_version ();
  1389. break;
  1390. case WCMD_VERIFY:
  1391. WCMD_verify (parms_start);
  1392. break;
  1393. case WCMD_VOL:
  1394. WCMD_volume (FALSE, parms_start);
  1395. break;
  1396. case WCMD_PUSHD:
  1397. WCMD_pushd(parms_start);
  1398. break;
  1399. case WCMD_POPD:
  1400. WCMD_popd();
  1401. break;
  1402. case WCMD_ASSOC:
  1403. WCMD_assoc(parms_start, TRUE);
  1404. break;
  1405. case WCMD_COLOR:
  1406. WCMD_color();
  1407. break;
  1408. case WCMD_FTYPE:
  1409. WCMD_assoc(parms_start, FALSE);
  1410. break;
  1411. case WCMD_MORE:
  1412. WCMD_more(parms_start);
  1413. break;
  1414. case WCMD_CHOICE:
  1415. WCMD_choice(parms_start);
  1416. break;
  1417. case WCMD_MKLINK:
  1418. WCMD_mklink(parms_start);
  1419. break;
  1420. case WCMD_EXIT:
  1421. WCMD_exit (cmdList);
  1422. break;
  1423. case WCMD_FOR:
  1424. case WCMD_IF:
  1425. /* Very oddly, probably because of all the special parsing required for
  1426. these two commands, neither 'for' nor 'if' is supported when called,
  1427. i.e. 'call if 1==1...' will fail. */
  1428. if (!retrycall) {
  1429. if (i==WCMD_FOR) WCMD_for (parms_start, cmdList);
  1430. else if (i==WCMD_IF) WCMD_if (parms_start, cmdList);
  1431. break;
  1432. }
  1433. /* else: drop through */
  1434. default:
  1435. prev_echo_mode = echo_mode;
  1436. WCMD_run_program (whichcmd, FALSE);
  1437. echo_mode = prev_echo_mode;
  1438. }
  1439. heap_free(cmd);
  1440. heap_free(new_redir);
  1441. /* Restore old handles */
  1442. for (i=0; i<3; i++) {
  1443. if (old_stdhandles[i] != GetStdHandle(idx_stdhandles[i])) {
  1444. CloseHandle (GetStdHandle (idx_stdhandles[i]));
  1445. SetStdHandle (idx_stdhandles[i], old_stdhandles[i]);
  1446. }
  1447. }
  1448. }
  1449. /*************************************************************************
  1450. * WCMD_LoadMessage
  1451. * Load a string from the resource file, handling any error
  1452. * Returns string retrieved from resource file
  1453. */
  1454. WCHAR *WCMD_LoadMessage(UINT id) {
  1455. static WCHAR msg[2048];
  1456. if (!LoadStringW(GetModuleHandleW(NULL), id, msg, ARRAY_SIZE(msg))) {
  1457. WINE_FIXME("LoadString failed with %d\n", GetLastError());
  1458. lstrcpyW(msg, L"Failed!");
  1459. }
  1460. return msg;
  1461. }
  1462. /***************************************************************************
  1463. * WCMD_DumpCommands
  1464. *
  1465. * Dumps out the parsed command line to ensure syntax is correct
  1466. */
  1467. static void WCMD_DumpCommands(CMD_LIST *commands) {
  1468. CMD_LIST *thisCmd = commands;
  1469. WINE_TRACE("Parsed line:\n");
  1470. while (thisCmd != NULL) {
  1471. WINE_TRACE("%p %d %2.2d %p %s Redir:%s\n",
  1472. thisCmd,
  1473. thisCmd->prevDelim,
  1474. thisCmd->bracketDepth,
  1475. thisCmd->nextcommand,
  1476. wine_dbgstr_w(thisCmd->command),
  1477. wine_dbgstr_w(thisCmd->redirects));
  1478. thisCmd = thisCmd->nextcommand;
  1479. }
  1480. }
  1481. /***************************************************************************
  1482. * WCMD_addCommand
  1483. *
  1484. * Adds a command to the current command list
  1485. */
  1486. static void WCMD_addCommand(WCHAR *command, int *commandLen,
  1487. WCHAR *redirs, int *redirLen,
  1488. WCHAR **copyTo, int **copyToLen,
  1489. CMD_DELIMITERS prevDelim, int curDepth,
  1490. CMD_LIST **lastEntry, CMD_LIST **output) {
  1491. CMD_LIST *thisEntry = NULL;
  1492. /* Allocate storage for command */
  1493. thisEntry = heap_xalloc(sizeof(CMD_LIST));
  1494. /* Copy in the command */
  1495. if (command) {
  1496. thisEntry->command = heap_xalloc((*commandLen+1) * sizeof(WCHAR));
  1497. memcpy(thisEntry->command, command, *commandLen * sizeof(WCHAR));
  1498. thisEntry->command[*commandLen] = 0x00;
  1499. /* Copy in the redirects */
  1500. thisEntry->redirects = heap_xalloc((*redirLen+1) * sizeof(WCHAR));
  1501. memcpy(thisEntry->redirects, redirs, *redirLen * sizeof(WCHAR));
  1502. thisEntry->redirects[*redirLen] = 0x00;
  1503. thisEntry->pipeFile[0] = 0x00;
  1504. /* Reset the lengths */
  1505. *commandLen = 0;
  1506. *redirLen = 0;
  1507. *copyToLen = commandLen;
  1508. *copyTo = command;
  1509. } else {
  1510. thisEntry->command = NULL;
  1511. thisEntry->redirects = NULL;
  1512. thisEntry->pipeFile[0] = 0x00;
  1513. }
  1514. /* Fill in other fields */
  1515. thisEntry->nextcommand = NULL;
  1516. thisEntry->prevDelim = prevDelim;
  1517. thisEntry->bracketDepth = curDepth;
  1518. if (*lastEntry) {
  1519. (*lastEntry)->nextcommand = thisEntry;
  1520. } else {
  1521. *output = thisEntry;
  1522. }
  1523. *lastEntry = thisEntry;
  1524. }
  1525. /***************************************************************************
  1526. * WCMD_IsEndQuote
  1527. *
  1528. * Checks if the quote pointed to is the end-quote.
  1529. *
  1530. * Quotes end if:
  1531. *
  1532. * 1) The current parameter ends at EOL or at the beginning
  1533. * of a redirection or pipe and not in a quote section.
  1534. *
  1535. * 2) If the next character is a space and not in a quote section.
  1536. *
  1537. * Returns TRUE if this is an end quote, and FALSE if it is not.
  1538. *
  1539. */
  1540. static BOOL WCMD_IsEndQuote(const WCHAR *quote, int quoteIndex)
  1541. {
  1542. int quoteCount = quoteIndex;
  1543. int i;
  1544. /* If we are not in a quoted section, then we are not an end-quote */
  1545. if(quoteIndex == 0)
  1546. {
  1547. return FALSE;
  1548. }
  1549. /* Check how many quotes are left for this parameter */
  1550. for(i=0;quote[i];i++)
  1551. {
  1552. if(quote[i] == '"')
  1553. {
  1554. quoteCount++;
  1555. }
  1556. /* Quote counting ends at EOL, redirection, space or pipe if current quote is complete */
  1557. else if(((quoteCount % 2) == 0)
  1558. && ((quote[i] == '<') || (quote[i] == '>') || (quote[i] == '|') || (quote[i] == ' ') ||
  1559. (quote[i] == '&')))
  1560. {
  1561. break;
  1562. }
  1563. }
  1564. /* If the quote is part of the last part of a series of quotes-on-quotes, then it must
  1565. be an end-quote */
  1566. if(quoteIndex >= (quoteCount / 2))
  1567. {
  1568. return TRUE;
  1569. }
  1570. /* No cigar */
  1571. return FALSE;
  1572. }
  1573. /***************************************************************************
  1574. * WCMD_ReadAndParseLine
  1575. *
  1576. * Either uses supplied input or
  1577. * Reads a file from the handle, and then...
  1578. * Parse the text buffer, splitting into separate commands
  1579. * - unquoted && strings split 2 commands but the 2nd is flagged as
  1580. * following an &&
  1581. * - ( as the first character just ups the bracket depth
  1582. * - unquoted ) when bracket depth > 0 terminates a bracket and
  1583. * adds a CMD_LIST structure with null command
  1584. * - Anything else gets put into the command string (including
  1585. * redirects)
  1586. */
  1587. WCHAR *WCMD_ReadAndParseLine(const WCHAR *optionalcmd, CMD_LIST **output, HANDLE readFrom)
  1588. {
  1589. WCHAR *curPos;
  1590. int inQuotes = 0;
  1591. WCHAR curString[MAXSTRING];
  1592. int curStringLen = 0;
  1593. WCHAR curRedirs[MAXSTRING];
  1594. int curRedirsLen = 0;
  1595. WCHAR *curCopyTo;
  1596. int *curLen;
  1597. int curDepth = 0;
  1598. CMD_LIST *lastEntry = NULL;
  1599. CMD_DELIMITERS prevDelim = CMD_NONE;
  1600. static WCHAR *extraSpace = NULL; /* Deliberately never freed */
  1601. BOOL inOneLine = FALSE;
  1602. BOOL inFor = FALSE;
  1603. BOOL inIn = FALSE;
  1604. BOOL inIf = FALSE;
  1605. BOOL inElse= FALSE;
  1606. BOOL onlyWhiteSpace = FALSE;
  1607. BOOL lastWasWhiteSpace = FALSE;
  1608. BOOL lastWasDo = FALSE;
  1609. BOOL lastWasIn = FALSE;
  1610. BOOL lastWasElse = FALSE;
  1611. BOOL lastWasRedirect = TRUE;
  1612. BOOL lastWasCaret = FALSE;
  1613. BOOL ignoreBracket = FALSE; /* Some expressions after if (set) require */
  1614. /* handling brackets as a normal character */
  1615. int lineCurDepth; /* Bracket depth when line was read in */
  1616. BOOL resetAtEndOfLine = FALSE; /* Do we need to reset curdepth at EOL */
  1617. /* Allocate working space for a command read from keyboard, file etc */
  1618. if (!extraSpace)
  1619. extraSpace = heap_xalloc((MAXSTRING+1) * sizeof(WCHAR));
  1620. if (!extraSpace)
  1621. {
  1622. WINE_ERR("Could not allocate memory for extraSpace\n");
  1623. return NULL;
  1624. }
  1625. /* If initial command read in, use that, otherwise get input from handle */
  1626. if (optionalcmd != NULL) {
  1627. lstrcpyW(extraSpace, optionalcmd);
  1628. } else if (readFrom == INVALID_HANDLE_VALUE) {
  1629. WINE_FIXME("No command nor handle supplied\n");
  1630. } else {
  1631. if (!WCMD_fgets(extraSpace, MAXSTRING, readFrom))
  1632. return NULL;
  1633. }
  1634. curPos = extraSpace;
  1635. /* Handle truncated input - issue warning */
  1636. if (lstrlenW(extraSpace) == MAXSTRING -1) {
  1637. WCMD_output_asis_stderr(WCMD_LoadMessage(WCMD_TRUNCATEDLINE));
  1638. WCMD_output_asis_stderr(extraSpace);
  1639. WCMD_output_asis_stderr(L"\r\n");
  1640. }
  1641. /* Replace env vars if in a batch context */
  1642. if (context) handleExpansion(extraSpace, FALSE, FALSE);
  1643. /* Skip preceding whitespace */
  1644. while (*curPos == ' ' || *curPos == '\t') curPos++;
  1645. /* Show prompt before batch line IF echo is on and in batch program */
  1646. if (context && echo_mode && *curPos && (*curPos != '@')) {
  1647. const DWORD len = lstrlenW(L"echo.");
  1648. DWORD curr_size = lstrlenW(curPos);
  1649. DWORD min_len = (curr_size < len ? curr_size : len);
  1650. WCMD_show_prompt(TRUE);
  1651. WCMD_output_asis(curPos);
  1652. /* I don't know why Windows puts a space here but it does */
  1653. /* Except for lines starting with 'echo.', 'echo:' or 'echo/'. Ask MS why */
  1654. if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
  1655. curPos, min_len, L"echo.", len) != CSTR_EQUAL
  1656. && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
  1657. curPos, min_len, L"echo:", len) != CSTR_EQUAL
  1658. && CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE,
  1659. curPos, min_len, L"echo/", len) != CSTR_EQUAL)
  1660. {
  1661. WCMD_output_asis(L" ");
  1662. }
  1663. WCMD_output_asis(L"\r\n");
  1664. }
  1665. /* Skip repeated 'no echo' characters */
  1666. while (*curPos == '@') curPos++;
  1667. /* Start with an empty string, copying to the command string */
  1668. curStringLen = 0;
  1669. curRedirsLen = 0;
  1670. curCopyTo = curString;
  1671. curLen = &curStringLen;
  1672. lastWasRedirect = FALSE; /* Required e.g. for spaces between > and filename */
  1673. lineCurDepth = curDepth; /* What was the curdepth at the beginning of the line */
  1674. /* Parse every character on the line being processed */
  1675. while (*curPos != 0x00) {
  1676. WCHAR thisChar;
  1677. /* Debugging AID:
  1678. WINE_TRACE("Looking at '%c' (len:%d, lws:%d, ows:%d)\n", *curPos, *curLen,
  1679. lastWasWhiteSpace, onlyWhiteSpace);
  1680. */
  1681. /* Prevent overflow caused by the caret escape char */
  1682. if (*curLen >= MAXSTRING) {
  1683. WINE_ERR("Overflow detected in command\n");
  1684. return NULL;
  1685. }
  1686. /* Certain commands need special handling */
  1687. if (curStringLen == 0 && curCopyTo == curString) {
  1688. /* If command starts with 'rem ' or identifies a label, ignore any &&, ( etc. */
  1689. if (WCMD_keyword_ws_found(L"rem", curPos) || *curPos == ':') {
  1690. inOneLine = TRUE;
  1691. } else if (WCMD_keyword_ws_found(L"for", curPos)) {
  1692. inFor = TRUE;
  1693. /* If command starts with 'if ' or 'else ', handle ('s mid line. We should ensure this
  1694. is only true in the command portion of the IF statement, but this
  1695. should suffice for now.
  1696. To be able to handle ('s in the condition part take as much as evaluate_if_condition
  1697. would take and skip parsing it here. */
  1698. } else if (WCMD_keyword_ws_found(L"if", curPos)) {
  1699. int negate; /* Negate condition */
  1700. int test; /* Condition evaluation result */
  1701. WCHAR *p, *command;
  1702. inIf = TRUE;
  1703. p = curPos+(lstrlenW(L"if"));
  1704. while (*p == ' ' || *p == '\t')
  1705. p++;
  1706. WCMD_parse (p, quals, param1, param2);
  1707. /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
  1708. set in a call to WCMD_parse before */
  1709. if (evaluate_if_condition(p, &command, &test, &negate) != -1)
  1710. {
  1711. int if_condition_len = command - curPos;
  1712. WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s, if_condition_len: %d\n",
  1713. wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
  1714. wine_dbgstr_w(param2), wine_dbgstr_w(command), if_condition_len);
  1715. memcpy(&curCopyTo[*curLen], curPos, if_condition_len*sizeof(WCHAR));
  1716. (*curLen)+=if_condition_len;
  1717. curPos+=if_condition_len;
  1718. }
  1719. if (WCMD_keyword_ws_found(L"set", curPos))
  1720. ignoreBracket = TRUE;
  1721. } else if (WCMD_keyword_ws_found(L"else", curPos)) {
  1722. const int keyw_len = lstrlenW(L"else") + 1;
  1723. inElse = TRUE;
  1724. lastWasElse = TRUE;
  1725. onlyWhiteSpace = TRUE;
  1726. memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
  1727. (*curLen)+=keyw_len;
  1728. curPos+=keyw_len;
  1729. /* If we had a single line if XXX which reaches an else (needs odd
  1730. syntax like if 1=1 command && (command) else command we pretended
  1731. to add brackets for the if, so they are now over */
  1732. if (resetAtEndOfLine) {
  1733. WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
  1734. resetAtEndOfLine = FALSE;
  1735. curDepth = lineCurDepth;
  1736. }
  1737. continue;
  1738. /* In a for loop, the DO command will follow a close bracket followed by
  1739. whitespace, followed by DO, ie closeBracket inserts a NULL entry, curLen
  1740. is then 0, and all whitespace is skipped */
  1741. } else if (inFor && WCMD_keyword_ws_found(L"do", curPos)) {
  1742. const int keyw_len = lstrlenW(L"do") + 1;
  1743. WINE_TRACE("Found 'DO '\n");
  1744. lastWasDo = TRUE;
  1745. onlyWhiteSpace = TRUE;
  1746. memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
  1747. (*curLen)+=keyw_len;
  1748. curPos+=keyw_len;
  1749. continue;
  1750. }
  1751. } else if (curCopyTo == curString) {
  1752. /* Special handling for the 'FOR' command */
  1753. if (inFor && lastWasWhiteSpace) {
  1754. WINE_TRACE("Found 'FOR ', comparing next parm: '%s'\n", wine_dbgstr_w(curPos));
  1755. if (WCMD_keyword_ws_found(L"in", curPos)) {
  1756. const int keyw_len = lstrlenW(L"in") + 1;
  1757. WINE_TRACE("Found 'IN '\n");
  1758. lastWasIn = TRUE;
  1759. onlyWhiteSpace = TRUE;
  1760. memcpy(&curCopyTo[*curLen], curPos, keyw_len*sizeof(WCHAR));
  1761. (*curLen)+=keyw_len;
  1762. curPos+=keyw_len;
  1763. continue;
  1764. }
  1765. }
  1766. }
  1767. /* Nothing 'ends' a one line statement (e.g. REM or :labels mean
  1768. the &&, quotes and redirection etc are ineffective, so just force
  1769. the use of the default processing by skipping character specific
  1770. matching below) */
  1771. if (!inOneLine) thisChar = *curPos;
  1772. else thisChar = 'X'; /* Character with no special processing */
  1773. lastWasWhiteSpace = FALSE; /* Will be reset below */
  1774. lastWasCaret = FALSE;
  1775. switch (thisChar) {
  1776. case '=': /* drop through - ignore token delimiters at the start of a command */
  1777. case ',': /* drop through - ignore token delimiters at the start of a command */
  1778. case '\t':/* drop through - ignore token delimiters at the start of a command */
  1779. case ' ':
  1780. /* If a redirect in place, it ends here */
  1781. if (!inQuotes && !lastWasRedirect) {
  1782. /* If finishing off a redirect, add a whitespace delimiter */
  1783. if (curCopyTo == curRedirs) {
  1784. curCopyTo[(*curLen)++] = ' ';
  1785. }
  1786. curCopyTo = curString;
  1787. curLen = &curStringLen;
  1788. }
  1789. if (*curLen > 0) {
  1790. curCopyTo[(*curLen)++] = *curPos;
  1791. }
  1792. /* Remember just processed whitespace */
  1793. lastWasWhiteSpace = TRUE;
  1794. break;
  1795. case '>': /* drop through - handle redirect chars the same */
  1796. case '<':
  1797. /* Make a redirect start here */
  1798. if (!inQuotes) {
  1799. curCopyTo = curRedirs;
  1800. curLen = &curRedirsLen;
  1801. lastWasRedirect = TRUE;
  1802. }
  1803. /* See if 1>, 2> etc, in which case we have some patching up
  1804. to do (provided there's a preceding whitespace, and enough
  1805. chars read so far) */
  1806. if (curStringLen > 2
  1807. && (*(curPos-1)>='1') && (*(curPos-1)<='9')
  1808. && ((*(curPos-2)==' ') || (*(curPos-2)=='\t'))) {
  1809. curStringLen--;
  1810. curString[curStringLen] = 0x00;
  1811. curCopyTo[(*curLen)++] = *(curPos-1);
  1812. }
  1813. curCopyTo[(*curLen)++] = *curPos;
  1814. /* If a redirect is immediately followed by '&' (ie. 2>&1) then
  1815. do not process that ampersand as an AND operator */
  1816. if (thisChar == '>' && *(curPos+1) == '&') {
  1817. curCopyTo[(*curLen)++] = *(curPos+1);
  1818. curPos++;
  1819. }
  1820. break;
  1821. case '|': /* Pipe character only if not || */
  1822. if (!inQuotes) {
  1823. lastWasRedirect = FALSE;
  1824. /* Add an entry to the command list */
  1825. if (curStringLen > 0) {
  1826. /* Add the current command */
  1827. WCMD_addCommand(curString, &curStringLen,
  1828. curRedirs, &curRedirsLen,
  1829. &curCopyTo, &curLen,
  1830. prevDelim, curDepth,
  1831. &lastEntry, output);
  1832. }
  1833. if (*(curPos+1) == '|') {
  1834. curPos++; /* Skip other | */
  1835. prevDelim = CMD_ONFAILURE;
  1836. } else {
  1837. prevDelim = CMD_PIPE;
  1838. }
  1839. /* If in an IF or ELSE statement, put subsequent chained
  1840. commands at a higher depth as if brackets were supplied
  1841. but remember to reset to the original depth at EOL */
  1842. if ((inIf || inElse) && curDepth == lineCurDepth) {
  1843. curDepth++;
  1844. resetAtEndOfLine = TRUE;
  1845. }
  1846. } else {
  1847. curCopyTo[(*curLen)++] = *curPos;
  1848. }
  1849. break;
  1850. case '"': if (WCMD_IsEndQuote(curPos, inQuotes)) {
  1851. inQuotes--;
  1852. } else {
  1853. inQuotes++; /* Quotes within quotes are fun! */
  1854. }
  1855. curCopyTo[(*curLen)++] = *curPos;
  1856. lastWasRedirect = FALSE;
  1857. break;
  1858. case '(': /* If a '(' is the first non whitespace in a command portion
  1859. ie start of line or just after &&, then we read until an
  1860. unquoted ) is found */
  1861. WINE_TRACE("Found '(' conditions: curLen(%d), inQ(%d), onlyWS(%d)"
  1862. ", for(%d, In:%d, Do:%d)"
  1863. ", if(%d, else:%d, lwe:%d)\n",
  1864. *curLen, inQuotes,
  1865. onlyWhiteSpace,
  1866. inFor, lastWasIn, lastWasDo,
  1867. inIf, inElse, lastWasElse);
  1868. lastWasRedirect = FALSE;
  1869. /* Ignore open brackets inside the for set */
  1870. if (*curLen == 0 && !inIn) {
  1871. curDepth++;
  1872. /* If in quotes, ignore brackets */
  1873. } else if (inQuotes) {
  1874. curCopyTo[(*curLen)++] = *curPos;
  1875. /* In a FOR loop, an unquoted '(' may occur straight after
  1876. IN or DO
  1877. In an IF statement just handle it regardless as we don't
  1878. parse the operands
  1879. In an ELSE statement, only allow it straight away after
  1880. the ELSE and whitespace
  1881. */
  1882. } else if ((inIf && !ignoreBracket) ||
  1883. (inElse && lastWasElse && onlyWhiteSpace) ||
  1884. (inFor && (lastWasIn || lastWasDo) && onlyWhiteSpace)) {
  1885. /* If entering into an 'IN', set inIn */
  1886. if (inFor && lastWasIn && onlyWhiteSpace) {
  1887. WINE_TRACE("Inside an IN\n");
  1888. inIn = TRUE;
  1889. }
  1890. /* Add the current command */
  1891. WCMD_addCommand(curString, &curStringLen,
  1892. curRedirs, &curRedirsLen,
  1893. &curCopyTo, &curLen,
  1894. prevDelim, curDepth,
  1895. &lastEntry, output);
  1896. curDepth++;
  1897. } else {
  1898. curCopyTo[(*curLen)++] = *curPos;
  1899. }
  1900. break;
  1901. case '^': if (!inQuotes) {
  1902. /* If we reach the end of the input, we need to wait for more */
  1903. if (*(curPos+1) == 0x00) {
  1904. lastWasCaret = TRUE;
  1905. WINE_TRACE("Caret found at end of line\n");
  1906. break;
  1907. }
  1908. curPos++;
  1909. }
  1910. curCopyTo[(*curLen)++] = *curPos;
  1911. break;
  1912. case '&': if (!inQuotes) {
  1913. lastWasRedirect = FALSE;
  1914. /* Add an entry to the command list */
  1915. if (curStringLen > 0) {
  1916. /* Add the current command */
  1917. WCMD_addCommand(curString, &curStringLen,
  1918. curRedirs, &curRedirsLen,
  1919. &curCopyTo, &curLen,
  1920. prevDelim, curDepth,
  1921. &lastEntry, output);
  1922. }
  1923. if (*(curPos+1) == '&') {
  1924. curPos++; /* Skip other & */
  1925. prevDelim = CMD_ONSUCCESS;
  1926. } else {
  1927. prevDelim = CMD_NONE;
  1928. }
  1929. /* If in an IF or ELSE statement, put subsequent chained
  1930. commands at a higher depth as if brackets were supplied
  1931. but remember to reset to the original depth at EOL */
  1932. if ((inIf || inElse) && curDepth == lineCurDepth) {
  1933. curDepth++;
  1934. resetAtEndOfLine = TRUE;
  1935. }
  1936. } else {
  1937. curCopyTo[(*curLen)++] = *curPos;
  1938. }
  1939. break;
  1940. case ')': if (!inQuotes && curDepth > 0) {
  1941. lastWasRedirect = FALSE;
  1942. /* Add the current command if there is one */
  1943. if (curStringLen) {
  1944. /* Add the current command */
  1945. WCMD_addCommand(curString, &curStringLen,
  1946. curRedirs, &curRedirsLen,
  1947. &curCopyTo, &curLen,
  1948. prevDelim, curDepth,
  1949. &lastEntry, output);
  1950. }
  1951. /* Add an empty entry to the command list */
  1952. prevDelim = CMD_NONE;
  1953. WCMD_addCommand(NULL, &curStringLen,
  1954. curRedirs, &curRedirsLen,
  1955. &curCopyTo, &curLen,
  1956. prevDelim, curDepth,
  1957. &lastEntry, output);
  1958. curDepth--;
  1959. /* Leave inIn if necessary */
  1960. if (inIn) inIn = FALSE;
  1961. } else {
  1962. curCopyTo[(*curLen)++] = *curPos;
  1963. }
  1964. break;
  1965. default:
  1966. lastWasRedirect = FALSE;
  1967. curCopyTo[(*curLen)++] = *curPos;
  1968. }
  1969. curPos++;
  1970. /* At various times we need to know if we have only skipped whitespace,
  1971. so reset this variable and then it will remain true until a non
  1972. whitespace is found */
  1973. if ((thisChar != ' ') && (thisChar != '\t') && (thisChar != '\n'))
  1974. onlyWhiteSpace = FALSE;
  1975. /* Flag end of interest in FOR DO and IN parms once something has been processed */
  1976. if (!lastWasWhiteSpace) {
  1977. lastWasIn = lastWasDo = FALSE;
  1978. }
  1979. /* If we have reached the end, add this command into the list
  1980. Do not add command to list if escape char ^ was last */
  1981. if (*curPos == 0x00 && !lastWasCaret && *curLen > 0) {
  1982. /* Add an entry to the command list */
  1983. WCMD_addCommand(curString, &curStringLen,
  1984. curRedirs, &curRedirsLen,
  1985. &curCopyTo, &curLen,
  1986. prevDelim, curDepth,
  1987. &lastEntry, output);
  1988. /* If we had a single line if or else, and we pretended to add
  1989. brackets, end them now */
  1990. if (resetAtEndOfLine) {
  1991. WINE_TRACE("Resetting curdepth at end of line to %d\n", lineCurDepth);
  1992. resetAtEndOfLine = FALSE;
  1993. curDepth = lineCurDepth;
  1994. }
  1995. }
  1996. /* If we have reached the end of the string, see if bracketing or
  1997. final caret is outstanding */
  1998. if (*curPos == 0x00 && (curDepth > 0 || lastWasCaret) &&
  1999. readFrom != INVALID_HANDLE_VALUE) {
  2000. WCHAR *extraData;
  2001. WINE_TRACE("Need to read more data as outstanding brackets or carets\n");
  2002. inOneLine = FALSE;
  2003. prevDelim = CMD_NONE;
  2004. inQuotes = 0;
  2005. memset(extraSpace, 0x00, (MAXSTRING+1) * sizeof(WCHAR));
  2006. extraData = extraSpace;
  2007. /* Read more, skipping any blank lines */
  2008. do {
  2009. WINE_TRACE("Read more input\n");
  2010. if (!context) WCMD_output_asis( WCMD_LoadMessage(WCMD_MOREPROMPT));
  2011. if (!WCMD_fgets(extraData, MAXSTRING, readFrom))
  2012. break;
  2013. /* Edge case for carets - a completely blank line (i.e. was just
  2014. CRLF) is oddly added as an LF but then more data is received (but
  2015. only once more!) */
  2016. if (lastWasCaret) {
  2017. if (*extraSpace == 0x00) {
  2018. WINE_TRACE("Read nothing, so appending LF char and will try again\n");
  2019. *extraData++ = '\r';
  2020. *extraData = 0x00;
  2021. } else break;
  2022. }
  2023. } while (*extraData == 0x00);
  2024. curPos = extraSpace;
  2025. /* Skip preceding whitespace */
  2026. while (*curPos == ' ' || *curPos == '\t') curPos++;
  2027. /* Replace env vars if in a batch context */
  2028. if (context) handleExpansion(curPos, FALSE, FALSE);
  2029. /* Continue to echo commands IF echo is on and in batch program */
  2030. if (context && echo_mode && *curPos && *curPos != '@') {
  2031. WCMD_output_asis(extraSpace);
  2032. WCMD_output_asis(L"\r\n");
  2033. }
  2034. /* Skip repeated 'no echo' characters and whitespace */
  2035. while (*curPos == '@' || *curPos == ' ' || *curPos == '\t') curPos++;
  2036. }
  2037. }
  2038. /* Dump out the parsed output */
  2039. WCMD_DumpCommands(*output);
  2040. return extraSpace;
  2041. }
  2042. /***************************************************************************
  2043. * WCMD_process_commands
  2044. *
  2045. * Process all the commands read in so far
  2046. */
  2047. CMD_LIST *WCMD_process_commands(CMD_LIST *thisCmd, BOOL oneBracket,
  2048. BOOL retrycall) {
  2049. int bdepth = -1;
  2050. if (thisCmd && oneBracket) bdepth = thisCmd->bracketDepth;
  2051. /* Loop through the commands, processing them one by one */
  2052. while (thisCmd) {
  2053. CMD_LIST *origCmd = thisCmd;
  2054. /* If processing one bracket only, and we find the end bracket
  2055. entry (or less), return */
  2056. if (oneBracket && !thisCmd->command &&
  2057. bdepth <= thisCmd->bracketDepth) {
  2058. WINE_TRACE("Finished bracket @ %p, next command is %p\n",
  2059. thisCmd, thisCmd->nextcommand);
  2060. return thisCmd->nextcommand;
  2061. }
  2062. /* Ignore the NULL entries a ')' inserts (Only 'if' cares
  2063. about them and it will be handled in there)
  2064. Also, skip over any batch labels (eg. :fred) */
  2065. if (thisCmd->command && thisCmd->command[0] != ':') {
  2066. WINE_TRACE("Executing command: '%s'\n", wine_dbgstr_w(thisCmd->command));
  2067. WCMD_execute (thisCmd->command, thisCmd->redirects, &thisCmd, retrycall);
  2068. }
  2069. /* Step on unless the command itself already stepped on */
  2070. if (thisCmd == origCmd) thisCmd = thisCmd->nextcommand;
  2071. }
  2072. return NULL;
  2073. }
  2074. /***************************************************************************
  2075. * WCMD_free_commands
  2076. *
  2077. * Frees the storage held for a parsed command line
  2078. * - This is not done in the process_commands, as eventually the current
  2079. * pointer will be modified within the commands, and hence a single free
  2080. * routine is simpler
  2081. */
  2082. void WCMD_free_commands(CMD_LIST *cmds) {
  2083. /* Loop through the commands, freeing them one by one */
  2084. while (cmds) {
  2085. CMD_LIST *thisCmd = cmds;
  2086. cmds = cmds->nextcommand;
  2087. heap_free(thisCmd->command);
  2088. heap_free(thisCmd->redirects);
  2089. heap_free(thisCmd);
  2090. }
  2091. }
  2092. /*****************************************************************************
  2093. * Main entry point. This is a console application so we have a main() not a
  2094. * winmain().
  2095. */
  2096. int __cdecl wmain (int argc, WCHAR *argvW[])
  2097. {
  2098. WCHAR *cmdLine = NULL;
  2099. WCHAR *cmd = NULL;
  2100. WCHAR string[1024];
  2101. WCHAR envvar[4];
  2102. BOOL promptNewLine = TRUE;
  2103. BOOL opt_q;
  2104. int opt_t = 0;
  2105. WCHAR comspec[MAX_PATH];
  2106. CMD_LIST *toExecute = NULL; /* Commands left to be executed */
  2107. RTL_OSVERSIONINFOEXW osv;
  2108. char osver[50];
  2109. STARTUPINFOW startupInfo;
  2110. const WCHAR *arg;
  2111. if (!GetEnvironmentVariableW(L"COMSPEC", comspec, ARRAY_SIZE(comspec)))
  2112. {
  2113. GetSystemDirectoryW(comspec, ARRAY_SIZE(comspec) - ARRAY_SIZE(L"\\cmd.exe"));
  2114. lstrcatW(comspec, L"\\cmd.exe");
  2115. SetEnvironmentVariableW(L"COMSPEC", comspec);
  2116. }
  2117. srand(time(NULL));
  2118. /* Get the windows version being emulated */
  2119. osv.dwOSVersionInfoSize = sizeof(osv);
  2120. RtlGetVersion(&osv);
  2121. /* Pre initialize some messages */
  2122. lstrcpyW(anykey, WCMD_LoadMessage(WCMD_ANYKEY));
  2123. sprintf(osver, "%d.%d.%d", osv.dwMajorVersion, osv.dwMinorVersion, osv.dwBuildNumber);
  2124. cmd = WCMD_format_string(WCMD_LoadMessage(WCMD_VERSION), osver);
  2125. lstrcpyW(version_string, cmd);
  2126. LocalFree(cmd);
  2127. cmd = NULL;
  2128. /* Can't use argc/argv as it will have stripped quotes from parameters
  2129. * meaning cmd.exe /C echo "quoted string" is impossible
  2130. */
  2131. cmdLine = GetCommandLineW();
  2132. WINE_TRACE("Full commandline '%s'\n", wine_dbgstr_w(cmdLine));
  2133. while (*cmdLine && *cmdLine != '/') ++cmdLine;
  2134. opt_c = opt_k = opt_q = opt_s = FALSE;
  2135. for (arg = cmdLine; *arg; ++arg)
  2136. {
  2137. if (arg[0] != '/')
  2138. continue;
  2139. switch (towlower(arg[1]))
  2140. {
  2141. case 'a':
  2142. unicodeOutput = FALSE;
  2143. break;
  2144. case 'c':
  2145. opt_c = TRUE;
  2146. break;
  2147. case 'k':
  2148. opt_k = TRUE;
  2149. break;
  2150. case 'q':
  2151. opt_q = TRUE;
  2152. break;
  2153. case 's':
  2154. opt_s = TRUE;
  2155. break;
  2156. case 't':
  2157. if (arg[2] == ':')
  2158. opt_t = wcstoul(&arg[3], NULL, 16);
  2159. break;
  2160. case 'u':
  2161. unicodeOutput = TRUE;
  2162. break;
  2163. case 'v':
  2164. if (arg[2] == ':')
  2165. delayedsubst = wcsnicmp(&arg[3], L"OFF", 3);
  2166. break;
  2167. }
  2168. if (opt_c || opt_k)
  2169. {
  2170. arg += 2;
  2171. break;
  2172. }
  2173. }
  2174. while (*arg && wcschr(L" \t,=;", *arg)) arg++;
  2175. if (opt_q) {
  2176. WCMD_echo(L"OFF");
  2177. }
  2178. /* Until we start to read from the keyboard, stay as non-interactive */
  2179. interactive = FALSE;
  2180. SetEnvironmentVariableW(L"PROMPT", L"$P$G");
  2181. if (opt_c || opt_k) {
  2182. int len;
  2183. WCHAR *q1 = NULL,*q2 = NULL,*p;
  2184. /* Take a copy */
  2185. cmd = heap_strdupW(arg);
  2186. /* opt_s left unflagged if the command starts with and contains exactly
  2187. * one quoted string (exactly two quote characters). The quoted string
  2188. * must be an executable name that has whitespace and must not have the
  2189. * following characters: &<>()@^| */
  2190. if (!opt_s) {
  2191. /* 1. Confirm there is at least one quote */
  2192. q1 = wcschr(arg, '"');
  2193. if (!q1) opt_s=1;
  2194. }
  2195. if (!opt_s) {
  2196. /* 2. Confirm there is a second quote */
  2197. q2 = wcschr(q1+1, '"');
  2198. if (!q2) opt_s=1;
  2199. }
  2200. if (!opt_s) {
  2201. /* 3. Ensure there are no more quotes */
  2202. if (wcschr(q2+1, '"')) opt_s=1;
  2203. }
  2204. /* check first parameter for a space and invalid characters. There must not be any
  2205. * invalid characters, but there must be one or more whitespace */
  2206. if (!opt_s) {
  2207. opt_s = TRUE;
  2208. p=q1;
  2209. while (p!=q2) {
  2210. if (*p=='&' || *p=='<' || *p=='>' || *p=='(' || *p==')'
  2211. || *p=='@' || *p=='^' || *p=='|') {
  2212. opt_s = TRUE;
  2213. break;
  2214. }
  2215. if (*p==' ' || *p=='\t')
  2216. opt_s = FALSE;
  2217. p++;
  2218. }
  2219. }
  2220. WINE_TRACE("/c command line: '%s'\n", wine_dbgstr_w(cmd));
  2221. /* Finally, we only stay in new mode IF the first parameter is quoted and
  2222. is a valid executable, i.e. must exist, otherwise drop back to old mode */
  2223. if (!opt_s) {
  2224. WCHAR *thisArg = WCMD_parameter(cmd, 0, NULL, FALSE, TRUE);
  2225. WCHAR pathext[MAXSTRING];
  2226. BOOL found = FALSE;
  2227. /* Now extract PATHEXT */
  2228. len = GetEnvironmentVariableW(L"PATHEXT", pathext, ARRAY_SIZE(pathext));
  2229. if ((len == 0) || (len >= ARRAY_SIZE(pathext))) {
  2230. lstrcpyW(pathext, L".bat;.com;.cmd;.exe");
  2231. }
  2232. /* If the supplied parameter has any directory information, look there */
  2233. WINE_TRACE("First parameter is '%s'\n", wine_dbgstr_w(thisArg));
  2234. if (wcschr(thisArg, '\\') != NULL) {
  2235. GetFullPathNameW(thisArg, ARRAY_SIZE(string), string, NULL);
  2236. WINE_TRACE("Full path name '%s'\n", wine_dbgstr_w(string));
  2237. p = string + lstrlenW(string);
  2238. /* Does file exist with this name? */
  2239. if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
  2240. WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
  2241. found = TRUE;
  2242. } else {
  2243. WCHAR *thisExt = pathext;
  2244. /* No - try with each of the PATHEXT extensions */
  2245. while (!found && thisExt) {
  2246. WCHAR *nextExt = wcschr(thisExt, ';');
  2247. if (nextExt) {
  2248. memcpy(p, thisExt, (nextExt-thisExt) * sizeof(WCHAR));
  2249. p[(nextExt-thisExt)] = 0x00;
  2250. thisExt = nextExt+1;
  2251. } else {
  2252. lstrcpyW(p, thisExt);
  2253. thisExt = NULL;
  2254. }
  2255. /* Does file exist with this extension appended? */
  2256. if (GetFileAttributesW(string) != INVALID_FILE_ATTRIBUTES) {
  2257. WINE_TRACE("Found file as '%s'\n", wine_dbgstr_w(string));
  2258. found = TRUE;
  2259. }
  2260. }
  2261. }
  2262. /* Otherwise we now need to look in the path to see if we can find it */
  2263. } else {
  2264. /* Does file exist with this name? */
  2265. if (SearchPathW(NULL, thisArg, NULL, ARRAY_SIZE(string), string, NULL) != 0) {
  2266. WINE_TRACE("Found on path as '%s'\n", wine_dbgstr_w(string));
  2267. found = TRUE;
  2268. } else {
  2269. WCHAR *thisExt = pathext;
  2270. /* No - try with each of the PATHEXT extensions */
  2271. while (!found && thisExt) {
  2272. WCHAR *nextExt = wcschr(thisExt, ';');
  2273. if (nextExt) {
  2274. *nextExt = 0;
  2275. nextExt = nextExt+1;
  2276. } else {
  2277. nextExt = NULL;
  2278. }
  2279. /* Does file exist with this extension? */
  2280. if (SearchPathW(NULL, thisArg, thisExt, ARRAY_SIZE(string), string, NULL) != 0) {
  2281. WINE_TRACE("Found on path as '%s' with extension '%s'\n", wine_dbgstr_w(string),
  2282. wine_dbgstr_w(thisExt));
  2283. found = TRUE;
  2284. }
  2285. thisExt = nextExt;
  2286. }
  2287. }
  2288. }
  2289. /* If not found, drop back to old behaviour */
  2290. if (!found) {
  2291. WINE_TRACE("Binary not found, dropping back to old behaviour\n");
  2292. opt_s = TRUE;
  2293. }
  2294. }
  2295. /* strip first and last quote characters if opt_s; check for invalid
  2296. * executable is done later */
  2297. if (opt_s && *cmd=='\"')
  2298. WCMD_strip_quotes(cmd);
  2299. }
  2300. /* Save cwd into appropriate env var (Must be before the /c processing */
  2301. GetCurrentDirectoryW(ARRAY_SIZE(string), string);
  2302. if (IsCharAlphaW(string[0]) && string[1] == ':') {
  2303. wsprintfW(envvar, L"=%c:", string[0]);
  2304. SetEnvironmentVariableW(envvar, string);
  2305. WINE_TRACE("Set %s to %s\n", wine_dbgstr_w(envvar), wine_dbgstr_w(string));
  2306. }
  2307. if (opt_c) {
  2308. /* If we do a "cmd /c command", we don't want to allocate a new
  2309. * console since the command returns immediately. Rather, we use
  2310. * the currently allocated input and output handles. This allows
  2311. * us to pipe to and read from the command interpreter.
  2312. */
  2313. /* Parse the command string, without reading any more input */
  2314. WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
  2315. WCMD_process_commands(toExecute, FALSE, FALSE);
  2316. WCMD_free_commands(toExecute);
  2317. toExecute = NULL;
  2318. heap_free(cmd);
  2319. return errorlevel;
  2320. }
  2321. GetStartupInfoW(&startupInfo);
  2322. if (startupInfo.lpTitle != NULL)
  2323. SetConsoleTitleW(startupInfo.lpTitle);
  2324. else
  2325. SetConsoleTitleW(WCMD_LoadMessage(WCMD_CONSTITLE));
  2326. /* Note: cmd.exe /c dir does not get a new color, /k dir does */
  2327. if (opt_t) {
  2328. if (!(((opt_t & 0xF0) >> 4) == (opt_t & 0x0F))) {
  2329. defaultColor = opt_t & 0xFF;
  2330. param1[0] = 0x00;
  2331. WCMD_color();
  2332. }
  2333. } else {
  2334. /* Check HKCU\Software\Microsoft\Command Processor
  2335. Then HKLM\Software\Microsoft\Command Processor
  2336. for defaultcolour value
  2337. Note Can be supplied as DWORD or REG_SZ
  2338. Note2 When supplied as REG_SZ it's in decimal!!! */
  2339. HKEY key;
  2340. DWORD type;
  2341. DWORD value=0, size=4;
  2342. static const WCHAR regKeyW[] = L"Software\\Microsoft\\Command Processor";
  2343. if (RegOpenKeyExW(HKEY_CURRENT_USER, regKeyW,
  2344. 0, KEY_READ, &key) == ERROR_SUCCESS) {
  2345. WCHAR strvalue[4];
  2346. /* See if DWORD or REG_SZ */
  2347. if (RegQueryValueExW(key, L"DefaultColor", NULL, &type, NULL, NULL) == ERROR_SUCCESS) {
  2348. if (type == REG_DWORD) {
  2349. size = sizeof(DWORD);
  2350. RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
  2351. } else if (type == REG_SZ) {
  2352. size = sizeof(strvalue);
  2353. RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
  2354. value = wcstoul(strvalue, NULL, 10);
  2355. }
  2356. }
  2357. RegCloseKey(key);
  2358. }
  2359. if (value == 0 && RegOpenKeyExW(HKEY_LOCAL_MACHINE, regKeyW,
  2360. 0, KEY_READ, &key) == ERROR_SUCCESS) {
  2361. WCHAR strvalue[4];
  2362. /* See if DWORD or REG_SZ */
  2363. if (RegQueryValueExW(key, L"DefaultColor", NULL, &type,
  2364. NULL, NULL) == ERROR_SUCCESS) {
  2365. if (type == REG_DWORD) {
  2366. size = sizeof(DWORD);
  2367. RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)&value, &size);
  2368. } else if (type == REG_SZ) {
  2369. size = sizeof(strvalue);
  2370. RegQueryValueExW(key, L"DefaultColor", NULL, NULL, (BYTE *)strvalue, &size);
  2371. value = wcstoul(strvalue, NULL, 10);
  2372. }
  2373. }
  2374. RegCloseKey(key);
  2375. }
  2376. /* If one found, set the screen to that colour */
  2377. if (!(((value & 0xF0) >> 4) == (value & 0x0F))) {
  2378. defaultColor = value & 0xFF;
  2379. param1[0] = 0x00;
  2380. WCMD_color();
  2381. }
  2382. }
  2383. if (opt_k) {
  2384. /* Parse the command string, without reading any more input */
  2385. WCMD_ReadAndParseLine(cmd, &toExecute, INVALID_HANDLE_VALUE);
  2386. WCMD_process_commands(toExecute, FALSE, FALSE);
  2387. WCMD_free_commands(toExecute);
  2388. toExecute = NULL;
  2389. heap_free(cmd);
  2390. }
  2391. /*
  2392. * Loop forever getting commands and executing them.
  2393. */
  2394. interactive = TRUE;
  2395. if (!opt_k) WCMD_version ();
  2396. while (TRUE) {
  2397. /* Read until EOF (which for std input is never, but if redirect
  2398. in place, may occur */
  2399. if (echo_mode) WCMD_show_prompt(promptNewLine);
  2400. if (!WCMD_ReadAndParseLine(NULL, &toExecute, GetStdHandle(STD_INPUT_HANDLE)))
  2401. break;
  2402. WCMD_process_commands(toExecute, FALSE, FALSE);
  2403. WCMD_free_commands(toExecute);
  2404. promptNewLine = !!toExecute;
  2405. toExecute = NULL;
  2406. }
  2407. return 0;
  2408. }