builtins.c 162 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942194319441945194619471948194919501951195219531954195519561957195819591960196119621963196419651966196719681969197019711972197319741975197619771978197919801981198219831984198519861987198819891990199119921993199419951996199719981999200020012002200320042005200620072008200920102011201220132014201520162017201820192020202120222023202420252026202720282029203020312032203320342035203620372038203920402041204220432044204520462047204820492050205120522053205420552056205720582059206020612062206320642065206620672068206920702071207220732074207520762077207820792080208120822083208420852086208720882089209020912092209320942095209620972098209921002101210221032104210521062107210821092110211121122113211421152116211721182119212021212122212321242125212621272128212921302131213221332134213521362137213821392140214121422143214421452146214721482149215021512152215321542155215621572158215921602161216221632164216521662167216821692170217121722173217421752176217721782179218021812182218321842185218621872188218921902191219221932194219521962197219821992200220122022203220422052206220722082209221022112212221322142215221622172218221922202221222222232224222522262227222822292230223122322233223422352236223722382239224022412242224322442245224622472248224922502251225222532254225522562257225822592260226122622263226422652266226722682269227022712272227322742275227622772278227922802281228222832284228522862287228822892290229122922293229422952296229722982299230023012302230323042305230623072308230923102311231223132314231523162317231823192320232123222323232423252326232723282329233023312332233323342335233623372338233923402341234223432344234523462347234823492350235123522353235423552356235723582359236023612362236323642365236623672368236923702371237223732374237523762377237823792380238123822383238423852386238723882389239023912392239323942395239623972398239924002401240224032404240524062407240824092410241124122413241424152416241724182419242024212422242324242425242624272428242924302431243224332434243524362437243824392440244124422443244424452446244724482449245024512452245324542455245624572458245924602461246224632464246524662467246824692470247124722473247424752476247724782479248024812482248324842485248624872488248924902491249224932494249524962497249824992500250125022503250425052506250725082509251025112512251325142515251625172518251925202521252225232524252525262527252825292530253125322533253425352536253725382539254025412542254325442545254625472548254925502551255225532554255525562557255825592560256125622563256425652566256725682569257025712572257325742575257625772578257925802581258225832584258525862587258825892590259125922593259425952596259725982599260026012602260326042605260626072608260926102611261226132614261526162617261826192620262126222623262426252626262726282629263026312632263326342635263626372638263926402641264226432644264526462647264826492650265126522653265426552656265726582659266026612662266326642665266626672668266926702671267226732674267526762677267826792680268126822683268426852686268726882689269026912692269326942695269626972698269927002701270227032704270527062707270827092710271127122713271427152716271727182719272027212722272327242725272627272728272927302731273227332734273527362737273827392740274127422743274427452746274727482749275027512752275327542755275627572758275927602761276227632764276527662767276827692770277127722773277427752776277727782779278027812782278327842785278627872788278927902791279227932794279527962797279827992800280128022803280428052806280728082809281028112812281328142815281628172818281928202821282228232824282528262827282828292830283128322833283428352836283728382839284028412842284328442845284628472848284928502851285228532854285528562857285828592860286128622863286428652866286728682869287028712872287328742875287628772878287928802881288228832884288528862887288828892890289128922893289428952896289728982899290029012902290329042905290629072908290929102911291229132914291529162917291829192920292129222923292429252926292729282929293029312932293329342935293629372938293929402941294229432944294529462947294829492950295129522953295429552956295729582959296029612962296329642965296629672968296929702971297229732974297529762977297829792980298129822983298429852986298729882989299029912992299329942995299629972998299930003001300230033004300530063007300830093010301130123013301430153016301730183019302030213022302330243025302630273028302930303031303230333034303530363037303830393040304130423043304430453046304730483049305030513052305330543055305630573058305930603061306230633064306530663067306830693070307130723073307430753076307730783079308030813082308330843085308630873088308930903091309230933094309530963097309830993100310131023103310431053106310731083109311031113112311331143115311631173118311931203121312231233124312531263127312831293130313131323133313431353136313731383139314031413142314331443145314631473148314931503151315231533154315531563157315831593160316131623163316431653166316731683169317031713172317331743175317631773178317931803181318231833184318531863187318831893190319131923193319431953196319731983199320032013202320332043205320632073208320932103211321232133214321532163217321832193220322132223223322432253226322732283229323032313232323332343235323632373238323932403241324232433244324532463247324832493250325132523253325432553256325732583259326032613262326332643265326632673268326932703271327232733274327532763277327832793280328132823283328432853286328732883289329032913292329332943295329632973298329933003301330233033304330533063307330833093310331133123313331433153316331733183319332033213322332333243325332633273328332933303331333233333334333533363337333833393340334133423343334433453346334733483349335033513352335333543355335633573358335933603361336233633364336533663367336833693370337133723373337433753376337733783379338033813382338333843385338633873388338933903391339233933394339533963397339833993400340134023403340434053406340734083409341034113412341334143415341634173418341934203421342234233424342534263427342834293430343134323433343434353436343734383439344034413442344334443445344634473448344934503451345234533454345534563457345834593460346134623463346434653466346734683469347034713472347334743475347634773478347934803481348234833484348534863487348834893490349134923493349434953496349734983499350035013502350335043505350635073508350935103511351235133514351535163517351835193520352135223523352435253526352735283529353035313532353335343535353635373538353935403541354235433544354535463547354835493550355135523553355435553556355735583559356035613562356335643565356635673568356935703571357235733574357535763577357835793580358135823583358435853586358735883589359035913592359335943595359635973598359936003601360236033604360536063607360836093610361136123613361436153616361736183619362036213622362336243625362636273628362936303631363236333634363536363637363836393640364136423643364436453646364736483649365036513652365336543655365636573658365936603661366236633664366536663667366836693670367136723673367436753676367736783679368036813682368336843685368636873688368936903691369236933694369536963697369836993700370137023703370437053706370737083709371037113712371337143715371637173718371937203721372237233724372537263727372837293730373137323733373437353736373737383739374037413742374337443745374637473748374937503751375237533754375537563757375837593760376137623763376437653766376737683769377037713772377337743775377637773778377937803781378237833784378537863787378837893790379137923793379437953796379737983799380038013802380338043805380638073808380938103811381238133814381538163817381838193820382138223823382438253826382738283829383038313832383338343835383638373838383938403841384238433844384538463847384838493850385138523853385438553856385738583859386038613862386338643865386638673868386938703871387238733874387538763877387838793880388138823883388438853886388738883889389038913892389338943895389638973898389939003901390239033904390539063907390839093910391139123913391439153916391739183919392039213922392339243925392639273928392939303931393239333934393539363937393839393940394139423943394439453946394739483949395039513952395339543955395639573958395939603961396239633964396539663967396839693970397139723973397439753976397739783979398039813982398339843985398639873988398939903991399239933994399539963997399839994000400140024003400440054006400740084009401040114012401340144015401640174018401940204021402240234024402540264027402840294030403140324033403440354036403740384039404040414042404340444045404640474048404940504051405240534054405540564057405840594060406140624063406440654066406740684069407040714072407340744075407640774078407940804081408240834084408540864087408840894090409140924093409440954096409740984099410041014102410341044105410641074108410941104111411241134114411541164117411841194120412141224123412441254126412741284129413041314132413341344135413641374138413941404141414241434144414541464147414841494150415141524153415441554156415741584159416041614162416341644165416641674168416941704171417241734174417541764177417841794180418141824183418441854186418741884189419041914192419341944195419641974198419942004201420242034204420542064207420842094210421142124213421442154216421742184219422042214222422342244225422642274228422942304231423242334234423542364237423842394240424142424243424442454246424742484249425042514252425342544255425642574258425942604261426242634264426542664267426842694270427142724273427442754276427742784279428042814282428342844285428642874288428942904291429242934294429542964297429842994300430143024303430443054306430743084309431043114312431343144315431643174318431943204321432243234324432543264327432843294330433143324333433443354336433743384339434043414342434343444345434643474348434943504351435243534354435543564357435843594360436143624363436443654366436743684369437043714372437343744375437643774378437943804381438243834384438543864387438843894390439143924393439443954396439743984399440044014402440344044405440644074408440944104411441244134414441544164417441844194420442144224423442444254426442744284429443044314432443344344435443644374438443944404441444244434444444544464447444844494450445144524453445444554456445744584459446044614462446344644465446644674468446944704471447244734474447544764477447844794480448144824483448444854486448744884489449044914492449344944495449644974498449945004501450245034504450545064507450845094510451145124513451445154516451745184519452045214522452345244525452645274528452945304531453245334534453545364537453845394540454145424543454445454546454745484549455045514552455345544555455645574558455945604561456245634564456545664567456845694570457145724573457445754576457745784579458045814582458345844585458645874588458945904591459245934594459545964597459845994600460146024603460446054606460746084609461046114612461346144615461646174618461946204621462246234624462546264627462846294630463146324633463446354636463746384639464046414642464346444645464646474648464946504651465246534654465546564657465846594660466146624663466446654666466746684669467046714672467346744675467646774678467946804681468246834684468546864687468846894690469146924693469446954696469746984699470047014702470347044705470647074708470947104711471247134714471547164717471847194720472147224723472447254726472747284729473047314732473347344735473647374738473947404741474247434744474547464747474847494750475147524753475447554756475747584759476047614762476347644765476647674768476947704771477247734774477547764777477847794780478147824783478447854786478747884789479047914792479347944795479647974798479948004801480248034804480548064807480848094810481148124813481448154816481748184819482048214822482348244825482648274828482948304831483248334834483548364837483848394840484148424843484448454846484748484849485048514852485348544855485648574858485948604861486248634864486548664867486848694870487148724873487448754876487748784879488048814882488348844885488648874888488948904891489248934894489548964897489848994900490149024903490449054906490749084909491049114912491349144915491649174918491949204921492249234924492549264927492849294930493149324933493449354936493749384939494049414942494349444945494649474948494949504951495249534954495549564957495849594960496149624963496449654966496749684969497049714972497349744975497649774978497949804981498249834984498549864987498849894990499149924993499449954996499749984999500050015002500350045005500650075008500950105011501250135014501550165017501850195020502150225023502450255026
  1. /*
  2. * CMD - Wine-compatible command line interface - built-in functions.
  3. *
  4. * Copyright (C) 1999 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. * - No support for pipes, shell parameters
  24. * - Lots of functionality missing from builtins
  25. * - Messages etc need international support
  26. */
  27. #define WIN32_LEAN_AND_MEAN
  28. #include "wcmd.h"
  29. #include <shellapi.h>
  30. #include "wine/debug.h"
  31. WINE_DEFAULT_DEBUG_CHANNEL(cmd);
  32. extern int defaultColor;
  33. extern BOOL echo_mode;
  34. extern BOOL interactive;
  35. struct env_stack *pushd_directories;
  36. const WCHAR inbuilt[][10] = {
  37. L"CALL",
  38. L"CD",
  39. L"CHDIR",
  40. L"CLS",
  41. L"COPY",
  42. L"CTTY",
  43. L"DATE",
  44. L"DEL",
  45. L"DIR",
  46. L"ECHO",
  47. L"ERASE",
  48. L"FOR",
  49. L"GOTO",
  50. L"HELP",
  51. L"IF",
  52. L"LABEL",
  53. L"MD",
  54. L"MKDIR",
  55. L"MOVE",
  56. L"PATH",
  57. L"PAUSE",
  58. L"PROMPT",
  59. L"REM",
  60. L"REN",
  61. L"RENAME",
  62. L"RD",
  63. L"RMDIR",
  64. L"SET",
  65. L"SHIFT",
  66. L"START",
  67. L"TIME",
  68. L"TITLE",
  69. L"TYPE",
  70. L"VERIFY",
  71. L"VER",
  72. L"VOL",
  73. L"ENDLOCAL",
  74. L"SETLOCAL",
  75. L"PUSHD",
  76. L"POPD",
  77. L"ASSOC",
  78. L"COLOR",
  79. L"FTYPE",
  80. L"MORE",
  81. L"CHOICE",
  82. L"MKLINK",
  83. L"EXIT"
  84. };
  85. static const WCHAR externals[][10] = {
  86. L"ATTRIB",
  87. L"XCOPY"
  88. };
  89. static HINSTANCE hinst;
  90. struct env_stack *saved_environment;
  91. static BOOL verify_mode = FALSE;
  92. /* set /a routines work from single character operators, but some of the
  93. operators are multiple character ones, especially the assignment ones.
  94. Temporarily represent these using the values below on the operator stack */
  95. #define OP_POSITIVE 'P'
  96. #define OP_NEGATIVE 'N'
  97. #define OP_ASSSIGNMUL 'a'
  98. #define OP_ASSSIGNDIV 'b'
  99. #define OP_ASSSIGNMOD 'c'
  100. #define OP_ASSSIGNADD 'd'
  101. #define OP_ASSSIGNSUB 'e'
  102. #define OP_ASSSIGNAND 'f'
  103. #define OP_ASSSIGNNOT 'g'
  104. #define OP_ASSSIGNOR 'h'
  105. #define OP_ASSSIGNSHL 'i'
  106. #define OP_ASSSIGNSHR 'j'
  107. /* This maintains a stack of operators, holding both the operator precedence
  108. and the single character representation of the operator in question */
  109. typedef struct _OPSTACK
  110. {
  111. int precedence;
  112. WCHAR op;
  113. struct _OPSTACK *next;
  114. } OPSTACK;
  115. /* This maintains a stack of values, where each value can either be a
  116. numeric value, or a string representing an environment variable */
  117. typedef struct _VARSTACK
  118. {
  119. BOOL isnum;
  120. WCHAR *variable;
  121. int value;
  122. struct _VARSTACK *next;
  123. } VARSTACK;
  124. /* This maintains a mapping between the calculated operator and the
  125. single character representation for the assignment operators. */
  126. static struct
  127. {
  128. WCHAR op;
  129. WCHAR calculatedop;
  130. } calcassignments[] =
  131. {
  132. {'*', OP_ASSSIGNMUL},
  133. {'/', OP_ASSSIGNDIV},
  134. {'%', OP_ASSSIGNMOD},
  135. {'+', OP_ASSSIGNADD},
  136. {'-', OP_ASSSIGNSUB},
  137. {'&', OP_ASSSIGNAND},
  138. {'^', OP_ASSSIGNNOT},
  139. {'|', OP_ASSSIGNOR},
  140. {'<', OP_ASSSIGNSHL},
  141. {'>', OP_ASSSIGNSHR},
  142. {' ',' '}
  143. };
  144. /**************************************************************************
  145. * WCMD_ask_confirm
  146. *
  147. * Issue a message and ask for confirmation, waiting on a valid answer.
  148. *
  149. * Returns True if Y (or A) answer is selected
  150. * If optionAll contains a pointer, ALL is allowed, and if answered
  151. * set to TRUE
  152. *
  153. */
  154. static BOOL WCMD_ask_confirm (const WCHAR *message, BOOL showSureText,
  155. BOOL *optionAll) {
  156. UINT msgid;
  157. WCHAR confirm[MAXSTRING];
  158. WCHAR options[MAXSTRING];
  159. WCHAR Ybuffer[MAXSTRING];
  160. WCHAR Nbuffer[MAXSTRING];
  161. WCHAR Abuffer[MAXSTRING];
  162. WCHAR answer[MAX_PATH] = {'\0'};
  163. DWORD count = 0;
  164. /* Load the translated valid answers */
  165. if (showSureText)
  166. LoadStringW(hinst, WCMD_CONFIRM, confirm, ARRAY_SIZE(confirm));
  167. msgid = optionAll ? WCMD_YESNOALL : WCMD_YESNO;
  168. LoadStringW(hinst, msgid, options, ARRAY_SIZE(options));
  169. LoadStringW(hinst, WCMD_YES, Ybuffer, ARRAY_SIZE(Ybuffer));
  170. LoadStringW(hinst, WCMD_NO, Nbuffer, ARRAY_SIZE(Nbuffer));
  171. LoadStringW(hinst, WCMD_ALL, Abuffer, ARRAY_SIZE(Abuffer));
  172. /* Loop waiting on a valid answer */
  173. if (optionAll)
  174. *optionAll = FALSE;
  175. while (1)
  176. {
  177. WCMD_output_asis (message);
  178. if (showSureText)
  179. WCMD_output_asis (confirm);
  180. WCMD_output_asis (options);
  181. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, ARRAY_SIZE(answer), &count);
  182. answer[0] = towupper(answer[0]);
  183. if (answer[0] == Ybuffer[0])
  184. return TRUE;
  185. if (answer[0] == Nbuffer[0])
  186. return FALSE;
  187. if (optionAll && answer[0] == Abuffer[0])
  188. {
  189. *optionAll = TRUE;
  190. return TRUE;
  191. }
  192. }
  193. }
  194. /****************************************************************************
  195. * WCMD_clear_screen
  196. *
  197. * Clear the terminal screen.
  198. */
  199. void WCMD_clear_screen (void) {
  200. /* Emulate by filling the screen from the top left to bottom right with
  201. spaces, then moving the cursor to the top left afterwards */
  202. CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
  203. HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
  204. if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
  205. {
  206. COORD topLeft;
  207. DWORD screenSize, written;
  208. screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
  209. topLeft.X = 0;
  210. topLeft.Y = 0;
  211. FillConsoleOutputCharacterW(hStdOut, ' ', screenSize, topLeft, &written);
  212. FillConsoleOutputAttribute(hStdOut, consoleInfo.wAttributes, screenSize, topLeft, &written);
  213. SetConsoleCursorPosition(hStdOut, topLeft);
  214. }
  215. }
  216. /****************************************************************************
  217. * WCMD_change_tty
  218. *
  219. * Change the default i/o device (ie redirect STDin/STDout).
  220. */
  221. void WCMD_change_tty (void) {
  222. WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
  223. }
  224. /****************************************************************************
  225. * WCMD_choice
  226. *
  227. */
  228. void WCMD_choice (const WCHAR * args) {
  229. WCHAR answer[16];
  230. WCHAR buffer[16];
  231. WCHAR *ptr = NULL;
  232. WCHAR *opt_c = NULL;
  233. WCHAR *my_command = NULL;
  234. WCHAR opt_default = 0;
  235. DWORD opt_timeout = 0;
  236. DWORD count;
  237. DWORD oldmode;
  238. BOOL have_console;
  239. BOOL opt_n = FALSE;
  240. BOOL opt_s = FALSE;
  241. have_console = GetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), &oldmode);
  242. errorlevel = 0;
  243. my_command = heap_strdupW(WCMD_skip_leading_spaces((WCHAR*) args));
  244. ptr = WCMD_skip_leading_spaces(my_command);
  245. while (*ptr == '/') {
  246. switch (towupper(ptr[1])) {
  247. case 'C':
  248. ptr += 2;
  249. /* the colon is optional */
  250. if (*ptr == ':')
  251. ptr++;
  252. if (!*ptr || iswspace(*ptr)) {
  253. WINE_FIXME("bad parameter %s for /C\n", wine_dbgstr_w(ptr));
  254. heap_free(my_command);
  255. return;
  256. }
  257. /* remember the allowed keys (overwrite previous /C option) */
  258. opt_c = ptr;
  259. while (*ptr && (!iswspace(*ptr)))
  260. ptr++;
  261. if (*ptr) {
  262. /* terminate allowed chars */
  263. *ptr = 0;
  264. ptr = WCMD_skip_leading_spaces(&ptr[1]);
  265. }
  266. WINE_TRACE("answer-list: %s\n", wine_dbgstr_w(opt_c));
  267. break;
  268. case 'N':
  269. opt_n = TRUE;
  270. ptr = WCMD_skip_leading_spaces(&ptr[2]);
  271. break;
  272. case 'S':
  273. opt_s = TRUE;
  274. ptr = WCMD_skip_leading_spaces(&ptr[2]);
  275. break;
  276. case 'T':
  277. ptr = &ptr[2];
  278. /* the colon is optional */
  279. if (*ptr == ':')
  280. ptr++;
  281. opt_default = *ptr++;
  282. if (!opt_default || (*ptr != ',')) {
  283. WINE_FIXME("bad option %s for /T\n", opt_default ? wine_dbgstr_w(ptr) : "");
  284. heap_free(my_command);
  285. return;
  286. }
  287. ptr++;
  288. count = 0;
  289. while (((answer[count] = *ptr)) && iswdigit(*ptr) && (count < 15)) {
  290. count++;
  291. ptr++;
  292. }
  293. answer[count] = 0;
  294. opt_timeout = wcstol(answer, NULL, 10);
  295. ptr = WCMD_skip_leading_spaces(ptr);
  296. break;
  297. default:
  298. WINE_FIXME("bad parameter: %s\n", wine_dbgstr_w(ptr));
  299. heap_free(my_command);
  300. return;
  301. }
  302. }
  303. if (opt_timeout)
  304. WINE_FIXME("timeout not supported: %c,%d\n", opt_default, opt_timeout);
  305. if (have_console)
  306. SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), 0);
  307. /* use default keys, when needed: localized versions of "Y"es and "No" */
  308. if (!opt_c) {
  309. LoadStringW(hinst, WCMD_YES, buffer, ARRAY_SIZE(buffer));
  310. LoadStringW(hinst, WCMD_NO, buffer + 1, ARRAY_SIZE(buffer) - 1);
  311. opt_c = buffer;
  312. buffer[2] = 0;
  313. }
  314. /* print the question, when needed */
  315. if (*ptr)
  316. WCMD_output_asis(ptr);
  317. if (!opt_s) {
  318. wcsupr(opt_c);
  319. WINE_TRACE("case insensitive answer-list: %s\n", wine_dbgstr_w(opt_c));
  320. }
  321. if (!opt_n) {
  322. /* print a list of all allowed answers inside brackets */
  323. WCMD_output_asis(L"[");
  324. ptr = opt_c;
  325. answer[1] = 0;
  326. while ((answer[0] = *ptr++)) {
  327. WCMD_output_asis(answer);
  328. if (*ptr)
  329. WCMD_output_asis(L",");
  330. }
  331. WCMD_output_asis(L"]?");
  332. }
  333. while (TRUE) {
  334. /* FIXME: Add support for option /T */
  335. answer[1] = 0; /* terminate single character string */
  336. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), answer, 1, &count);
  337. if (!opt_s)
  338. answer[0] = towupper(answer[0]);
  339. ptr = wcschr(opt_c, answer[0]);
  340. if (ptr) {
  341. WCMD_output_asis(answer);
  342. WCMD_output_asis(L"\r\n");
  343. if (have_console)
  344. SetConsoleMode(GetStdHandle(STD_INPUT_HANDLE), oldmode);
  345. errorlevel = (ptr - opt_c) + 1;
  346. WINE_TRACE("answer: %d\n", errorlevel);
  347. heap_free(my_command);
  348. return;
  349. }
  350. else
  351. {
  352. /* key not allowed: play the bell */
  353. WINE_TRACE("key not allowed: %s\n", wine_dbgstr_w(answer));
  354. WCMD_output_asis(L"\a");
  355. }
  356. }
  357. }
  358. /****************************************************************************
  359. * WCMD_AppendEOF
  360. *
  361. * Adds an EOF onto the end of a file
  362. * Returns TRUE on success
  363. */
  364. static BOOL WCMD_AppendEOF(WCHAR *filename)
  365. {
  366. HANDLE h;
  367. DWORD bytes_written;
  368. char eof = '\x1a';
  369. WINE_TRACE("Appending EOF to %s\n", wine_dbgstr_w(filename));
  370. h = CreateFileW(filename, GENERIC_WRITE, 0, NULL,
  371. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  372. if (h == INVALID_HANDLE_VALUE) {
  373. WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
  374. return FALSE;
  375. } else {
  376. SetFilePointer (h, 0, NULL, FILE_END);
  377. if (!WriteFile(h, &eof, 1, &bytes_written, NULL)) {
  378. WINE_ERR("Failed to append EOF to %s (%d)\n", wine_dbgstr_w(filename), GetLastError());
  379. CloseHandle(h);
  380. return FALSE;
  381. }
  382. CloseHandle(h);
  383. }
  384. return TRUE;
  385. }
  386. /****************************************************************************
  387. * WCMD_IsSameFile
  388. *
  389. * Checks if the two paths reference to the same file
  390. */
  391. static BOOL WCMD_IsSameFile(const WCHAR *name1, const WCHAR *name2)
  392. {
  393. BOOL ret = FALSE;
  394. HANDLE file1 = INVALID_HANDLE_VALUE, file2 = INVALID_HANDLE_VALUE;
  395. BY_HANDLE_FILE_INFORMATION info1, info2;
  396. file1 = CreateFileW(name1, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
  397. if (file1 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file1, &info1))
  398. goto end;
  399. file2 = CreateFileW(name2, 0, FILE_SHARE_DELETE | FILE_SHARE_READ | FILE_SHARE_WRITE, 0, OPEN_EXISTING, 0, 0);
  400. if (file2 == INVALID_HANDLE_VALUE || !GetFileInformationByHandle(file2, &info2))
  401. goto end;
  402. ret = info1.dwVolumeSerialNumber == info2.dwVolumeSerialNumber
  403. && info1.nFileIndexHigh == info2.nFileIndexHigh
  404. && info1.nFileIndexLow == info2.nFileIndexLow;
  405. end:
  406. if (file1 != INVALID_HANDLE_VALUE)
  407. CloseHandle(file1);
  408. if (file2 != INVALID_HANDLE_VALUE)
  409. CloseHandle(file2);
  410. return ret;
  411. }
  412. /****************************************************************************
  413. * WCMD_ManualCopy
  414. *
  415. * Copies from a file
  416. * optionally reading only until EOF (ascii copy)
  417. * optionally appending onto an existing file (append)
  418. * Returns TRUE on success
  419. */
  420. static BOOL WCMD_ManualCopy(WCHAR *srcname, WCHAR *dstname, BOOL ascii, BOOL append)
  421. {
  422. HANDLE in,out;
  423. BOOL ok;
  424. DWORD bytesread, byteswritten;
  425. WINE_TRACE("Manual Copying %s to %s (append?%d)\n",
  426. wine_dbgstr_w(srcname), wine_dbgstr_w(dstname), append);
  427. in = CreateFileW(srcname, GENERIC_READ, 0, NULL,
  428. OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  429. if (in == INVALID_HANDLE_VALUE) {
  430. WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(srcname), GetLastError());
  431. return FALSE;
  432. }
  433. /* Open the output file, overwriting if not appending */
  434. out = CreateFileW(dstname, GENERIC_WRITE, 0, NULL,
  435. append?OPEN_EXISTING:CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
  436. if (out == INVALID_HANDLE_VALUE) {
  437. WINE_ERR("Failed to open %s (%d)\n", wine_dbgstr_w(dstname), GetLastError());
  438. CloseHandle(in);
  439. return FALSE;
  440. }
  441. /* Move to end of destination if we are going to append to it */
  442. if (append) {
  443. SetFilePointer(out, 0, NULL, FILE_END);
  444. }
  445. /* Loop copying data from source to destination until EOF read */
  446. do
  447. {
  448. char buffer[MAXSTRING];
  449. ok = ReadFile(in, buffer, MAXSTRING, &bytesread, NULL);
  450. if (ok) {
  451. /* Stop at first EOF */
  452. if (ascii) {
  453. char *ptr = (char *)memchr((void *)buffer, '\x1a', bytesread);
  454. if (ptr) bytesread = (ptr - buffer);
  455. }
  456. if (bytesread) {
  457. ok = WriteFile(out, buffer, bytesread, &byteswritten, NULL);
  458. if (!ok || byteswritten != bytesread) {
  459. WINE_ERR("Unexpected failure writing to %s, rc=%d\n",
  460. wine_dbgstr_w(dstname), GetLastError());
  461. }
  462. }
  463. } else {
  464. WINE_ERR("Unexpected failure reading from %s, rc=%d\n",
  465. wine_dbgstr_w(srcname), GetLastError());
  466. }
  467. } while (ok && bytesread > 0);
  468. CloseHandle(out);
  469. CloseHandle(in);
  470. return ok;
  471. }
  472. /****************************************************************************
  473. * WCMD_copy
  474. *
  475. * Copy a file or wildcarded set.
  476. * For ascii/binary type copies, it gets complex:
  477. * Syntax on command line is
  478. * ... /a | /b filename /a /b {[ + filename /a /b]} [dest /a /b]
  479. * Where first /a or /b sets 'mode in operation' until another is found
  480. * once another is found, it applies to the file preceding the /a or /b
  481. * In addition each filename can contain wildcards
  482. * To make matters worse, the + may be in the same parameter (i.e. no
  483. * whitespace) or with whitespace separating it
  484. *
  485. * ASCII mode on read == read and stop at first EOF
  486. * ASCII mode on write == append EOF to destination
  487. * Binary == copy as-is
  488. *
  489. * Design of this is to build up a list of files which will be copied into a
  490. * list, then work through the list file by file.
  491. * If no destination is specified, it defaults to the name of the first file in
  492. * the list, but the current directory.
  493. *
  494. */
  495. void WCMD_copy(WCHAR * args) {
  496. BOOL opt_d, opt_v, opt_n, opt_z, opt_y, opt_noty;
  497. WCHAR *thisparam;
  498. int argno = 0;
  499. WCHAR *rawarg;
  500. WIN32_FIND_DATAW fd;
  501. HANDLE hff = INVALID_HANDLE_VALUE;
  502. int binarymode = -1; /* -1 means use the default, 1 is binary, 0 ascii */
  503. BOOL concatnextfilename = FALSE; /* True if we have just processed a + */
  504. BOOL anyconcats = FALSE; /* Have we found any + options */
  505. BOOL appendfirstsource = FALSE; /* Use first found filename as destination */
  506. BOOL writtenoneconcat = FALSE; /* Remember when the first concatenated file done */
  507. BOOL prompt; /* Prompt before overwriting */
  508. WCHAR destname[MAX_PATH]; /* Used in calculating the destination name */
  509. BOOL destisdirectory = FALSE; /* Is the destination a directory? */
  510. BOOL status;
  511. WCHAR copycmd[4];
  512. DWORD len;
  513. BOOL dstisdevice = FALSE;
  514. typedef struct _COPY_FILES
  515. {
  516. struct _COPY_FILES *next;
  517. BOOL concatenate;
  518. WCHAR *name;
  519. int binarycopy;
  520. } COPY_FILES;
  521. COPY_FILES *sourcelist = NULL;
  522. COPY_FILES *lastcopyentry = NULL;
  523. COPY_FILES *destination = NULL;
  524. COPY_FILES *thiscopy = NULL;
  525. COPY_FILES *prevcopy = NULL;
  526. /* Assume we were successful! */
  527. errorlevel = 0;
  528. /* If no args supplied at all, report an error */
  529. if (param1[0] == 0x00) {
  530. WCMD_output_stderr (WCMD_LoadMessage(WCMD_NOARG));
  531. errorlevel = 1;
  532. return;
  533. }
  534. opt_d = opt_v = opt_n = opt_z = opt_y = opt_noty = FALSE;
  535. /* Walk through all args, building up a list of files to process */
  536. thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
  537. while (*(thisparam)) {
  538. WCHAR *pos1, *pos2;
  539. BOOL inquotes;
  540. WINE_TRACE("Working on parameter '%s'\n", wine_dbgstr_w(thisparam));
  541. /* Handle switches */
  542. if (*thisparam == '/') {
  543. while (*thisparam == '/') {
  544. thisparam++;
  545. if (towupper(*thisparam) == 'D') {
  546. opt_d = TRUE;
  547. if (opt_d) WINE_FIXME("copy /D support not implemented yet\n");
  548. } else if (towupper(*thisparam) == 'Y') {
  549. opt_y = TRUE;
  550. } else if (towupper(*thisparam) == '-' && towupper(*(thisparam+1)) == 'Y') {
  551. opt_noty = TRUE;
  552. } else if (towupper(*thisparam) == 'V') {
  553. opt_v = TRUE;
  554. if (opt_v) WINE_FIXME("copy /V support not implemented yet\n");
  555. } else if (towupper(*thisparam) == 'N') {
  556. opt_n = TRUE;
  557. if (opt_n) WINE_FIXME("copy /N support not implemented yet\n");
  558. } else if (towupper(*thisparam) == 'Z') {
  559. opt_z = TRUE;
  560. if (opt_z) WINE_FIXME("copy /Z support not implemented yet\n");
  561. } else if (towupper(*thisparam) == 'A') {
  562. if (binarymode != 0) {
  563. binarymode = 0;
  564. WINE_TRACE("Subsequent files will be handled as ASCII\n");
  565. if (destination != NULL) {
  566. WINE_TRACE("file %s will be written as ASCII\n", wine_dbgstr_w(destination->name));
  567. destination->binarycopy = binarymode;
  568. } else if (lastcopyentry != NULL) {
  569. WINE_TRACE("file %s will be read as ASCII\n", wine_dbgstr_w(lastcopyentry->name));
  570. lastcopyentry->binarycopy = binarymode;
  571. }
  572. }
  573. } else if (towupper(*thisparam) == 'B') {
  574. if (binarymode != 1) {
  575. binarymode = 1;
  576. WINE_TRACE("Subsequent files will be handled as binary\n");
  577. if (destination != NULL) {
  578. WINE_TRACE("file %s will be written as binary\n", wine_dbgstr_w(destination->name));
  579. destination->binarycopy = binarymode;
  580. } else if (lastcopyentry != NULL) {
  581. WINE_TRACE("file %s will be read as binary\n", wine_dbgstr_w(lastcopyentry->name));
  582. lastcopyentry->binarycopy = binarymode;
  583. }
  584. }
  585. } else {
  586. WINE_FIXME("Unexpected copy switch %s\n", wine_dbgstr_w(thisparam));
  587. }
  588. thisparam++;
  589. }
  590. /* This parameter was purely switches, get the next one */
  591. thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
  592. continue;
  593. }
  594. /* We have found something which is not a switch. If could be anything of the form
  595. sourcefilename (which could be destination too)
  596. + (when filename + filename syntex used)
  597. sourcefilename+sourcefilename
  598. +sourcefilename
  599. +/b[tests show windows then ignores to end of parameter]
  600. */
  601. if (*thisparam=='+') {
  602. if (lastcopyentry == NULL) {
  603. WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
  604. errorlevel = 1;
  605. goto exitreturn;
  606. } else {
  607. concatnextfilename = TRUE;
  608. anyconcats = TRUE;
  609. }
  610. /* Move to next thing to process */
  611. thisparam++;
  612. if (*thisparam == 0x00)
  613. thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
  614. continue;
  615. }
  616. /* We have found something to process - build a COPY_FILE block to store it */
  617. thiscopy = heap_xalloc(sizeof(COPY_FILES));
  618. WINE_TRACE("Not a switch, but probably a filename/list %s\n", wine_dbgstr_w(thisparam));
  619. thiscopy->concatenate = concatnextfilename;
  620. thiscopy->binarycopy = binarymode;
  621. thiscopy->next = NULL;
  622. /* Time to work out the name. Allocate at least enough space (deliberately too much to
  623. leave space to append \* to the end) , then copy in character by character. Strip off
  624. quotes if we find them. */
  625. len = lstrlenW(thisparam) + (sizeof(WCHAR) * 5); /* 5 spare characters, null + \*.* */
  626. thiscopy->name = heap_xalloc(len*sizeof(WCHAR));
  627. memset(thiscopy->name, 0x00, len);
  628. pos1 = thisparam;
  629. pos2 = thiscopy->name;
  630. inquotes = FALSE;
  631. while (*pos1 && (inquotes || (*pos1 != '+' && *pos1 != '/'))) {
  632. if (*pos1 == '"') {
  633. inquotes = !inquotes;
  634. pos1++;
  635. } else *pos2++ = *pos1++;
  636. }
  637. *pos2 = 0;
  638. WINE_TRACE("Calculated file name %s\n", wine_dbgstr_w(thiscopy->name));
  639. /* This is either the first source, concatenated subsequent source or destination */
  640. if (sourcelist == NULL) {
  641. WINE_TRACE("Adding as first source part\n");
  642. sourcelist = thiscopy;
  643. lastcopyentry = thiscopy;
  644. } else if (concatnextfilename) {
  645. WINE_TRACE("Adding to source file list to be concatenated\n");
  646. lastcopyentry->next = thiscopy;
  647. lastcopyentry = thiscopy;
  648. } else if (destination == NULL) {
  649. destination = thiscopy;
  650. } else {
  651. /* We have processed sources and destinations and still found more to do - invalid */
  652. WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
  653. errorlevel = 1;
  654. goto exitreturn;
  655. }
  656. concatnextfilename = FALSE;
  657. /* We either need to process the rest of the parameter or move to the next */
  658. if (*pos1 == '/' || *pos1 == '+') {
  659. thisparam = pos1;
  660. continue;
  661. } else {
  662. thisparam = WCMD_parameter(args, argno++, &rawarg, TRUE, FALSE);
  663. }
  664. }
  665. /* Ensure we have at least one source file */
  666. if (!sourcelist) {
  667. WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
  668. errorlevel = 1;
  669. goto exitreturn;
  670. }
  671. /* Default whether automatic overwriting is on. If we are interactive then
  672. we prompt by default, otherwise we overwrite by default
  673. /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
  674. if (opt_noty) prompt = TRUE;
  675. else if (opt_y) prompt = FALSE;
  676. else {
  677. /* By default, we will force the overwrite in batch mode and ask for
  678. * confirmation in interactive mode. */
  679. prompt = interactive;
  680. /* If COPYCMD is set, then we force the overwrite with /Y and ask for
  681. * confirmation with /-Y. If COPYCMD is neither of those, then we use the
  682. * default behavior. */
  683. len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
  684. if (len && len < ARRAY_SIZE(copycmd)) {
  685. if (!lstrcmpiW(copycmd, L"/Y"))
  686. prompt = FALSE;
  687. else if (!lstrcmpiW(copycmd, L"/-Y"))
  688. prompt = TRUE;
  689. }
  690. }
  691. /* Calculate the destination now - if none supplied, it's current dir +
  692. filename of first file in list*/
  693. if (destination == NULL) {
  694. WINE_TRACE("No destination supplied, so need to calculate it\n");
  695. lstrcpyW(destname, L".");
  696. lstrcatW(destname, L"\\");
  697. destination = heap_xalloc(sizeof(COPY_FILES));
  698. if (destination == NULL) goto exitreturn;
  699. destination->concatenate = FALSE; /* Not used for destination */
  700. destination->binarycopy = binarymode;
  701. destination->next = NULL; /* Not used for destination */
  702. destination->name = NULL; /* To be filled in */
  703. destisdirectory = TRUE;
  704. } else {
  705. WCHAR *filenamepart;
  706. DWORD attributes;
  707. WINE_TRACE("Destination supplied, processing to see if file or directory\n");
  708. /* Convert to fully qualified path/filename */
  709. GetFullPathNameW(destination->name, ARRAY_SIZE(destname), destname, &filenamepart);
  710. WINE_TRACE("Full dest name is '%s'\n", wine_dbgstr_w(destname));
  711. /* If parameter is a directory, ensure it ends in \ */
  712. attributes = GetFileAttributesW(destname);
  713. if (ends_with_backslash( destname ) ||
  714. ((attributes != INVALID_FILE_ATTRIBUTES) &&
  715. (attributes & FILE_ATTRIBUTE_DIRECTORY))) {
  716. destisdirectory = TRUE;
  717. if (!ends_with_backslash(destname)) lstrcatW(destname, L"\\");
  718. WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(destname));
  719. }
  720. }
  721. /* Normally, the destination is the current directory unless we are
  722. concatenating, in which case it's current directory plus first filename.
  723. Note that if the
  724. In addition by default it is a binary copy unless concatenating, when
  725. the copy defaults to an ascii copy (stop at EOF). We do not know the
  726. first source part yet (until we search) so flag as needing filling in. */
  727. if (anyconcats) {
  728. /* We have found an a+b type syntax, so destination has to be a filename
  729. and we need to default to ascii copying. If we have been supplied a
  730. directory as the destination, we need to defer calculating the name */
  731. if (destisdirectory) appendfirstsource = TRUE;
  732. if (destination->binarycopy == -1) destination->binarycopy = 0;
  733. } else if (!destisdirectory) {
  734. /* We have been asked to copy to a filename. Default to ascii IF the
  735. source contains wildcards (true even if only one match) */
  736. if (wcspbrk(sourcelist->name, L"*?") != NULL) {
  737. anyconcats = TRUE; /* We really are concatenating to a single file */
  738. if (destination->binarycopy == -1) {
  739. destination->binarycopy = 0;
  740. }
  741. } else {
  742. if (destination->binarycopy == -1) {
  743. destination->binarycopy = 1;
  744. }
  745. }
  746. }
  747. /* Save away the destination name*/
  748. heap_free(destination->name);
  749. destination->name = heap_strdupW(destname);
  750. WINE_TRACE("Resolved destination is '%s' (calc later %d)\n",
  751. wine_dbgstr_w(destname), appendfirstsource);
  752. /* Remember if the destination is a device */
  753. if (wcsncmp(destination->name, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
  754. WINE_TRACE("Destination is a device\n");
  755. dstisdevice = TRUE;
  756. }
  757. /* Now we need to walk the set of sources, and process each name we come to.
  758. If anyconcats is true, we are writing to one file, otherwise we are using
  759. the source name each time.
  760. If destination exists, prompt for overwrite the first time (if concatenating
  761. we ask each time until yes is answered)
  762. The first source file we come across must exist (when wildcards expanded)
  763. and if concatenating with overwrite prompts, each source file must exist
  764. until a yes is answered. */
  765. thiscopy = sourcelist;
  766. prevcopy = NULL;
  767. while (thiscopy != NULL) {
  768. WCHAR srcpath[MAX_PATH];
  769. const WCHAR *srcname;
  770. WCHAR *filenamepart;
  771. DWORD attributes;
  772. BOOL srcisdevice = FALSE;
  773. /* If it was not explicit, we now know whether we are concatenating or not and
  774. hence whether to copy as binary or ascii */
  775. if (thiscopy->binarycopy == -1) thiscopy->binarycopy = !anyconcats;
  776. /* Convert to fully qualified path/filename in srcpath, file filenamepart pointing
  777. to where the filename portion begins (used for wildcard expansion). */
  778. GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
  779. WINE_TRACE("Full src name is '%s'\n", wine_dbgstr_w(srcpath));
  780. /* If parameter is a directory, ensure it ends in \* */
  781. attributes = GetFileAttributesW(srcpath);
  782. if (ends_with_backslash( srcpath )) {
  783. /* We need to know where the filename part starts, so append * and
  784. recalculate the full resulting path */
  785. lstrcatW(thiscopy->name, L"*");
  786. GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
  787. WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
  788. } else if ((wcspbrk(srcpath, L"*?") == NULL) &&
  789. (attributes != INVALID_FILE_ATTRIBUTES) &&
  790. (attributes & FILE_ATTRIBUTE_DIRECTORY)) {
  791. /* We need to know where the filename part starts, so append \* and
  792. recalculate the full resulting path */
  793. lstrcatW(thiscopy->name, L"\\*");
  794. GetFullPathNameW(thiscopy->name, ARRAY_SIZE(srcpath), srcpath, &filenamepart);
  795. WINE_TRACE("Directory, so full name is now '%s'\n", wine_dbgstr_w(srcpath));
  796. }
  797. WINE_TRACE("Copy source (calculated): path: '%s' (Concats: %d)\n",
  798. wine_dbgstr_w(srcpath), anyconcats);
  799. /* If the source is a device, just use it, otherwise search */
  800. if (wcsncmp(srcpath, L"\\\\.\\", lstrlenW(L"\\\\.\\")) == 0) {
  801. WINE_TRACE("Source is a device\n");
  802. srcisdevice = TRUE;
  803. srcname = &srcpath[4]; /* After the \\.\ prefix */
  804. } else {
  805. /* Loop through all source files */
  806. WINE_TRACE("Searching for: '%s'\n", wine_dbgstr_w(srcpath));
  807. hff = FindFirstFileW(srcpath, &fd);
  808. if (hff != INVALID_HANDLE_VALUE) {
  809. srcname = fd.cFileName;
  810. }
  811. }
  812. if (srcisdevice || hff != INVALID_HANDLE_VALUE) {
  813. do {
  814. WCHAR outname[MAX_PATH];
  815. BOOL overwrite;
  816. BOOL appendtofirstfile = FALSE;
  817. /* Skip . and .., and directories */
  818. if (!srcisdevice && fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  819. WINE_TRACE("Skipping directories\n");
  820. } else {
  821. /* Build final destination name */
  822. lstrcpyW(outname, destination->name);
  823. if (destisdirectory || appendfirstsource) lstrcatW(outname, srcname);
  824. /* Build source name */
  825. if (!srcisdevice) lstrcpyW(filenamepart, srcname);
  826. /* Do we just overwrite (we do if we are writing to a device) */
  827. overwrite = !prompt;
  828. if (dstisdevice || (anyconcats && writtenoneconcat)) {
  829. overwrite = TRUE;
  830. }
  831. WINE_TRACE("Copying from : '%s'\n", wine_dbgstr_w(srcpath));
  832. WINE_TRACE("Copying to : '%s'\n", wine_dbgstr_w(outname));
  833. WINE_TRACE("Flags: srcbinary(%d), dstbinary(%d), over(%d), prompt(%d)\n",
  834. thiscopy->binarycopy, destination->binarycopy, overwrite, prompt);
  835. if (!writtenoneconcat) {
  836. appendtofirstfile = anyconcats && WCMD_IsSameFile(srcpath, outname);
  837. }
  838. /* Prompt before overwriting */
  839. if (appendtofirstfile) {
  840. overwrite = TRUE;
  841. } else if (!overwrite) {
  842. DWORD attributes = GetFileAttributesW(outname);
  843. if (attributes != INVALID_FILE_ATTRIBUTES) {
  844. WCHAR* question;
  845. question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), outname);
  846. overwrite = WCMD_ask_confirm(question, FALSE, NULL);
  847. LocalFree(question);
  848. }
  849. else overwrite = TRUE;
  850. }
  851. /* If we needed to save away the first filename, do it */
  852. if (appendfirstsource && overwrite) {
  853. heap_free(destination->name);
  854. destination->name = heap_strdupW(outname);
  855. WINE_TRACE("Final resolved destination name : '%s'\n", wine_dbgstr_w(outname));
  856. appendfirstsource = FALSE;
  857. destisdirectory = FALSE;
  858. }
  859. /* Do the copy as appropriate */
  860. if (overwrite) {
  861. if (anyconcats && WCMD_IsSameFile(srcpath, outname)) {
  862. /* Silently skip if the destination file is also a source file */
  863. status = TRUE;
  864. } else if (anyconcats && writtenoneconcat) {
  865. if (thiscopy->binarycopy) {
  866. status = WCMD_ManualCopy(srcpath, outname, FALSE, TRUE);
  867. } else {
  868. status = WCMD_ManualCopy(srcpath, outname, TRUE, TRUE);
  869. }
  870. } else if (!thiscopy->binarycopy) {
  871. status = WCMD_ManualCopy(srcpath, outname, TRUE, FALSE);
  872. } else if (srcisdevice) {
  873. status = WCMD_ManualCopy(srcpath, outname, FALSE, FALSE);
  874. } else {
  875. status = CopyFileW(srcpath, outname, FALSE);
  876. }
  877. if (!status) {
  878. WCMD_print_error ();
  879. errorlevel = 1;
  880. } else {
  881. WINE_TRACE("Copied successfully\n");
  882. if (anyconcats) writtenoneconcat = TRUE;
  883. /* Append EOF if ascii destination and we are not going to add more onto the end
  884. Note: Testing shows windows has an optimization whereas if you have a binary
  885. copy of a file to a single destination (ie concatenation) then it does not add
  886. the EOF, hence the check on the source copy type below. */
  887. if (!destination->binarycopy && !anyconcats && !thiscopy->binarycopy) {
  888. if (!WCMD_AppendEOF(outname)) {
  889. WCMD_print_error ();
  890. errorlevel = 1;
  891. }
  892. }
  893. }
  894. }
  895. }
  896. } while (!srcisdevice && FindNextFileW(hff, &fd) != 0);
  897. if (!srcisdevice) FindClose (hff);
  898. } else {
  899. /* Error if the first file was not found */
  900. if (!anyconcats || !writtenoneconcat) {
  901. WCMD_print_error ();
  902. errorlevel = 1;
  903. }
  904. }
  905. /* Step on to the next supplied source */
  906. thiscopy = thiscopy -> next;
  907. }
  908. /* Append EOF if ascii destination and we were concatenating */
  909. if (!errorlevel && !destination->binarycopy && anyconcats && writtenoneconcat) {
  910. if (!WCMD_AppendEOF(destination->name)) {
  911. WCMD_print_error ();
  912. errorlevel = 1;
  913. }
  914. }
  915. /* Exit out of the routine, freeing any remaining allocated memory */
  916. exitreturn:
  917. thiscopy = sourcelist;
  918. while (thiscopy != NULL) {
  919. prevcopy = thiscopy;
  920. /* Free up this block*/
  921. thiscopy = thiscopy -> next;
  922. heap_free(prevcopy->name);
  923. heap_free(prevcopy);
  924. }
  925. /* Free up the destination memory */
  926. if (destination) {
  927. heap_free(destination->name);
  928. heap_free(destination);
  929. }
  930. return;
  931. }
  932. /****************************************************************************
  933. * WCMD_create_dir
  934. *
  935. * Create a directory (and, if needed, any intermediate directories).
  936. *
  937. * Modifies its argument by replacing slashes temporarily with nulls.
  938. */
  939. static BOOL create_full_path(WCHAR* path)
  940. {
  941. WCHAR *p, *start;
  942. /* don't mess with drive letter portion of path, if any */
  943. start = path;
  944. if (path[1] == ':')
  945. start = path+2;
  946. /* Strip trailing slashes. */
  947. for (p = path + lstrlenW(path) - 1; p != start && *p == '\\'; p--)
  948. *p = 0;
  949. /* Step through path, creating intermediate directories as needed. */
  950. /* First component includes drive letter, if any. */
  951. p = start;
  952. for (;;) {
  953. DWORD rv;
  954. /* Skip to end of component */
  955. while (*p == '\\') p++;
  956. while (*p && *p != '\\') p++;
  957. if (!*p) {
  958. /* path is now the original full path */
  959. return CreateDirectoryW(path, NULL);
  960. }
  961. /* Truncate path, create intermediate directory, and restore path */
  962. *p = 0;
  963. rv = CreateDirectoryW(path, NULL);
  964. *p = '\\';
  965. if (!rv && GetLastError() != ERROR_ALREADY_EXISTS)
  966. return FALSE;
  967. }
  968. /* notreached */
  969. return FALSE;
  970. }
  971. void WCMD_create_dir (WCHAR *args) {
  972. int argno = 0;
  973. WCHAR *argN = args;
  974. if (param1[0] == 0x00) {
  975. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  976. return;
  977. }
  978. /* Loop through all args */
  979. while (TRUE) {
  980. WCHAR *thisArg = WCMD_parameter(args, argno++, &argN, FALSE, FALSE);
  981. if (!argN) break;
  982. if (!create_full_path(thisArg)) {
  983. WCMD_print_error ();
  984. errorlevel = 1;
  985. }
  986. }
  987. }
  988. /* Parse the /A options given by the user on the commandline
  989. * into a bitmask of wanted attributes (*wantSet),
  990. * and a bitmask of unwanted attributes (*wantClear).
  991. */
  992. static void WCMD_delete_parse_attributes(DWORD *wantSet, DWORD *wantClear) {
  993. WCHAR *p;
  994. /* both are strictly 'out' parameters */
  995. *wantSet=0;
  996. *wantClear=0;
  997. /* For each /A argument */
  998. for (p=wcsstr(quals, L"/A"); p != NULL; p=wcsstr(p, L"/A")) {
  999. /* Skip /A itself */
  1000. p += 2;
  1001. /* Skip optional : */
  1002. if (*p == ':') p++;
  1003. /* For each of the attribute specifier chars to this /A option */
  1004. for (; *p != 0 && *p != '/'; p++) {
  1005. BOOL negate = FALSE;
  1006. DWORD mask = 0;
  1007. if (*p == '-') {
  1008. negate=TRUE;
  1009. p++;
  1010. }
  1011. /* Convert the attribute specifier to a bit in one of the masks */
  1012. switch (*p) {
  1013. case 'R': mask = FILE_ATTRIBUTE_READONLY; break;
  1014. case 'H': mask = FILE_ATTRIBUTE_HIDDEN; break;
  1015. case 'S': mask = FILE_ATTRIBUTE_SYSTEM; break;
  1016. case 'A': mask = FILE_ATTRIBUTE_ARCHIVE; break;
  1017. default:
  1018. WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
  1019. }
  1020. if (negate)
  1021. *wantClear |= mask;
  1022. else
  1023. *wantSet |= mask;
  1024. }
  1025. }
  1026. }
  1027. /* If filename part of parameter is * or *.*,
  1028. * and neither /Q nor /P options were given,
  1029. * prompt the user whether to proceed.
  1030. * Returns FALSE if user says no, TRUE otherwise.
  1031. * *pPrompted is set to TRUE if the user is prompted.
  1032. * (If /P supplied, del will prompt for individual files later.)
  1033. */
  1034. static BOOL WCMD_delete_confirm_wildcard(const WCHAR *filename, BOOL *pPrompted) {
  1035. if ((wcsstr(quals, L"/Q") == NULL) && (wcsstr(quals, L"/P") == NULL)) {
  1036. WCHAR drive[10];
  1037. WCHAR dir[MAX_PATH];
  1038. WCHAR fname[MAX_PATH];
  1039. WCHAR ext[MAX_PATH];
  1040. WCHAR fpath[MAX_PATH];
  1041. /* Convert path into actual directory spec */
  1042. GetFullPathNameW(filename, ARRAY_SIZE(fpath), fpath, NULL);
  1043. _wsplitpath(fpath, drive, dir, fname, ext);
  1044. /* Only prompt for * and *.*, not *a, a*, *.a* etc */
  1045. if ((lstrcmpW(fname, L"*") == 0) && (*ext == 0x00 || (lstrcmpW(ext, L".*") == 0))) {
  1046. WCHAR question[MAXSTRING];
  1047. /* Caller uses this to suppress "file not found" warning later */
  1048. *pPrompted = TRUE;
  1049. /* Ask for confirmation */
  1050. wsprintfW(question, L"%s ", fpath);
  1051. return WCMD_ask_confirm(question, TRUE, NULL);
  1052. }
  1053. }
  1054. /* No scary wildcard, or question suppressed, so it's ok to delete the file(s) */
  1055. return TRUE;
  1056. }
  1057. /* Helper function for WCMD_delete().
  1058. * Deletes a single file, directory, or wildcard.
  1059. * If /S was given, does it recursively.
  1060. * Returns TRUE if a file was deleted.
  1061. */
  1062. static BOOL WCMD_delete_one (const WCHAR *thisArg) {
  1063. DWORD wanted_attrs;
  1064. DWORD unwanted_attrs;
  1065. BOOL found = FALSE;
  1066. WCHAR argCopy[MAX_PATH];
  1067. WIN32_FIND_DATAW fd;
  1068. HANDLE hff;
  1069. WCHAR fpath[MAX_PATH];
  1070. WCHAR *p;
  1071. BOOL handleParm = TRUE;
  1072. WCMD_delete_parse_attributes(&wanted_attrs, &unwanted_attrs);
  1073. lstrcpyW(argCopy, thisArg);
  1074. WINE_TRACE("del: Processing arg %s (quals:%s)\n",
  1075. wine_dbgstr_w(argCopy), wine_dbgstr_w(quals));
  1076. if (!WCMD_delete_confirm_wildcard(argCopy, &found)) {
  1077. /* Skip this arg if user declines to delete *.* */
  1078. return FALSE;
  1079. }
  1080. /* First, try to delete in the current directory */
  1081. hff = FindFirstFileW(argCopy, &fd);
  1082. if (hff == INVALID_HANDLE_VALUE) {
  1083. handleParm = FALSE;
  1084. } else {
  1085. found = TRUE;
  1086. }
  1087. /* Support del <dirname> by just deleting all files dirname\* */
  1088. if (handleParm
  1089. && (wcschr(argCopy,'*') == NULL)
  1090. && (wcschr(argCopy,'?') == NULL)
  1091. && (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY))
  1092. {
  1093. WCHAR modifiedParm[MAX_PATH];
  1094. lstrcpyW(modifiedParm, argCopy);
  1095. lstrcatW(modifiedParm, L"\\*");
  1096. FindClose(hff);
  1097. found = TRUE;
  1098. WCMD_delete_one(modifiedParm);
  1099. } else if (handleParm) {
  1100. /* Build the filename to delete as <supplied directory>\<findfirst filename> */
  1101. lstrcpyW (fpath, argCopy);
  1102. do {
  1103. p = wcsrchr (fpath, '\\');
  1104. if (p != NULL) {
  1105. *++p = '\0';
  1106. lstrcatW (fpath, fd.cFileName);
  1107. }
  1108. else lstrcpyW (fpath, fd.cFileName);
  1109. if (!(fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) {
  1110. BOOL ok;
  1111. /* Handle attribute matching (/A) */
  1112. ok = ((fd.dwFileAttributes & wanted_attrs) == wanted_attrs)
  1113. && ((fd.dwFileAttributes & unwanted_attrs) == 0);
  1114. /* /P means prompt for each file */
  1115. if (ok && wcsstr(quals, L"/P") != NULL) {
  1116. WCHAR* question;
  1117. /* Ask for confirmation */
  1118. question = WCMD_format_string(WCMD_LoadMessage(WCMD_DELPROMPT), fpath);
  1119. ok = WCMD_ask_confirm(question, FALSE, NULL);
  1120. LocalFree(question);
  1121. }
  1122. /* Only proceed if ok to */
  1123. if (ok) {
  1124. /* If file is read only, and /A:r or /F supplied, delete it */
  1125. if (fd.dwFileAttributes & FILE_ATTRIBUTE_READONLY &&
  1126. ((wanted_attrs & FILE_ATTRIBUTE_READONLY) ||
  1127. wcsstr(quals, L"/F") != NULL)) {
  1128. SetFileAttributesW(fpath, fd.dwFileAttributes & ~FILE_ATTRIBUTE_READONLY);
  1129. }
  1130. /* Now do the delete */
  1131. if (!DeleteFileW(fpath)) WCMD_print_error ();
  1132. }
  1133. }
  1134. } while (FindNextFileW(hff, &fd) != 0);
  1135. FindClose (hff);
  1136. }
  1137. /* Now recurse into all subdirectories handling the parameter in the same way */
  1138. if (wcsstr(quals, L"/S") != NULL) {
  1139. WCHAR thisDir[MAX_PATH];
  1140. int cPos;
  1141. WCHAR drive[10];
  1142. WCHAR dir[MAX_PATH];
  1143. WCHAR fname[MAX_PATH];
  1144. WCHAR ext[MAX_PATH];
  1145. /* Convert path into actual directory spec */
  1146. GetFullPathNameW(argCopy, ARRAY_SIZE(thisDir), thisDir, NULL);
  1147. _wsplitpath(thisDir, drive, dir, fname, ext);
  1148. lstrcpyW(thisDir, drive);
  1149. lstrcatW(thisDir, dir);
  1150. cPos = lstrlenW(thisDir);
  1151. WINE_TRACE("Searching recursively in '%s'\n", wine_dbgstr_w(thisDir));
  1152. /* Append '*' to the directory */
  1153. thisDir[cPos] = '*';
  1154. thisDir[cPos+1] = 0x00;
  1155. hff = FindFirstFileW(thisDir, &fd);
  1156. /* Remove residual '*' */
  1157. thisDir[cPos] = 0x00;
  1158. if (hff != INVALID_HANDLE_VALUE) {
  1159. DIRECTORY_STACK *allDirs = NULL;
  1160. DIRECTORY_STACK *lastEntry = NULL;
  1161. do {
  1162. if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  1163. (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0)) {
  1164. DIRECTORY_STACK *nextDir;
  1165. WCHAR subParm[MAX_PATH];
  1166. /* Work out search parameter in sub dir */
  1167. lstrcpyW (subParm, thisDir);
  1168. lstrcatW (subParm, fd.cFileName);
  1169. lstrcatW (subParm, L"\\");
  1170. lstrcatW (subParm, fname);
  1171. lstrcatW (subParm, ext);
  1172. WINE_TRACE("Recursive, Adding to search list '%s'\n", wine_dbgstr_w(subParm));
  1173. /* Allocate memory, add to list */
  1174. nextDir = heap_xalloc(sizeof(DIRECTORY_STACK));
  1175. if (allDirs == NULL) allDirs = nextDir;
  1176. if (lastEntry != NULL) lastEntry->next = nextDir;
  1177. lastEntry = nextDir;
  1178. nextDir->next = NULL;
  1179. nextDir->dirName = heap_strdupW(subParm);
  1180. }
  1181. } while (FindNextFileW(hff, &fd) != 0);
  1182. FindClose (hff);
  1183. /* Go through each subdir doing the delete */
  1184. while (allDirs != NULL) {
  1185. DIRECTORY_STACK *tempDir;
  1186. tempDir = allDirs->next;
  1187. found |= WCMD_delete_one (allDirs->dirName);
  1188. heap_free(allDirs->dirName);
  1189. heap_free(allDirs);
  1190. allDirs = tempDir;
  1191. }
  1192. }
  1193. }
  1194. return found;
  1195. }
  1196. /****************************************************************************
  1197. * WCMD_delete
  1198. *
  1199. * Delete a file or wildcarded set.
  1200. *
  1201. * Note on /A:
  1202. * - Testing shows /A is repeatable, eg. /a-r /ar matches all files
  1203. * - Each set is a pattern, eg /ahr /as-r means
  1204. * readonly+hidden OR nonreadonly system files
  1205. * - The '-' applies to a single field, ie /a:-hr means read only
  1206. * non-hidden files
  1207. */
  1208. BOOL WCMD_delete (WCHAR *args) {
  1209. int argno;
  1210. WCHAR *argN;
  1211. BOOL argsProcessed = FALSE;
  1212. BOOL foundAny = FALSE;
  1213. errorlevel = 0;
  1214. for (argno=0; ; argno++) {
  1215. BOOL found;
  1216. WCHAR *thisArg;
  1217. argN = NULL;
  1218. thisArg = WCMD_parameter (args, argno, &argN, FALSE, FALSE);
  1219. if (!argN)
  1220. break; /* no more parameters */
  1221. if (argN[0] == '/')
  1222. continue; /* skip options */
  1223. argsProcessed = TRUE;
  1224. found = WCMD_delete_one(thisArg);
  1225. if (!found)
  1226. WCMD_output_stderr(WCMD_LoadMessage(WCMD_FILENOTFOUND), thisArg);
  1227. foundAny |= found;
  1228. }
  1229. /* Handle no valid args */
  1230. if (!argsProcessed)
  1231. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  1232. return foundAny;
  1233. }
  1234. /*
  1235. * WCMD_strtrim
  1236. *
  1237. * Returns a trimmed version of s with all leading and trailing whitespace removed
  1238. * Pre: s non NULL
  1239. *
  1240. */
  1241. static WCHAR *WCMD_strtrim(const WCHAR *s)
  1242. {
  1243. DWORD len = lstrlenW(s);
  1244. const WCHAR *start = s;
  1245. WCHAR* result;
  1246. result = heap_xalloc((len + 1) * sizeof(WCHAR));
  1247. while (iswspace(*start)) start++;
  1248. if (*start) {
  1249. const WCHAR *end = s + len - 1;
  1250. while (end > start && iswspace(*end)) end--;
  1251. memcpy(result, start, (end - start + 2) * sizeof(WCHAR));
  1252. result[end - start + 1] = '\0';
  1253. } else {
  1254. result[0] = '\0';
  1255. }
  1256. return result;
  1257. }
  1258. /****************************************************************************
  1259. * WCMD_echo
  1260. *
  1261. * Echo input to the screen (or not). We don't try to emulate the bugs
  1262. * in DOS (try typing "ECHO ON AGAIN" for an example).
  1263. */
  1264. void WCMD_echo (const WCHAR *args)
  1265. {
  1266. int count;
  1267. const WCHAR *origcommand = args;
  1268. WCHAR *trimmed;
  1269. if ( args[0]==' ' || args[0]=='\t' || args[0]=='.'
  1270. || args[0]==':' || args[0]==';' || args[0]=='/')
  1271. args++;
  1272. trimmed = WCMD_strtrim(args);
  1273. if (!trimmed) return;
  1274. count = lstrlenW(trimmed);
  1275. if (count == 0 && origcommand[0]!='.' && origcommand[0]!=':'
  1276. && origcommand[0]!=';' && origcommand[0]!='/') {
  1277. if (echo_mode) WCMD_output(WCMD_LoadMessage(WCMD_ECHOPROMPT), L"ON");
  1278. else WCMD_output (WCMD_LoadMessage(WCMD_ECHOPROMPT), L"OFF");
  1279. heap_free(trimmed);
  1280. return;
  1281. }
  1282. if (lstrcmpiW(trimmed, L"ON") == 0)
  1283. echo_mode = TRUE;
  1284. else if (lstrcmpiW(trimmed, L"OFF") == 0)
  1285. echo_mode = FALSE;
  1286. else {
  1287. WCMD_output_asis (args);
  1288. WCMD_output_asis(L"\r\n");
  1289. }
  1290. heap_free(trimmed);
  1291. }
  1292. /*****************************************************************************
  1293. * WCMD_part_execute
  1294. *
  1295. * Execute a command, and any && or bracketed follow on to the command. The
  1296. * first command to be executed may not be at the front of the
  1297. * commands->thiscommand string (eg. it may point after a DO or ELSE)
  1298. */
  1299. static void WCMD_part_execute(CMD_LIST **cmdList, const WCHAR *firstcmd,
  1300. BOOL isIF, BOOL executecmds)
  1301. {
  1302. CMD_LIST *curPosition = *cmdList;
  1303. int myDepth = (*cmdList)->bracketDepth;
  1304. WINE_TRACE("cmdList(%p), firstCmd(%s), doIt(%d), isIF(%d)\n", cmdList,
  1305. wine_dbgstr_w(firstcmd), executecmds, isIF);
  1306. /* Skip leading whitespace between condition and the command */
  1307. while (firstcmd && *firstcmd && (*firstcmd==' ' || *firstcmd=='\t')) firstcmd++;
  1308. /* Process the first command, if there is one */
  1309. if (executecmds && firstcmd && *firstcmd) {
  1310. WCHAR *command = heap_strdupW(firstcmd);
  1311. WCMD_execute (firstcmd, (*cmdList)->redirects, cmdList, FALSE);
  1312. heap_free(command);
  1313. }
  1314. /* If it didn't move the position, step to next command */
  1315. if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
  1316. /* Process any other parts of the command */
  1317. if (*cmdList) {
  1318. BOOL processThese = executecmds;
  1319. while (*cmdList) {
  1320. /* execute all appropriate commands */
  1321. curPosition = *cmdList;
  1322. WINE_TRACE("Processing cmdList(%p) - delim(%d) bd(%d / %d) processThese(%d)\n",
  1323. *cmdList,
  1324. (*cmdList)->prevDelim,
  1325. (*cmdList)->bracketDepth,
  1326. myDepth,
  1327. processThese);
  1328. /* Execute any statements appended to the line */
  1329. /* FIXME: Only if previous call worked for && or failed for || */
  1330. if ((*cmdList)->prevDelim == CMD_ONFAILURE ||
  1331. (*cmdList)->prevDelim == CMD_ONSUCCESS) {
  1332. if (processThese && (*cmdList)->command) {
  1333. WCMD_execute ((*cmdList)->command, (*cmdList)->redirects,
  1334. cmdList, FALSE);
  1335. }
  1336. if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
  1337. /* Execute any appended to the statement with (...) */
  1338. } else if ((*cmdList)->bracketDepth > myDepth) {
  1339. if (processThese) {
  1340. *cmdList = WCMD_process_commands(*cmdList, TRUE, FALSE);
  1341. } else {
  1342. WINE_TRACE("Skipping command %p due to stack depth\n", *cmdList);
  1343. }
  1344. if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
  1345. /* End of the command - does 'ELSE ' follow as the next command? */
  1346. } else {
  1347. if (isIF && WCMD_keyword_ws_found(L"else", (*cmdList)->command)) {
  1348. /* Swap between if and else processing */
  1349. processThese = !executecmds;
  1350. /* Process the ELSE part */
  1351. if (processThese) {
  1352. const int keyw_len = lstrlenW(L"else") + 1;
  1353. WCHAR *cmd = ((*cmdList)->command) + keyw_len;
  1354. /* Skip leading whitespace between condition and the command */
  1355. while (*cmd && (*cmd==' ' || *cmd=='\t')) cmd++;
  1356. if (*cmd) {
  1357. WCMD_execute (cmd, (*cmdList)->redirects, cmdList, FALSE);
  1358. }
  1359. } else {
  1360. /* Loop skipping all commands until we get back to the current
  1361. depth, including skipping commands and their subsequent
  1362. pipes (eg cmd | prog) */
  1363. do {
  1364. *cmdList = (*cmdList)->nextcommand;
  1365. } while (*cmdList &&
  1366. ((*cmdList)->bracketDepth > myDepth ||
  1367. (*cmdList)->prevDelim));
  1368. /* After the else is complete, we need to now process subsequent commands */
  1369. processThese = TRUE;
  1370. }
  1371. if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
  1372. /* If we were in an IF statement and we didn't find an else and yet we get back to
  1373. the same bracket depth as the IF, then the IF statement is over. This is required
  1374. to handle nested ifs properly */
  1375. } else if (isIF && (*cmdList)->bracketDepth == myDepth) {
  1376. if (WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
  1377. WINE_TRACE("Still inside FOR-loop, not an end of IF statement\n");
  1378. *cmdList = (*cmdList)->nextcommand;
  1379. } else {
  1380. WINE_TRACE("Found end of this nested IF statement, ending this if\n");
  1381. break;
  1382. }
  1383. } else if (!processThese) {
  1384. if (curPosition == *cmdList) *cmdList = (*cmdList)->nextcommand;
  1385. WINE_TRACE("Skipping this command, as in not process mode (next = %p)\n", *cmdList);
  1386. } else {
  1387. WINE_TRACE("Found end of this IF statement (next = %p)\n", *cmdList);
  1388. break;
  1389. }
  1390. }
  1391. }
  1392. }
  1393. return;
  1394. }
  1395. /*****************************************************************************
  1396. * WCMD_parse_forf_options
  1397. *
  1398. * Parses the for /f 'options', extracting the values and validating the
  1399. * keywords. Note all keywords are optional.
  1400. * Parameters:
  1401. * options [I] The unparsed parameter string
  1402. * eol [O] Set to the comment character (eol=x)
  1403. * skip [O] Set to the number of lines to skip (skip=xx)
  1404. * delims [O] Set to the token delimiters (delims=)
  1405. * tokens [O] Set to the requested tokens, as provided (tokens=)
  1406. * usebackq [O] Set to TRUE if usebackq found
  1407. *
  1408. * Returns TRUE on success, FALSE on syntax error
  1409. *
  1410. */
  1411. static BOOL WCMD_parse_forf_options(WCHAR *options, WCHAR *eol, int *skip,
  1412. WCHAR *delims, WCHAR *tokens, BOOL *usebackq)
  1413. {
  1414. WCHAR *pos = options;
  1415. int len = lstrlenW(pos);
  1416. const int eol_len = lstrlenW(L"eol=");
  1417. const int skip_len = lstrlenW(L"skip=");
  1418. const int tokens_len = lstrlenW(L"tokens=");
  1419. const int delims_len = lstrlenW(L"delims=");
  1420. const int usebackq_len = lstrlenW(L"usebackq");
  1421. /* Initialize to defaults */
  1422. lstrcpyW(delims, L" \t");
  1423. lstrcpyW(tokens, L"1");
  1424. *eol = 0;
  1425. *skip = 0;
  1426. *usebackq = FALSE;
  1427. /* Strip (optional) leading and trailing quotes */
  1428. if ((*pos == '"') && (pos[len-1] == '"')) {
  1429. pos[len-1] = 0;
  1430. pos++;
  1431. }
  1432. /* Process each keyword */
  1433. while (pos && *pos) {
  1434. if (*pos == ' ' || *pos == '\t') {
  1435. pos++;
  1436. /* Save End of line character (Ignore line if first token (based on delims) starts with it) */
  1437. } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  1438. pos, eol_len, L"eol=", eol_len) == CSTR_EQUAL) {
  1439. *eol = *(pos + eol_len);
  1440. pos = pos + eol_len + 1;
  1441. WINE_TRACE("Found eol as %c(%x)\n", *eol, *eol);
  1442. /* Save number of lines to skip (Can be in base 10, hex (0x...) or octal (0xx) */
  1443. } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  1444. pos, skip_len, L"skip=", skip_len) == CSTR_EQUAL) {
  1445. WCHAR *nextchar = NULL;
  1446. pos = pos + skip_len;
  1447. *skip = wcstoul(pos, &nextchar, 0);
  1448. WINE_TRACE("Found skip as %d lines\n", *skip);
  1449. pos = nextchar;
  1450. /* Save if usebackq semantics are in effect */
  1451. } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT, pos,
  1452. usebackq_len, L"usebackq", usebackq_len) == CSTR_EQUAL) {
  1453. *usebackq = TRUE;
  1454. pos = pos + usebackq_len;
  1455. WINE_TRACE("Found usebackq\n");
  1456. /* Save the supplied delims. Slightly odd as space can be a delimiter but only
  1457. if you finish the optionsroot string with delims= otherwise the space is
  1458. just a token delimiter! */
  1459. } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  1460. pos, delims_len, L"delims=", delims_len) == CSTR_EQUAL) {
  1461. int i=0;
  1462. pos = pos + delims_len;
  1463. while (*pos && *pos != ' ') {
  1464. delims[i++] = *pos;
  1465. pos++;
  1466. }
  1467. if (*pos==' ' && *(pos+1)==0) delims[i++] = *pos;
  1468. delims[i++] = 0; /* Null terminate the delims */
  1469. WINE_TRACE("Found delims as '%s'\n", wine_dbgstr_w(delims));
  1470. /* Save the tokens being requested */
  1471. } else if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  1472. pos, tokens_len, L"tokens=", tokens_len) == CSTR_EQUAL) {
  1473. int i=0;
  1474. pos = pos + tokens_len;
  1475. while (*pos && *pos != ' ') {
  1476. tokens[i++] = *pos;
  1477. pos++;
  1478. }
  1479. tokens[i++] = 0; /* Null terminate the tokens */
  1480. WINE_TRACE("Found tokens as '%s'\n", wine_dbgstr_w(tokens));
  1481. } else {
  1482. WINE_WARN("Unexpected data in optionsroot: '%s'\n", wine_dbgstr_w(pos));
  1483. return FALSE;
  1484. }
  1485. }
  1486. return TRUE;
  1487. }
  1488. /*****************************************************************************
  1489. * WCMD_add_dirstowalk
  1490. *
  1491. * When recursing through directories (for /r), we need to add to the list of
  1492. * directories still to walk, any subdirectories of the one we are processing.
  1493. *
  1494. * Parameters
  1495. * options [I] The remaining list of directories still to process
  1496. *
  1497. * Note this routine inserts the subdirectories found between the entry being
  1498. * processed, and any other directory still to be processed, mimicking what
  1499. * Windows does
  1500. */
  1501. static void WCMD_add_dirstowalk(DIRECTORY_STACK *dirsToWalk) {
  1502. DIRECTORY_STACK *remainingDirs = dirsToWalk;
  1503. WCHAR fullitem[MAX_PATH];
  1504. WIN32_FIND_DATAW fd;
  1505. HANDLE hff;
  1506. /* Build a generic search and add all directories on the list of directories
  1507. still to walk */
  1508. lstrcpyW(fullitem, dirsToWalk->dirName);
  1509. lstrcatW(fullitem, L"\\*");
  1510. hff = FindFirstFileW(fullitem, &fd);
  1511. if (hff != INVALID_HANDLE_VALUE) {
  1512. do {
  1513. WINE_TRACE("Looking for subdirectories\n");
  1514. if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) &&
  1515. (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
  1516. {
  1517. /* Allocate memory, add to list */
  1518. DIRECTORY_STACK *toWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
  1519. WINE_TRACE("(%p->%p)\n", remainingDirs, remainingDirs->next);
  1520. toWalk->next = remainingDirs->next;
  1521. remainingDirs->next = toWalk;
  1522. remainingDirs = toWalk;
  1523. toWalk->dirName = heap_xalloc(sizeof(WCHAR) * (lstrlenW(dirsToWalk->dirName) + 2 + lstrlenW(fd.cFileName)));
  1524. lstrcpyW(toWalk->dirName, dirsToWalk->dirName);
  1525. lstrcatW(toWalk->dirName, L"\\");
  1526. lstrcatW(toWalk->dirName, fd.cFileName);
  1527. WINE_TRACE("Added to stack %s (%p->%p)\n", wine_dbgstr_w(toWalk->dirName),
  1528. toWalk, toWalk->next);
  1529. }
  1530. } while (FindNextFileW(hff, &fd) != 0);
  1531. WINE_TRACE("Finished adding all subdirectories\n");
  1532. FindClose (hff);
  1533. }
  1534. }
  1535. /**************************************************************************
  1536. * WCMD_for_nexttoken
  1537. *
  1538. * Parse the token= line, identifying the next highest number not processed
  1539. * so far. Count how many tokens are referred (including duplicates) and
  1540. * optionally return that, plus optionally indicate if the tokens= line
  1541. * ends in a star.
  1542. *
  1543. * Parameters:
  1544. * lasttoken [I] - Identifies the token index of the last one
  1545. * returned so far (-1 used for first loop)
  1546. * tokenstr [I] - The specified tokens= line
  1547. * firstCmd [O] - Optionally indicate how many tokens are listed
  1548. * doAll [O] - Optionally indicate if line ends with *
  1549. * duplicates [O] - Optionally indicate if there is any evidence of
  1550. * overlaying tokens in the string
  1551. * Note the caller should keep a running track of duplicates as the tokens
  1552. * are recursively passed. If any have duplicates, then the * token should
  1553. * not be honoured.
  1554. */
  1555. static int WCMD_for_nexttoken(int lasttoken, WCHAR *tokenstr,
  1556. int *totalfound, BOOL *doall,
  1557. BOOL *duplicates)
  1558. {
  1559. WCHAR *pos = tokenstr;
  1560. int nexttoken = -1;
  1561. if (totalfound) *totalfound = 0;
  1562. if (doall) *doall = FALSE;
  1563. if (duplicates) *duplicates = FALSE;
  1564. WINE_TRACE("Find next token after %d in %s\n", lasttoken,
  1565. wine_dbgstr_w(tokenstr));
  1566. /* Loop through the token string, parsing it. Valid syntax is:
  1567. token=m or x-y with comma delimiter and optionally * to finish*/
  1568. while (*pos) {
  1569. int nextnumber1, nextnumber2 = -1;
  1570. WCHAR *nextchar;
  1571. /* Remember if the next character is a star, it indicates a need to
  1572. show all remaining tokens and should be the last character */
  1573. if (*pos == '*') {
  1574. if (doall) *doall = TRUE;
  1575. if (totalfound) (*totalfound)++;
  1576. /* If we have not found a next token to return, then indicate
  1577. time to process the star */
  1578. if (nexttoken == -1) {
  1579. if (lasttoken == -1) {
  1580. /* Special case the syntax of tokens=* which just means get whole line */
  1581. nexttoken = 0;
  1582. } else {
  1583. nexttoken = lasttoken;
  1584. }
  1585. }
  1586. break;
  1587. }
  1588. /* Get the next number */
  1589. nextnumber1 = wcstoul(pos, &nextchar, 10);
  1590. /* If it is followed by a minus, it's a range, so get the next one as well */
  1591. if (*nextchar == '-') {
  1592. nextnumber2 = wcstoul(nextchar+1, &nextchar, 10);
  1593. /* We want to return the lowest number that is higher than lasttoken
  1594. but only if range is positive */
  1595. if (nextnumber2 >= nextnumber1 &&
  1596. lasttoken < nextnumber2) {
  1597. int nextvalue;
  1598. if (nexttoken == -1) {
  1599. nextvalue = max(nextnumber1, (lasttoken+1));
  1600. } else {
  1601. nextvalue = min(nexttoken, max(nextnumber1, (lasttoken+1)));
  1602. }
  1603. /* Flag if duplicates identified */
  1604. if (nexttoken == nextvalue && duplicates) *duplicates = TRUE;
  1605. nexttoken = nextvalue;
  1606. }
  1607. /* Update the running total for the whole range */
  1608. if (nextnumber2 >= nextnumber1 && totalfound) {
  1609. *totalfound = *totalfound + 1 + (nextnumber2 - nextnumber1);
  1610. }
  1611. pos = nextchar;
  1612. } else if (pos != nextchar) {
  1613. if (totalfound) (*totalfound)++;
  1614. /* See if the number found is one we have already seen */
  1615. if (nextnumber1 == nexttoken && duplicates) *duplicates = TRUE;
  1616. /* We want to return the lowest number that is higher than lasttoken */
  1617. if (lasttoken < nextnumber1 &&
  1618. ((nexttoken == -1) || (nextnumber1 < nexttoken))) {
  1619. nexttoken = nextnumber1;
  1620. }
  1621. pos = nextchar;
  1622. } else {
  1623. /* Step on to the next character, usually over comma */
  1624. if (*pos) pos++;
  1625. }
  1626. }
  1627. /* Return result */
  1628. if (nexttoken == -1) {
  1629. WINE_TRACE("No next token found, previous was %d\n", lasttoken);
  1630. nexttoken = lasttoken;
  1631. } else if (nexttoken==lasttoken && doall && *doall) {
  1632. WINE_TRACE("Request for all remaining tokens now\n");
  1633. } else {
  1634. WINE_TRACE("Found next token after %d was %d\n", lasttoken, nexttoken);
  1635. }
  1636. if (totalfound) WINE_TRACE("Found total tokens to be %d\n", *totalfound);
  1637. if (duplicates && *duplicates) WINE_TRACE("Duplicate numbers found\n");
  1638. return nexttoken;
  1639. }
  1640. /**************************************************************************
  1641. * WCMD_parse_line
  1642. *
  1643. * When parsing file or string contents (for /f), once the string to parse
  1644. * has been identified, handle the various options and call the do part
  1645. * if appropriate.
  1646. *
  1647. * Parameters:
  1648. * cmdStart [I] - Identifies the list of commands making up the
  1649. * for loop body (especially if brackets in use)
  1650. * firstCmd [I] - The textual start of the command after the DO
  1651. * which is within the first item of cmdStart
  1652. * cmdEnd [O] - Identifies where to continue after the DO
  1653. * variable [I] - The variable identified on the for line
  1654. * buffer [I] - The string to parse
  1655. * doExecuted [O] - Set to TRUE if the DO is ever executed once
  1656. * forf_skip [I/O] - How many lines to skip first
  1657. * forf_eol [I] - The 'end of line' (comment) character
  1658. * forf_delims [I] - The delimiters to use when breaking the string apart
  1659. * forf_tokens [I] - The tokens to use when breaking the string apart
  1660. */
  1661. static void WCMD_parse_line(CMD_LIST *cmdStart,
  1662. const WCHAR *firstCmd,
  1663. CMD_LIST **cmdEnd,
  1664. const WCHAR variable,
  1665. WCHAR *buffer,
  1666. BOOL *doExecuted,
  1667. int *forf_skip,
  1668. WCHAR forf_eol,
  1669. WCHAR *forf_delims,
  1670. WCHAR *forf_tokens) {
  1671. WCHAR *parm;
  1672. FOR_CONTEXT oldcontext;
  1673. int varidx, varoffset;
  1674. int nexttoken, lasttoken = -1;
  1675. BOOL starfound = FALSE;
  1676. BOOL thisduplicate = FALSE;
  1677. BOOL anyduplicates = FALSE;
  1678. int totalfound;
  1679. static WCHAR emptyW[] = L"";
  1680. /* Skip lines if requested */
  1681. if (*forf_skip) {
  1682. (*forf_skip)--;
  1683. return;
  1684. }
  1685. /* Save away any existing for variable context (e.g. nested for loops) */
  1686. oldcontext = forloopcontext;
  1687. /* Extract the parameters based on the tokens= value (There will always
  1688. be some value, as if it is not supplied, it defaults to tokens=1).
  1689. Rough logic:
  1690. Count how many tokens are named in the line, identify the lowest
  1691. Empty (set to null terminated string) that number of named variables
  1692. While lasttoken != nextlowest
  1693. %letter = parameter number 'nextlowest'
  1694. letter++ (if >26 or >52 abort)
  1695. Go through token= string finding next lowest number
  1696. If token ends in * set %letter = raw position of token(nextnumber+1)
  1697. */
  1698. lasttoken = -1;
  1699. nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, &totalfound,
  1700. &starfound, &thisduplicate);
  1701. varidx = FOR_VAR_IDX(variable);
  1702. /* Empty out variables */
  1703. for (varoffset=0;
  1704. varidx >= 0 && varoffset<totalfound && (((varidx%26) + varoffset) < 26);
  1705. varoffset++) {
  1706. forloopcontext.variable[varidx + varoffset] = emptyW;
  1707. }
  1708. /* Loop extracting the tokens
  1709. Note: nexttoken of 0 means there were no tokens requested, to handle
  1710. the special case of tokens=* */
  1711. varoffset = 0;
  1712. WINE_TRACE("Parsing buffer into tokens: '%s'\n", wine_dbgstr_w(buffer));
  1713. while (varidx >= 0 && (nexttoken > 0 && (nexttoken > lasttoken))) {
  1714. anyduplicates |= thisduplicate;
  1715. /* Extract the token number requested and set into the next variable context */
  1716. parm = WCMD_parameter_with_delims(buffer, (nexttoken-1), NULL, TRUE, FALSE, forf_delims);
  1717. WINE_TRACE("Parsed token %d(%d) as parameter %s\n", nexttoken,
  1718. varidx + varoffset, wine_dbgstr_w(parm));
  1719. if (varidx >=0) {
  1720. if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
  1721. varoffset++;
  1722. if (((varidx%26)+varoffset) >= 26) break;
  1723. }
  1724. /* Find the next token */
  1725. lasttoken = nexttoken;
  1726. nexttoken = WCMD_for_nexttoken(lasttoken, forf_tokens, NULL,
  1727. &starfound, &thisduplicate);
  1728. }
  1729. /* If all the rest of the tokens were requested, and there is still space in
  1730. the variable range, write them now */
  1731. if (!anyduplicates && starfound && varidx >= 0 && (((varidx%26) + varoffset) < 26)) {
  1732. nexttoken++;
  1733. WCMD_parameter_with_delims(buffer, (nexttoken-1), &parm, FALSE, FALSE, forf_delims);
  1734. WINE_TRACE("Parsed allremaining tokens (%d) as parameter %s\n",
  1735. varidx + varoffset, wine_dbgstr_w(parm));
  1736. if (parm) forloopcontext.variable[varidx + varoffset] = heap_strdupW(parm);
  1737. }
  1738. /* Execute the body of the foor loop with these values */
  1739. if (forloopcontext.variable[varidx] && forloopcontext.variable[varidx][0] != forf_eol) {
  1740. CMD_LIST *thisCmdStart = cmdStart;
  1741. *doExecuted = TRUE;
  1742. WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, TRUE);
  1743. *cmdEnd = thisCmdStart;
  1744. }
  1745. /* Free the duplicated strings, and restore the context */
  1746. if (varidx >=0) {
  1747. int i;
  1748. for (i=varidx; i<MAX_FOR_VARIABLES; i++) {
  1749. if ((forloopcontext.variable[i] != oldcontext.variable[i]) &&
  1750. (forloopcontext.variable[i] != emptyW)) {
  1751. heap_free(forloopcontext.variable[i]);
  1752. }
  1753. }
  1754. }
  1755. /* Restore the original for variable contextx */
  1756. forloopcontext = oldcontext;
  1757. }
  1758. /**************************************************************************
  1759. * WCMD_forf_getinputhandle
  1760. *
  1761. * Return a file handle which can be used for reading the input lines,
  1762. * either to a specific file (which may be quote delimited as we have to
  1763. * read the parameters in raw mode) or to a command which we need to
  1764. * execute. The command being executed runs in its own shell and stores
  1765. * its data in a temporary file.
  1766. *
  1767. * Parameters:
  1768. * usebackq [I] - Indicates whether usebackq is in effect or not
  1769. * itemStr [I] - The item to be handled, either a filename or
  1770. * whole command string to execute
  1771. * iscmd [I] - Identifies whether this is a command or not
  1772. *
  1773. * Returns a file handle which can be used to read the input lines from.
  1774. */
  1775. static HANDLE WCMD_forf_getinputhandle(BOOL usebackq, WCHAR *itemstr, BOOL iscmd) {
  1776. WCHAR temp_str[MAX_PATH];
  1777. WCHAR temp_file[MAX_PATH];
  1778. WCHAR temp_cmd[MAXSTRING];
  1779. WCHAR *trimmed = NULL;
  1780. HANDLE hinput = INVALID_HANDLE_VALUE;
  1781. /* Remove leading and trailing character (but there may be trailing whitespace too) */
  1782. if ((iscmd && (itemstr[0] == '`' && usebackq)) ||
  1783. (iscmd && (itemstr[0] == '\'' && !usebackq)) ||
  1784. (!iscmd && (itemstr[0] == '"' && usebackq)))
  1785. {
  1786. trimmed = WCMD_strtrim(itemstr);
  1787. if (trimmed) {
  1788. itemstr = trimmed;
  1789. }
  1790. itemstr[lstrlenW(itemstr)-1] = 0x00;
  1791. itemstr++;
  1792. }
  1793. if (iscmd) {
  1794. /* Get temp filename */
  1795. GetTempPathW(ARRAY_SIZE(temp_str), temp_str);
  1796. GetTempFileNameW(temp_str, L"CMD", 0, temp_file);
  1797. /* Redirect output to the temporary file */
  1798. wsprintfW(temp_str, L">%s", temp_file);
  1799. wsprintfW(temp_cmd, L"CMD.EXE /C %s", itemstr);
  1800. WINE_TRACE("Issuing '%s' with redirs '%s'\n",
  1801. wine_dbgstr_w(temp_cmd), wine_dbgstr_w(temp_str));
  1802. WCMD_execute (temp_cmd, temp_str, NULL, FALSE);
  1803. /* Open the file, read line by line and process */
  1804. hinput = CreateFileW(temp_file, GENERIC_READ, FILE_SHARE_READ,
  1805. NULL, OPEN_EXISTING, FILE_FLAG_DELETE_ON_CLOSE, NULL);
  1806. } else {
  1807. /* Open the file, read line by line and process */
  1808. WINE_TRACE("Reading input to parse from '%s'\n", wine_dbgstr_w(itemstr));
  1809. hinput = CreateFileW(itemstr, GENERIC_READ, FILE_SHARE_READ,
  1810. NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
  1811. }
  1812. heap_free(trimmed);
  1813. return hinput;
  1814. }
  1815. /**************************************************************************
  1816. * WCMD_for
  1817. *
  1818. * Batch file loop processing.
  1819. *
  1820. * On entry: cmdList contains the syntax up to the set
  1821. * next cmdList and all in that bracket contain the set data
  1822. * next cmdlist contains the DO cmd
  1823. * following that is either brackets or && entries (as per if)
  1824. *
  1825. */
  1826. void WCMD_for (WCHAR *p, CMD_LIST **cmdList) {
  1827. WIN32_FIND_DATAW fd;
  1828. HANDLE hff;
  1829. int i;
  1830. const int in_len = lstrlenW(L"in");
  1831. CMD_LIST *setStart, *thisSet, *cmdStart, *cmdEnd;
  1832. WCHAR variable[4];
  1833. int varidx = -1;
  1834. WCHAR *oldvariablevalue;
  1835. WCHAR *firstCmd;
  1836. int thisDepth;
  1837. WCHAR optionsRoot[MAX_PATH];
  1838. DIRECTORY_STACK *dirsToWalk = NULL;
  1839. BOOL expandDirs = FALSE;
  1840. BOOL useNumbers = FALSE;
  1841. BOOL doFileset = FALSE;
  1842. BOOL doRecurse = FALSE;
  1843. BOOL doExecuted = FALSE; /* Has the 'do' part been executed */
  1844. LONG numbers[3] = {0,0,0}; /* Defaults to 0 in native */
  1845. int itemNum;
  1846. CMD_LIST *thisCmdStart;
  1847. int parameterNo = 0;
  1848. WCHAR forf_eol = 0;
  1849. int forf_skip = 0;
  1850. WCHAR forf_delims[256];
  1851. WCHAR forf_tokens[MAXSTRING];
  1852. BOOL forf_usebackq = FALSE;
  1853. /* Handle optional qualifiers (multiple are allowed) */
  1854. WCHAR *thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
  1855. optionsRoot[0] = 0;
  1856. while (thisArg && *thisArg == '/') {
  1857. WINE_TRACE("Processing qualifier at %s\n", wine_dbgstr_w(thisArg));
  1858. thisArg++;
  1859. switch (towupper(*thisArg)) {
  1860. case 'D': expandDirs = TRUE; break;
  1861. case 'L': useNumbers = TRUE; break;
  1862. /* Recursive is special case - /R can have an optional path following it */
  1863. /* filenamesets are another special case - /F can have an optional options following it */
  1864. case 'R':
  1865. case 'F':
  1866. {
  1867. /* When recursing directories, use current directory as the starting point unless
  1868. subsequently overridden */
  1869. doRecurse = (towupper(*thisArg) == 'R');
  1870. if (doRecurse) GetCurrentDirectoryW(ARRAY_SIZE(optionsRoot), optionsRoot);
  1871. doFileset = (towupper(*thisArg) == 'F');
  1872. /* Retrieve next parameter to see if is root/options (raw form required
  1873. with for /f, or unquoted in for /r) */
  1874. thisArg = WCMD_parameter(p, parameterNo, NULL, doFileset, FALSE);
  1875. /* Next parm is either qualifier, path/options or variable -
  1876. only care about it if it is the path/options */
  1877. if (thisArg && *thisArg != '/' && *thisArg != '%') {
  1878. parameterNo++;
  1879. lstrcpyW(optionsRoot, thisArg);
  1880. }
  1881. break;
  1882. }
  1883. default:
  1884. WINE_FIXME("for qualifier '%c' unhandled\n", *thisArg);
  1885. }
  1886. /* Step to next token */
  1887. thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
  1888. }
  1889. /* Ensure line continues with variable */
  1890. if (*thisArg != '%') {
  1891. WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
  1892. return;
  1893. }
  1894. /* With for /f parse the options if provided */
  1895. if (doFileset) {
  1896. if (!WCMD_parse_forf_options(optionsRoot, &forf_eol, &forf_skip,
  1897. forf_delims, forf_tokens, &forf_usebackq))
  1898. {
  1899. WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
  1900. return;
  1901. }
  1902. /* Set up the list of directories to recurse if we are going to */
  1903. } else if (doRecurse) {
  1904. /* Allocate memory, add to list */
  1905. dirsToWalk = heap_xalloc(sizeof(DIRECTORY_STACK));
  1906. dirsToWalk->next = NULL;
  1907. dirsToWalk->dirName = heap_strdupW(optionsRoot);
  1908. WINE_TRACE("Starting with root directory %s\n", wine_dbgstr_w(dirsToWalk->dirName));
  1909. }
  1910. /* Variable should follow */
  1911. lstrcpyW(variable, thisArg);
  1912. WINE_TRACE("Variable identified as %s\n", wine_dbgstr_w(variable));
  1913. varidx = FOR_VAR_IDX(variable[1]);
  1914. /* Ensure line continues with IN */
  1915. thisArg = WCMD_parameter(p, parameterNo++, NULL, FALSE, FALSE);
  1916. if (!thisArg
  1917. || !(CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  1918. thisArg, in_len, L"in", in_len) == CSTR_EQUAL)) {
  1919. WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
  1920. return;
  1921. }
  1922. /* Save away where the set of data starts and the variable */
  1923. thisDepth = (*cmdList)->bracketDepth;
  1924. *cmdList = (*cmdList)->nextcommand;
  1925. setStart = (*cmdList);
  1926. /* Skip until the close bracket */
  1927. WINE_TRACE("Searching %p as the set\n", *cmdList);
  1928. while (*cmdList &&
  1929. (*cmdList)->command != NULL &&
  1930. (*cmdList)->bracketDepth > thisDepth) {
  1931. WINE_TRACE("Skipping %p which is part of the set\n", *cmdList);
  1932. *cmdList = (*cmdList)->nextcommand;
  1933. }
  1934. /* Skip the close bracket, if there is one */
  1935. if (*cmdList) *cmdList = (*cmdList)->nextcommand;
  1936. /* Syntax error if missing close bracket, or nothing following it
  1937. and once we have the complete set, we expect a DO */
  1938. WINE_TRACE("Looking for 'do ' in %p\n", *cmdList);
  1939. if ((*cmdList == NULL) || !WCMD_keyword_ws_found(L"do", (*cmdList)->command)) {
  1940. WCMD_output_stderr (WCMD_LoadMessage(WCMD_SYNTAXERR));
  1941. return;
  1942. }
  1943. cmdEnd = *cmdList;
  1944. /* Loop repeatedly per-directory we are potentially walking, when in for /r
  1945. mode, or once for the rest of the time. */
  1946. do {
  1947. /* Save away the starting position for the commands (and offset for the
  1948. first one) */
  1949. cmdStart = *cmdList;
  1950. firstCmd = (*cmdList)->command + 3; /* Skip 'do ' */
  1951. itemNum = 0;
  1952. /* If we are recursing directories (ie /R), add all sub directories now, then
  1953. prefix the root when searching for the item */
  1954. if (dirsToWalk) WCMD_add_dirstowalk(dirsToWalk);
  1955. thisSet = setStart;
  1956. /* Loop through all set entries */
  1957. while (thisSet &&
  1958. thisSet->command != NULL &&
  1959. thisSet->bracketDepth >= thisDepth) {
  1960. /* Loop through all entries on the same line */
  1961. WCHAR *staticitem;
  1962. WCHAR *itemStart;
  1963. WCHAR buffer[MAXSTRING];
  1964. WINE_TRACE("Processing for set %p\n", thisSet);
  1965. i = 0;
  1966. while (*(staticitem = WCMD_parameter (thisSet->command, i, &itemStart, TRUE, FALSE))) {
  1967. /*
  1968. * If the parameter within the set has a wildcard then search for matching files
  1969. * otherwise do a literal substitution.
  1970. */
  1971. /* Take a copy of the item returned from WCMD_parameter as it is held in a
  1972. static buffer which can be overwritten during parsing of the for body */
  1973. WCHAR item[MAXSTRING];
  1974. lstrcpyW(item, staticitem);
  1975. thisCmdStart = cmdStart;
  1976. itemNum++;
  1977. WINE_TRACE("Processing for item %d '%s'\n", itemNum, wine_dbgstr_w(item));
  1978. if (!useNumbers && !doFileset) {
  1979. WCHAR fullitem[MAX_PATH];
  1980. int prefixlen = 0;
  1981. /* Now build the item to use / search for in the specified directory,
  1982. as it is fully qualified in the /R case */
  1983. if (dirsToWalk) {
  1984. lstrcpyW(fullitem, dirsToWalk->dirName);
  1985. lstrcatW(fullitem, L"\\");
  1986. lstrcatW(fullitem, item);
  1987. } else {
  1988. WCHAR *prefix = wcsrchr(item, '\\');
  1989. if (prefix) prefixlen = (prefix - item) + 1;
  1990. lstrcpyW(fullitem, item);
  1991. }
  1992. if (wcspbrk(fullitem, L"*?")) {
  1993. hff = FindFirstFileW(fullitem, &fd);
  1994. if (hff != INVALID_HANDLE_VALUE) {
  1995. do {
  1996. BOOL isDirectory = FALSE;
  1997. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) isDirectory = TRUE;
  1998. /* Handle as files or dirs appropriately, but ignore . and .. */
  1999. if (isDirectory == expandDirs &&
  2000. (lstrcmpW(fd.cFileName, L"..") != 0) && (lstrcmpW(fd.cFileName, L".") != 0))
  2001. {
  2002. thisCmdStart = cmdStart;
  2003. WINE_TRACE("Processing FOR filename %s\n", wine_dbgstr_w(fd.cFileName));
  2004. if (doRecurse) {
  2005. lstrcpyW(fullitem, dirsToWalk->dirName);
  2006. lstrcatW(fullitem, L"\\");
  2007. lstrcatW(fullitem, fd.cFileName);
  2008. } else {
  2009. if (prefixlen) lstrcpynW(fullitem, item, prefixlen + 1);
  2010. fullitem[prefixlen] = 0x00;
  2011. lstrcatW(fullitem, fd.cFileName);
  2012. }
  2013. doExecuted = TRUE;
  2014. /* Save away any existing for variable context (e.g. nested for loops)
  2015. and restore it after executing the body of this for loop */
  2016. if (varidx >= 0) {
  2017. oldvariablevalue = forloopcontext.variable[varidx];
  2018. forloopcontext.variable[varidx] = fullitem;
  2019. }
  2020. WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
  2021. if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
  2022. cmdEnd = thisCmdStart;
  2023. }
  2024. } while (FindNextFileW(hff, &fd) != 0);
  2025. FindClose (hff);
  2026. }
  2027. } else {
  2028. doExecuted = TRUE;
  2029. /* Save away any existing for variable context (e.g. nested for loops)
  2030. and restore it after executing the body of this for loop */
  2031. if (varidx >= 0) {
  2032. oldvariablevalue = forloopcontext.variable[varidx];
  2033. forloopcontext.variable[varidx] = fullitem;
  2034. }
  2035. WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
  2036. if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
  2037. cmdEnd = thisCmdStart;
  2038. }
  2039. } else if (useNumbers) {
  2040. /* Convert the first 3 numbers to signed longs and save */
  2041. if (itemNum <=3) numbers[itemNum-1] = wcstol(item, NULL, 10);
  2042. /* else ignore them! */
  2043. /* Filesets - either a list of files, or a command to run and parse the output */
  2044. } else if (doFileset && ((!forf_usebackq && *itemStart != '"') ||
  2045. (forf_usebackq && *itemStart != '\''))) {
  2046. HANDLE input;
  2047. WCHAR *itemparm;
  2048. WINE_TRACE("Processing for filespec from item %d '%s'\n", itemNum,
  2049. wine_dbgstr_w(item));
  2050. /* If backquote or single quote, we need to launch that command
  2051. and parse the results - use a temporary file */
  2052. if ((forf_usebackq && *itemStart == '`') ||
  2053. (!forf_usebackq && *itemStart == '\'')) {
  2054. /* Use itemstart because the command is the whole set, not just the first token */
  2055. itemparm = itemStart;
  2056. } else {
  2057. /* Use item because the file to process is just the first item in the set */
  2058. itemparm = item;
  2059. }
  2060. input = WCMD_forf_getinputhandle(forf_usebackq, itemparm, (itemparm==itemStart));
  2061. /* Process the input file */
  2062. if (input == INVALID_HANDLE_VALUE) {
  2063. WCMD_print_error ();
  2064. WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), item);
  2065. errorlevel = 1;
  2066. return; /* FOR loop aborts at first failure here */
  2067. } else {
  2068. /* Read line by line until end of file */
  2069. while (WCMD_fgets(buffer, ARRAY_SIZE(buffer), input)) {
  2070. WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
  2071. &forf_skip, forf_eol, forf_delims, forf_tokens);
  2072. buffer[0] = 0;
  2073. }
  2074. CloseHandle (input);
  2075. }
  2076. /* When we have processed the item as a whole command, abort future set processing */
  2077. if (itemparm==itemStart) {
  2078. thisSet = NULL;
  2079. break;
  2080. }
  2081. /* Filesets - A string literal */
  2082. } else if (doFileset && ((!forf_usebackq && *itemStart == '"') ||
  2083. (forf_usebackq && *itemStart == '\''))) {
  2084. /* Remove leading and trailing character, ready to parse with delims= delimiters
  2085. Note that the last quote is removed from the set and the string terminates
  2086. there to mimic windows */
  2087. WCHAR *strend = wcsrchr(itemStart, forf_usebackq?'\'':'"');
  2088. if (strend) {
  2089. *strend = 0x00;
  2090. itemStart++;
  2091. }
  2092. /* Copy the item away from the global buffer used by WCMD_parameter */
  2093. lstrcpyW(buffer, itemStart);
  2094. WCMD_parse_line(cmdStart, firstCmd, &cmdEnd, variable[1], buffer, &doExecuted,
  2095. &forf_skip, forf_eol, forf_delims, forf_tokens);
  2096. /* Only one string can be supplied in the whole set, abort future set processing */
  2097. thisSet = NULL;
  2098. break;
  2099. }
  2100. WINE_TRACE("Post-command, cmdEnd = %p\n", cmdEnd);
  2101. i++;
  2102. }
  2103. /* Move onto the next set line */
  2104. if (thisSet) thisSet = thisSet->nextcommand;
  2105. }
  2106. /* If /L is provided, now run the for loop */
  2107. if (useNumbers) {
  2108. WCHAR thisNum[20];
  2109. WINE_TRACE("FOR /L provided range from %d to %d step %d\n",
  2110. numbers[0], numbers[2], numbers[1]);
  2111. for (i=numbers[0];
  2112. (numbers[1]<0)? i>=numbers[2] : i<=numbers[2];
  2113. i=i + numbers[1]) {
  2114. swprintf(thisNum, ARRAY_SIZE(thisNum), L"%d", i);
  2115. WINE_TRACE("Processing FOR number %s\n", wine_dbgstr_w(thisNum));
  2116. thisCmdStart = cmdStart;
  2117. doExecuted = TRUE;
  2118. /* Save away any existing for variable context (e.g. nested for loops)
  2119. and restore it after executing the body of this for loop */
  2120. if (varidx >= 0) {
  2121. oldvariablevalue = forloopcontext.variable[varidx];
  2122. forloopcontext.variable[varidx] = thisNum;
  2123. }
  2124. WCMD_part_execute (&thisCmdStart, firstCmd, FALSE, TRUE);
  2125. if (varidx >= 0) forloopcontext.variable[varidx] = oldvariablevalue;
  2126. }
  2127. cmdEnd = thisCmdStart;
  2128. }
  2129. /* If we are walking directories, move on to any which remain */
  2130. if (dirsToWalk != NULL) {
  2131. DIRECTORY_STACK *nextDir = dirsToWalk->next;
  2132. heap_free(dirsToWalk->dirName);
  2133. heap_free(dirsToWalk);
  2134. dirsToWalk = nextDir;
  2135. if (dirsToWalk) WINE_TRACE("Moving to next directory to iterate: %s\n",
  2136. wine_dbgstr_w(dirsToWalk->dirName));
  2137. else WINE_TRACE("Finished all directories.\n");
  2138. }
  2139. } while (dirsToWalk != NULL);
  2140. /* Now skip over the do part if we did not perform the for loop so far.
  2141. We store in cmdEnd the next command after the do block, but we only
  2142. know this if something was run. If it has not been, we need to calculate
  2143. it. */
  2144. if (!doExecuted) {
  2145. thisCmdStart = cmdStart;
  2146. WINE_TRACE("Skipping for loop commands due to no valid iterations\n");
  2147. WCMD_part_execute(&thisCmdStart, firstCmd, FALSE, FALSE);
  2148. cmdEnd = thisCmdStart;
  2149. }
  2150. /* When the loop ends, either something like a GOTO or EXIT /b has terminated
  2151. all processing, OR it should be pointing to the end of && processing OR
  2152. it should be pointing at the NULL end of bracket for the DO. The return
  2153. value needs to be the NEXT command to execute, which it either is, or
  2154. we need to step over the closing bracket */
  2155. *cmdList = cmdEnd;
  2156. if (cmdEnd && cmdEnd->command == NULL) *cmdList = cmdEnd->nextcommand;
  2157. }
  2158. /**************************************************************************
  2159. * WCMD_give_help
  2160. *
  2161. * Simple on-line help. Help text is stored in the resource file.
  2162. */
  2163. void WCMD_give_help (const WCHAR *args)
  2164. {
  2165. size_t i;
  2166. args = WCMD_skip_leading_spaces((WCHAR*) args);
  2167. if (!*args) {
  2168. WCMD_output_asis (WCMD_LoadMessage(WCMD_ALLHELP));
  2169. }
  2170. else {
  2171. /* Display help message for builtin commands */
  2172. for (i=0; i<=WCMD_EXIT; i++) {
  2173. if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  2174. args, -1, inbuilt[i], -1) == CSTR_EQUAL) {
  2175. WCMD_output_asis (WCMD_LoadMessage(i));
  2176. return;
  2177. }
  2178. }
  2179. /* Launch the command with the /? option for external commands shipped with cmd.exe */
  2180. for (i = 0; i <= ARRAY_SIZE(externals); i++) {
  2181. if (CompareStringW(LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  2182. args, -1, externals[i], -1) == CSTR_EQUAL) {
  2183. WCHAR cmd[128];
  2184. lstrcpyW(cmd, args);
  2185. lstrcatW(cmd, L" /?");
  2186. WCMD_run_program(cmd, FALSE);
  2187. return;
  2188. }
  2189. }
  2190. WCMD_output (WCMD_LoadMessage(WCMD_NOCMDHELP), args);
  2191. }
  2192. return;
  2193. }
  2194. /****************************************************************************
  2195. * WCMD_go_to
  2196. *
  2197. * Batch file jump instruction. Not the most efficient algorithm ;-)
  2198. * Prints error message if the specified label cannot be found - the file pointer is
  2199. * then at EOF, effectively stopping the batch file.
  2200. * FIXME: DOS is supposed to allow labels with spaces - we don't.
  2201. */
  2202. void WCMD_goto (CMD_LIST **cmdList) {
  2203. WCHAR string[MAX_PATH];
  2204. WCHAR *labelend = NULL;
  2205. const WCHAR labelEndsW[] = L"><|& :\t";
  2206. /* Do not process any more parts of a processed multipart or multilines command */
  2207. if (cmdList) *cmdList = NULL;
  2208. if (context != NULL) {
  2209. WCHAR *paramStart = param1, *str;
  2210. if (param1[0] == 0x00) {
  2211. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  2212. return;
  2213. }
  2214. /* Handle special :EOF label */
  2215. if (lstrcmpiW(L":eof", param1) == 0) {
  2216. context -> skip_rest = TRUE;
  2217. return;
  2218. }
  2219. /* Support goto :label as well as goto label plus remove trailing chars */
  2220. if (*paramStart == ':') paramStart++;
  2221. labelend = wcspbrk(paramStart, labelEndsW);
  2222. if (labelend) *labelend = 0x00;
  2223. WINE_TRACE("goto label: '%s'\n", wine_dbgstr_w(paramStart));
  2224. /* Loop through potentially twice - once from current file position
  2225. through to the end, and second time from start to current file
  2226. position */
  2227. if (*paramStart) {
  2228. int loop;
  2229. LARGE_INTEGER startli;
  2230. for (loop=0; loop<2; loop++) {
  2231. if (loop==0) {
  2232. /* On first loop, save the file size */
  2233. startli.QuadPart = 0;
  2234. startli.u.LowPart = SetFilePointer(context -> h, startli.u.LowPart,
  2235. &startli.u.HighPart, FILE_CURRENT);
  2236. } else {
  2237. /* On second loop, start at the beginning of the file */
  2238. WINE_TRACE("Label not found, trying from beginning of file\n");
  2239. if (loop==1) SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
  2240. }
  2241. while (WCMD_fgets (string, ARRAY_SIZE(string), context -> h)) {
  2242. str = string;
  2243. /* Ignore leading whitespace or no-echo character */
  2244. while (*str=='@' || iswspace (*str)) str++;
  2245. /* If the first real character is a : then this is a label */
  2246. if (*str == ':') {
  2247. str++;
  2248. /* Skip spaces between : and label */
  2249. while (iswspace (*str)) str++;
  2250. WINE_TRACE("str before brk %s\n", wine_dbgstr_w(str));
  2251. /* Label ends at whitespace or redirection characters */
  2252. labelend = wcspbrk(str, labelEndsW);
  2253. if (labelend) *labelend = 0x00;
  2254. WINE_TRACE("comparing found label %s\n", wine_dbgstr_w(str));
  2255. if (lstrcmpiW (str, paramStart) == 0) return;
  2256. }
  2257. /* See if we have gone beyond the end point if second time through */
  2258. if (loop==1) {
  2259. LARGE_INTEGER curli;
  2260. curli.QuadPart = 0;
  2261. curli.u.LowPart = SetFilePointer(context -> h, curli.u.LowPart,
  2262. &curli.u.HighPart, FILE_CURRENT);
  2263. if (curli.QuadPart > startli.QuadPart) {
  2264. WINE_TRACE("Reached wrap point, label not found\n");
  2265. break;
  2266. }
  2267. }
  2268. }
  2269. }
  2270. }
  2271. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
  2272. context -> skip_rest = TRUE;
  2273. }
  2274. return;
  2275. }
  2276. /*****************************************************************************
  2277. * WCMD_pushd
  2278. *
  2279. * Push a directory onto the stack
  2280. */
  2281. void WCMD_pushd (const WCHAR *args)
  2282. {
  2283. struct env_stack *curdir;
  2284. WCHAR *thisdir;
  2285. if (wcschr(args, '/') != NULL) {
  2286. SetLastError(ERROR_INVALID_PARAMETER);
  2287. WCMD_print_error();
  2288. return;
  2289. }
  2290. curdir = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
  2291. thisdir = LocalAlloc (LMEM_FIXED, 1024 * sizeof(WCHAR));
  2292. if( !curdir || !thisdir ) {
  2293. LocalFree(curdir);
  2294. LocalFree(thisdir);
  2295. WINE_ERR ("out of memory\n");
  2296. return;
  2297. }
  2298. /* Change directory using CD code with /D parameter */
  2299. lstrcpyW(quals, L"/D");
  2300. GetCurrentDirectoryW (1024, thisdir);
  2301. errorlevel = 0;
  2302. WCMD_setshow_default(args);
  2303. if (errorlevel) {
  2304. LocalFree(curdir);
  2305. LocalFree(thisdir);
  2306. return;
  2307. } else {
  2308. curdir -> next = pushd_directories;
  2309. curdir -> strings = thisdir;
  2310. if (pushd_directories == NULL) {
  2311. curdir -> u.stackdepth = 1;
  2312. } else {
  2313. curdir -> u.stackdepth = pushd_directories -> u.stackdepth + 1;
  2314. }
  2315. pushd_directories = curdir;
  2316. }
  2317. }
  2318. /*****************************************************************************
  2319. * WCMD_popd
  2320. *
  2321. * Pop a directory from the stack
  2322. */
  2323. void WCMD_popd (void) {
  2324. struct env_stack *temp = pushd_directories;
  2325. if (!pushd_directories)
  2326. return;
  2327. /* pop the old environment from the stack, and make it the current dir */
  2328. pushd_directories = temp->next;
  2329. SetCurrentDirectoryW(temp->strings);
  2330. LocalFree (temp->strings);
  2331. LocalFree (temp);
  2332. }
  2333. /*******************************************************************
  2334. * evaluate_if_comparison
  2335. *
  2336. * Evaluates an "if" comparison operation
  2337. *
  2338. * PARAMS
  2339. * leftOperand [I] left operand, non NULL
  2340. * operator [I] "if" binary comparison operator, non NULL
  2341. * rightOperand [I] right operand, non NULL
  2342. * caseInsensitive [I] 0 for case sensitive comparison, anything else for insensitive
  2343. *
  2344. * RETURNS
  2345. * Success: 1 if operator applied to the operands evaluates to TRUE
  2346. * 0 if operator applied to the operands evaluates to FALSE
  2347. * Failure: -1 if operator is not recognized
  2348. */
  2349. static int evaluate_if_comparison(const WCHAR *leftOperand, const WCHAR *operator,
  2350. const WCHAR *rightOperand, int caseInsensitive)
  2351. {
  2352. WCHAR *endptr_leftOp, *endptr_rightOp;
  2353. long int leftOperand_int, rightOperand_int;
  2354. BOOL int_operands;
  2355. /* == is a special case, as it always compares strings */
  2356. if (!lstrcmpiW(operator, L"=="))
  2357. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
  2358. : lstrcmpW (leftOperand, rightOperand) == 0;
  2359. /* Check if we have plain integers (in decimal, octal or hexadecimal notation) */
  2360. leftOperand_int = wcstol(leftOperand, &endptr_leftOp, 0);
  2361. rightOperand_int = wcstol(rightOperand, &endptr_rightOp, 0);
  2362. int_operands = (!*endptr_leftOp) && (!*endptr_rightOp);
  2363. /* Perform actual (integer or string) comparison */
  2364. if (!lstrcmpiW(operator, L"lss")) {
  2365. if (int_operands)
  2366. return leftOperand_int < rightOperand_int;
  2367. else
  2368. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) < 0
  2369. : lstrcmpW (leftOperand, rightOperand) < 0;
  2370. }
  2371. if (!lstrcmpiW(operator, L"leq")) {
  2372. if (int_operands)
  2373. return leftOperand_int <= rightOperand_int;
  2374. else
  2375. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) <= 0
  2376. : lstrcmpW (leftOperand, rightOperand) <= 0;
  2377. }
  2378. if (!lstrcmpiW(operator, L"equ")) {
  2379. if (int_operands)
  2380. return leftOperand_int == rightOperand_int;
  2381. else
  2382. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) == 0
  2383. : lstrcmpW (leftOperand, rightOperand) == 0;
  2384. }
  2385. if (!lstrcmpiW(operator, L"neq")) {
  2386. if (int_operands)
  2387. return leftOperand_int != rightOperand_int;
  2388. else
  2389. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) != 0
  2390. : lstrcmpW (leftOperand, rightOperand) != 0;
  2391. }
  2392. if (!lstrcmpiW(operator, L"geq")) {
  2393. if (int_operands)
  2394. return leftOperand_int >= rightOperand_int;
  2395. else
  2396. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) >= 0
  2397. : lstrcmpW (leftOperand, rightOperand) >= 0;
  2398. }
  2399. if (!lstrcmpiW(operator, L"gtr")) {
  2400. if (int_operands)
  2401. return leftOperand_int > rightOperand_int;
  2402. else
  2403. return caseInsensitive ? lstrcmpiW(leftOperand, rightOperand) > 0
  2404. : lstrcmpW (leftOperand, rightOperand) > 0;
  2405. }
  2406. return -1;
  2407. }
  2408. int evaluate_if_condition(WCHAR *p, WCHAR **command, int *test, int *negate)
  2409. {
  2410. WCHAR condition[MAX_PATH];
  2411. int caseInsensitive = (wcsstr(quals, L"/I") != NULL);
  2412. *negate = !lstrcmpiW(param1,L"not");
  2413. lstrcpyW(condition, (*negate ? param2 : param1));
  2414. WINE_TRACE("Condition: %s\n", wine_dbgstr_w(condition));
  2415. if (!lstrcmpiW(condition, L"errorlevel")) {
  2416. WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
  2417. WCHAR *endptr;
  2418. long int param_int = wcstol(param, &endptr, 10);
  2419. if (*endptr) goto syntax_err;
  2420. *test = ((long int)errorlevel >= param_int);
  2421. WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
  2422. }
  2423. else if (!lstrcmpiW(condition, L"exist")) {
  2424. WIN32_FIND_DATAW fd;
  2425. HANDLE hff;
  2426. WCHAR *param = WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE);
  2427. int len = lstrlenW(param);
  2428. if (!len) goto syntax_err;
  2429. /* FindFirstFile does not like a directory path ending in '\', append a '.' */
  2430. if (param[len-1] == '\\') lstrcatW(param, L".");
  2431. hff = FindFirstFileW(param, &fd);
  2432. *test = (hff != INVALID_HANDLE_VALUE );
  2433. if (*test) FindClose(hff);
  2434. WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
  2435. }
  2436. else if (!lstrcmpiW(condition, L"defined")) {
  2437. *test = (GetEnvironmentVariableW(WCMD_parameter(p, 1+(*negate), NULL, FALSE, FALSE),
  2438. NULL, 0) > 0);
  2439. WCMD_parameter(p, 2+(*negate), command, FALSE, FALSE);
  2440. }
  2441. else { /* comparison operation */
  2442. WCHAR leftOperand[MAXSTRING], rightOperand[MAXSTRING], operator[MAXSTRING];
  2443. WCHAR *paramStart;
  2444. lstrcpyW(leftOperand, WCMD_parameter(p, (*negate)+caseInsensitive, &paramStart, TRUE, FALSE));
  2445. if (!*leftOperand)
  2446. goto syntax_err;
  2447. /* Note: '==' can't be returned by WCMD_parameter since '=' is a separator */
  2448. p = paramStart + lstrlenW(leftOperand);
  2449. while (*p == ' ' || *p == '\t')
  2450. p++;
  2451. if (!wcsncmp(p, L"==", lstrlenW(L"==")))
  2452. lstrcpyW(operator, L"==");
  2453. else {
  2454. lstrcpyW(operator, WCMD_parameter(p, 0, &paramStart, FALSE, FALSE));
  2455. if (!*operator) goto syntax_err;
  2456. }
  2457. p += lstrlenW(operator);
  2458. lstrcpyW(rightOperand, WCMD_parameter(p, 0, &paramStart, TRUE, FALSE));
  2459. if (!*rightOperand)
  2460. goto syntax_err;
  2461. *test = evaluate_if_comparison(leftOperand, operator, rightOperand, caseInsensitive);
  2462. if (*test == -1)
  2463. goto syntax_err;
  2464. p = paramStart + lstrlenW(rightOperand);
  2465. WCMD_parameter(p, 0, command, FALSE, FALSE);
  2466. }
  2467. return 1;
  2468. syntax_err:
  2469. return -1;
  2470. }
  2471. /****************************************************************************
  2472. * WCMD_if
  2473. *
  2474. * Batch file conditional.
  2475. *
  2476. * On entry, cmdlist will point to command containing the IF, and optionally
  2477. * the first command to execute (if brackets not found)
  2478. * If &&'s were found, this may be followed by a record flagged as isAmpersand
  2479. * If ('s were found, execute all within that bracket
  2480. * Command may optionally be followed by an ELSE - need to skip instructions
  2481. * in the else using the same logic
  2482. *
  2483. * FIXME: Much more syntax checking needed!
  2484. */
  2485. void WCMD_if (WCHAR *p, CMD_LIST **cmdList)
  2486. {
  2487. int negate; /* Negate condition */
  2488. int test; /* Condition evaluation result */
  2489. WCHAR *command;
  2490. /* Function evaluate_if_condition relies on the global variables quals, param1 and param2
  2491. set in a call to WCMD_parse before */
  2492. if (evaluate_if_condition(p, &command, &test, &negate) == -1)
  2493. goto syntax_err;
  2494. WINE_TRACE("p: %s, quals: %s, param1: %s, param2: %s, command: %s\n",
  2495. wine_dbgstr_w(p), wine_dbgstr_w(quals), wine_dbgstr_w(param1),
  2496. wine_dbgstr_w(param2), wine_dbgstr_w(command));
  2497. /* Process rest of IF statement which is on the same line
  2498. Note: This may process all or some of the cmdList (eg a GOTO) */
  2499. WCMD_part_execute(cmdList, command, TRUE, (test != negate));
  2500. return;
  2501. syntax_err:
  2502. WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
  2503. }
  2504. /****************************************************************************
  2505. * WCMD_move
  2506. *
  2507. * Move a file, directory tree or wildcarded set of files.
  2508. */
  2509. void WCMD_move (void)
  2510. {
  2511. BOOL status;
  2512. WIN32_FIND_DATAW fd;
  2513. HANDLE hff;
  2514. WCHAR input[MAX_PATH];
  2515. WCHAR output[MAX_PATH];
  2516. WCHAR drive[10];
  2517. WCHAR dir[MAX_PATH];
  2518. WCHAR fname[MAX_PATH];
  2519. WCHAR ext[MAX_PATH];
  2520. if (param1[0] == 0x00) {
  2521. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  2522. return;
  2523. }
  2524. /* If no destination supplied, assume current directory */
  2525. if (param2[0] == 0x00) {
  2526. lstrcpyW(param2, L".");
  2527. }
  2528. /* If 2nd parm is directory, then use original filename */
  2529. /* Convert partial path to full path */
  2530. GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
  2531. GetFullPathNameW(param2, ARRAY_SIZE(output), output, NULL);
  2532. WINE_TRACE("Move from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
  2533. wine_dbgstr_w(param1), wine_dbgstr_w(output));
  2534. /* Split into components */
  2535. _wsplitpath(input, drive, dir, fname, ext);
  2536. hff = FindFirstFileW(input, &fd);
  2537. if (hff == INVALID_HANDLE_VALUE)
  2538. return;
  2539. do {
  2540. WCHAR dest[MAX_PATH];
  2541. WCHAR src[MAX_PATH];
  2542. DWORD attribs;
  2543. BOOL ok = TRUE;
  2544. DWORD flags = 0;
  2545. WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
  2546. /* Build src & dest name */
  2547. lstrcpyW(src, drive);
  2548. lstrcatW(src, dir);
  2549. /* See if dest is an existing directory */
  2550. attribs = GetFileAttributesW(output);
  2551. if (attribs != INVALID_FILE_ATTRIBUTES &&
  2552. (attribs & FILE_ATTRIBUTE_DIRECTORY)) {
  2553. lstrcpyW(dest, output);
  2554. lstrcatW(dest, L"\\");
  2555. lstrcatW(dest, fd.cFileName);
  2556. } else {
  2557. lstrcpyW(dest, output);
  2558. }
  2559. lstrcatW(src, fd.cFileName);
  2560. WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
  2561. WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
  2562. /* If destination exists, prompt unless /Y supplied */
  2563. if (GetFileAttributesW(dest) != INVALID_FILE_ATTRIBUTES) {
  2564. BOOL force = FALSE;
  2565. WCHAR copycmd[MAXSTRING];
  2566. DWORD len;
  2567. /* Default whether automatic overwriting is on. If we are interactive then
  2568. we prompt by default, otherwise we overwrite by default
  2569. /-Y has the highest priority, then /Y and finally the COPYCMD env. variable */
  2570. if (wcsstr(quals, L"/-Y"))
  2571. force = FALSE;
  2572. else if (wcsstr(quals, L"/Y"))
  2573. force = TRUE;
  2574. else {
  2575. /* By default, we will force the overwrite in batch mode and ask for
  2576. * confirmation in interactive mode. */
  2577. force = !interactive;
  2578. /* If COPYCMD is set, then we force the overwrite with /Y and ask for
  2579. * confirmation with /-Y. If COPYCMD is neither of those, then we use the
  2580. * default behavior. */
  2581. len = GetEnvironmentVariableW(L"COPYCMD", copycmd, ARRAY_SIZE(copycmd));
  2582. if (len && len < ARRAY_SIZE(copycmd)) {
  2583. if (!lstrcmpiW(copycmd, L"/Y"))
  2584. force = TRUE;
  2585. else if (!lstrcmpiW(copycmd, L"/-Y"))
  2586. force = FALSE;
  2587. }
  2588. }
  2589. /* Prompt if overwriting */
  2590. if (!force) {
  2591. WCHAR* question;
  2592. /* Ask for confirmation */
  2593. question = WCMD_format_string(WCMD_LoadMessage(WCMD_OVERWRITE), dest);
  2594. ok = WCMD_ask_confirm(question, FALSE, NULL);
  2595. LocalFree(question);
  2596. }
  2597. if (ok)
  2598. flags |= MOVEFILE_REPLACE_EXISTING;
  2599. }
  2600. if (ok) {
  2601. status = MoveFileExW(src, dest, flags);
  2602. } else {
  2603. status = TRUE;
  2604. }
  2605. if (!status) {
  2606. WCMD_print_error ();
  2607. errorlevel = 1;
  2608. }
  2609. } while (FindNextFileW(hff, &fd) != 0);
  2610. FindClose(hff);
  2611. }
  2612. /****************************************************************************
  2613. * WCMD_pause
  2614. *
  2615. * Suspend execution of a batch script until a key is typed
  2616. */
  2617. void WCMD_pause (void)
  2618. {
  2619. DWORD oldmode;
  2620. BOOL have_console;
  2621. DWORD count;
  2622. WCHAR key;
  2623. HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
  2624. have_console = GetConsoleMode(hIn, &oldmode);
  2625. if (have_console)
  2626. SetConsoleMode(hIn, 0);
  2627. WCMD_output_asis(anykey);
  2628. WCMD_ReadFile(hIn, &key, 1, &count);
  2629. if (have_console)
  2630. SetConsoleMode(hIn, oldmode);
  2631. }
  2632. /****************************************************************************
  2633. * WCMD_remove_dir
  2634. *
  2635. * Delete a directory.
  2636. */
  2637. void WCMD_remove_dir (WCHAR *args) {
  2638. int argno = 0;
  2639. int argsProcessed = 0;
  2640. WCHAR *argN = args;
  2641. /* Loop through all args */
  2642. while (argN) {
  2643. WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
  2644. if (argN && argN[0] != '/') {
  2645. WINE_TRACE("rd: Processing arg %s (quals:%s)\n", wine_dbgstr_w(thisArg),
  2646. wine_dbgstr_w(quals));
  2647. argsProcessed++;
  2648. /* If subdirectory search not supplied, just try to remove
  2649. and report error if it fails (eg if it contains a file) */
  2650. if (wcsstr(quals, L"/S") == NULL) {
  2651. if (!RemoveDirectoryW(thisArg)) WCMD_print_error ();
  2652. /* Otherwise use ShFileOp to recursively remove a directory */
  2653. } else {
  2654. SHFILEOPSTRUCTW lpDir;
  2655. /* Ask first */
  2656. if (wcsstr(quals, L"/Q") == NULL) {
  2657. BOOL ok;
  2658. WCHAR question[MAXSTRING];
  2659. /* Ask for confirmation */
  2660. wsprintfW(question, L"%s ", thisArg);
  2661. ok = WCMD_ask_confirm(question, TRUE, NULL);
  2662. /* Abort if answer is 'N' */
  2663. if (!ok) return;
  2664. }
  2665. /* Do the delete */
  2666. lpDir.hwnd = NULL;
  2667. lpDir.pTo = NULL;
  2668. lpDir.pFrom = thisArg;
  2669. lpDir.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
  2670. lpDir.wFunc = FO_DELETE;
  2671. /* SHFileOperationW needs file list with a double null termination */
  2672. thisArg[lstrlenW(thisArg) + 1] = 0x00;
  2673. if (SHFileOperationW(&lpDir)) WCMD_print_error ();
  2674. }
  2675. }
  2676. }
  2677. /* Handle no valid args */
  2678. if (argsProcessed == 0) {
  2679. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  2680. return;
  2681. }
  2682. }
  2683. /****************************************************************************
  2684. * WCMD_rename
  2685. *
  2686. * Rename a file.
  2687. */
  2688. void WCMD_rename (void)
  2689. {
  2690. BOOL status;
  2691. HANDLE hff;
  2692. WIN32_FIND_DATAW fd;
  2693. WCHAR input[MAX_PATH];
  2694. WCHAR *dotDst = NULL;
  2695. WCHAR drive[10];
  2696. WCHAR dir[MAX_PATH];
  2697. WCHAR fname[MAX_PATH];
  2698. WCHAR ext[MAX_PATH];
  2699. errorlevel = 0;
  2700. /* Must be at least two args */
  2701. if (param1[0] == 0x00 || param2[0] == 0x00) {
  2702. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  2703. errorlevel = 1;
  2704. return;
  2705. }
  2706. /* Destination cannot contain a drive letter or directory separator */
  2707. if ((wcschr(param2,':') != NULL) || (wcschr(param2,'\\') != NULL)) {
  2708. SetLastError(ERROR_INVALID_PARAMETER);
  2709. WCMD_print_error();
  2710. errorlevel = 1;
  2711. return;
  2712. }
  2713. /* Convert partial path to full path */
  2714. GetFullPathNameW(param1, ARRAY_SIZE(input), input, NULL);
  2715. WINE_TRACE("Rename from '%s'('%s') to '%s'\n", wine_dbgstr_w(input),
  2716. wine_dbgstr_w(param1), wine_dbgstr_w(param2));
  2717. dotDst = wcschr(param2, '.');
  2718. /* Split into components */
  2719. _wsplitpath(input, drive, dir, fname, ext);
  2720. hff = FindFirstFileW(input, &fd);
  2721. if (hff == INVALID_HANDLE_VALUE)
  2722. return;
  2723. do {
  2724. WCHAR dest[MAX_PATH];
  2725. WCHAR src[MAX_PATH];
  2726. WCHAR *dotSrc = NULL;
  2727. int dirLen;
  2728. WINE_TRACE("Processing file '%s'\n", wine_dbgstr_w(fd.cFileName));
  2729. /* FIXME: If dest name or extension is *, replace with filename/ext
  2730. part otherwise use supplied name. This supports:
  2731. ren *.fred *.jim
  2732. ren jim.* fred.* etc
  2733. However, windows has a more complex algorithm supporting eg
  2734. ?'s and *'s mid name */
  2735. dotSrc = wcschr(fd.cFileName, '.');
  2736. /* Build src & dest name */
  2737. lstrcpyW(src, drive);
  2738. lstrcatW(src, dir);
  2739. lstrcpyW(dest, src);
  2740. dirLen = lstrlenW(src);
  2741. lstrcatW(src, fd.cFileName);
  2742. /* Build name */
  2743. if (param2[0] == '*') {
  2744. lstrcatW(dest, fd.cFileName);
  2745. if (dotSrc) dest[dirLen + (dotSrc - fd.cFileName)] = 0x00;
  2746. } else {
  2747. lstrcatW(dest, param2);
  2748. if (dotDst) dest[dirLen + (dotDst - param2)] = 0x00;
  2749. }
  2750. /* Build Extension */
  2751. if (dotDst && (*(dotDst+1)=='*')) {
  2752. if (dotSrc) lstrcatW(dest, dotSrc);
  2753. } else if (dotDst) {
  2754. lstrcatW(dest, dotDst);
  2755. }
  2756. WINE_TRACE("Source '%s'\n", wine_dbgstr_w(src));
  2757. WINE_TRACE("Dest '%s'\n", wine_dbgstr_w(dest));
  2758. status = MoveFileW(src, dest);
  2759. if (!status) {
  2760. WCMD_print_error ();
  2761. errorlevel = 1;
  2762. }
  2763. } while (FindNextFileW(hff, &fd) != 0);
  2764. FindClose(hff);
  2765. }
  2766. /*****************************************************************************
  2767. * WCMD_dupenv
  2768. *
  2769. * Make a copy of the environment.
  2770. */
  2771. static WCHAR *WCMD_dupenv( const WCHAR *env )
  2772. {
  2773. WCHAR *env_copy;
  2774. int len;
  2775. if( !env )
  2776. return NULL;
  2777. len = 0;
  2778. while ( env[len] )
  2779. len += (lstrlenW(&env[len]) + 1);
  2780. env_copy = LocalAlloc (LMEM_FIXED, (len+1) * sizeof (WCHAR) );
  2781. if (!env_copy)
  2782. {
  2783. WINE_ERR("out of memory\n");
  2784. return env_copy;
  2785. }
  2786. memcpy (env_copy, env, len*sizeof (WCHAR));
  2787. env_copy[len] = 0;
  2788. return env_copy;
  2789. }
  2790. /*****************************************************************************
  2791. * WCMD_setlocal
  2792. *
  2793. * setlocal pushes the environment onto a stack
  2794. * Save the environment as unicode so we don't screw anything up.
  2795. */
  2796. void WCMD_setlocal (const WCHAR *s) {
  2797. WCHAR *env;
  2798. struct env_stack *env_copy;
  2799. WCHAR cwd[MAX_PATH];
  2800. BOOL newdelay;
  2801. /* setlocal does nothing outside of batch programs */
  2802. if (!context) return;
  2803. /* DISABLEEXTENSIONS ignored */
  2804. /* ENABLEDELAYEDEXPANSION / DISABLEDELAYEDEXPANSION could be parm1 or parm2
  2805. (if both ENABLEEXTENSIONS and ENABLEDELAYEDEXPANSION supplied for example) */
  2806. if (!wcsicmp(param1, L"ENABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"ENABLEDELAYEDEXPANSION")) {
  2807. newdelay = TRUE;
  2808. } else if (!wcsicmp(param1, L"DISABLEDELAYEDEXPANSION") || !wcsicmp(param2, L"DISABLEDELAYEDEXPANSION")) {
  2809. newdelay = FALSE;
  2810. } else {
  2811. newdelay = delayedsubst;
  2812. }
  2813. WINE_TRACE("Setting delayed expansion to %d\n", newdelay);
  2814. env_copy = LocalAlloc (LMEM_FIXED, sizeof (struct env_stack));
  2815. if( !env_copy )
  2816. {
  2817. WINE_ERR ("out of memory\n");
  2818. return;
  2819. }
  2820. env = GetEnvironmentStringsW ();
  2821. env_copy->strings = WCMD_dupenv (env);
  2822. if (env_copy->strings)
  2823. {
  2824. env_copy->batchhandle = context->h;
  2825. env_copy->next = saved_environment;
  2826. env_copy->delayedsubst = delayedsubst;
  2827. delayedsubst = newdelay;
  2828. saved_environment = env_copy;
  2829. /* Save the current drive letter */
  2830. GetCurrentDirectoryW(MAX_PATH, cwd);
  2831. env_copy->u.cwd = cwd[0];
  2832. }
  2833. else
  2834. LocalFree (env_copy);
  2835. FreeEnvironmentStringsW (env);
  2836. }
  2837. /*****************************************************************************
  2838. * WCMD_endlocal
  2839. *
  2840. * endlocal pops the environment off a stack
  2841. * Note: When searching for '=', search from WCHAR position 1, to handle
  2842. * special internal environment variables =C:, =D: etc
  2843. */
  2844. void WCMD_endlocal (void) {
  2845. WCHAR *env, *old, *p;
  2846. struct env_stack *temp;
  2847. int len, n;
  2848. /* setlocal does nothing outside of batch programs */
  2849. if (!context) return;
  2850. /* setlocal needs a saved environment from within the same context (batch
  2851. program) as it was saved in */
  2852. if (!saved_environment || saved_environment->batchhandle != context->h)
  2853. return;
  2854. /* pop the old environment from the stack */
  2855. temp = saved_environment;
  2856. saved_environment = temp->next;
  2857. /* delete the current environment, totally */
  2858. env = GetEnvironmentStringsW ();
  2859. old = WCMD_dupenv (env);
  2860. len = 0;
  2861. while (old[len]) {
  2862. n = lstrlenW(&old[len]) + 1;
  2863. p = wcschr(&old[len] + 1, '=');
  2864. if (p)
  2865. {
  2866. *p++ = 0;
  2867. SetEnvironmentVariableW (&old[len], NULL);
  2868. }
  2869. len += n;
  2870. }
  2871. LocalFree (old);
  2872. FreeEnvironmentStringsW (env);
  2873. /* restore old environment */
  2874. env = temp->strings;
  2875. len = 0;
  2876. delayedsubst = temp->delayedsubst;
  2877. WINE_TRACE("Delayed expansion now %d\n", delayedsubst);
  2878. while (env[len]) {
  2879. n = lstrlenW(&env[len]) + 1;
  2880. p = wcschr(&env[len] + 1, '=');
  2881. if (p)
  2882. {
  2883. *p++ = 0;
  2884. SetEnvironmentVariableW (&env[len], p);
  2885. }
  2886. len += n;
  2887. }
  2888. /* Restore current drive letter */
  2889. if (IsCharAlphaW(temp->u.cwd)) {
  2890. WCHAR envvar[4];
  2891. WCHAR cwd[MAX_PATH];
  2892. wsprintfW(envvar, L"=%c:", temp->u.cwd);
  2893. if (GetEnvironmentVariableW(envvar, cwd, MAX_PATH)) {
  2894. WINE_TRACE("Resetting cwd to %s\n", wine_dbgstr_w(cwd));
  2895. SetCurrentDirectoryW(cwd);
  2896. }
  2897. }
  2898. LocalFree (env);
  2899. LocalFree (temp);
  2900. }
  2901. /*****************************************************************************
  2902. * WCMD_setshow_default
  2903. *
  2904. * Set/Show the current default directory
  2905. */
  2906. void WCMD_setshow_default (const WCHAR *args) {
  2907. BOOL status;
  2908. WCHAR string[1024];
  2909. WCHAR cwd[1024];
  2910. WCHAR *pos;
  2911. WIN32_FIND_DATAW fd;
  2912. HANDLE hff;
  2913. WINE_TRACE("Request change to directory '%s'\n", wine_dbgstr_w(args));
  2914. /* Skip /D and trailing whitespace if on the front of the command line */
  2915. if (lstrlenW(args) >= 2 &&
  2916. CompareStringW(LOCALE_USER_DEFAULT,
  2917. NORM_IGNORECASE | SORT_STRINGSORT,
  2918. args, 2, L"/D", -1) == CSTR_EQUAL) {
  2919. args += 2;
  2920. while (*args && (*args==' ' || *args=='\t'))
  2921. args++;
  2922. }
  2923. GetCurrentDirectoryW(ARRAY_SIZE(cwd), cwd);
  2924. if (!*args) {
  2925. lstrcatW(cwd, L"\r\n");
  2926. WCMD_output_asis (cwd);
  2927. }
  2928. else {
  2929. /* Remove any double quotes, which may be in the
  2930. middle, eg. cd "C:\Program Files"\Microsoft is ok */
  2931. pos = string;
  2932. while (*args) {
  2933. if (*args != '"') *pos++ = *args;
  2934. args++;
  2935. }
  2936. while (pos > string && (*(pos-1) == ' ' || *(pos-1) == '\t'))
  2937. pos--;
  2938. *pos = 0x00;
  2939. /* Search for appropriate directory */
  2940. WINE_TRACE("Looking for directory '%s'\n", wine_dbgstr_w(string));
  2941. hff = FindFirstFileW(string, &fd);
  2942. if (hff != INVALID_HANDLE_VALUE) {
  2943. do {
  2944. if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
  2945. WCHAR fpath[MAX_PATH];
  2946. WCHAR drive[10];
  2947. WCHAR dir[MAX_PATH];
  2948. WCHAR fname[MAX_PATH];
  2949. WCHAR ext[MAX_PATH];
  2950. /* Convert path into actual directory spec */
  2951. GetFullPathNameW(string, ARRAY_SIZE(fpath), fpath, NULL);
  2952. _wsplitpath(fpath, drive, dir, fname, ext);
  2953. /* Rebuild path */
  2954. wsprintfW(string, L"%s%s%s", drive, dir, fd.cFileName);
  2955. break;
  2956. }
  2957. } while (FindNextFileW(hff, &fd) != 0);
  2958. FindClose(hff);
  2959. }
  2960. /* Change to that directory */
  2961. WINE_TRACE("Really changing to directory '%s'\n", wine_dbgstr_w(string));
  2962. status = SetCurrentDirectoryW(string);
  2963. if (!status) {
  2964. errorlevel = 1;
  2965. WCMD_print_error ();
  2966. return;
  2967. } else {
  2968. /* Save away the actual new directory, to store as current location */
  2969. GetCurrentDirectoryW(ARRAY_SIZE(string), string);
  2970. /* Restore old directory if drive letter would change, and
  2971. CD x:\directory /D (or pushd c:\directory) not supplied */
  2972. if ((wcsstr(quals, L"/D") == NULL) &&
  2973. (param1[1] == ':') && (toupper(param1[0]) != toupper(cwd[0]))) {
  2974. SetCurrentDirectoryW(cwd);
  2975. }
  2976. }
  2977. /* Set special =C: type environment variable, for drive letter of
  2978. change of directory, even if path was restored due to missing
  2979. /D (allows changing drive letter when not resident on that
  2980. drive */
  2981. if ((string[1] == ':') && IsCharAlphaW(string[0])) {
  2982. WCHAR env[4];
  2983. lstrcpyW(env, L"=");
  2984. memcpy(env+1, string, 2 * sizeof(WCHAR));
  2985. env[3] = 0x00;
  2986. WINE_TRACE("Setting '%s' to '%s'\n", wine_dbgstr_w(env), wine_dbgstr_w(string));
  2987. SetEnvironmentVariableW(env, string);
  2988. }
  2989. }
  2990. return;
  2991. }
  2992. /****************************************************************************
  2993. * WCMD_setshow_date
  2994. *
  2995. * Set/Show the system date
  2996. * FIXME: Can't change date yet
  2997. */
  2998. void WCMD_setshow_date (void) {
  2999. WCHAR curdate[64], buffer[64];
  3000. DWORD count;
  3001. if (!*param1) {
  3002. if (GetDateFormatW(LOCALE_USER_DEFAULT, 0, NULL, NULL, curdate, ARRAY_SIZE(curdate))) {
  3003. WCMD_output (WCMD_LoadMessage(WCMD_CURRENTDATE), curdate);
  3004. if (wcsstr(quals, L"/T") == NULL) {
  3005. WCMD_output (WCMD_LoadMessage(WCMD_NEWDATE));
  3006. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
  3007. if (count > 2) {
  3008. WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
  3009. }
  3010. }
  3011. }
  3012. else WCMD_print_error ();
  3013. }
  3014. else {
  3015. WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
  3016. }
  3017. }
  3018. /****************************************************************************
  3019. * WCMD_compare
  3020. * Note: Native displays 'fred' before 'fred ', so need to only compare up to
  3021. * the equals sign.
  3022. */
  3023. static int __cdecl WCMD_compare( const void *a, const void *b )
  3024. {
  3025. int r;
  3026. const WCHAR * const *str_a = a, * const *str_b = b;
  3027. r = CompareStringW( LOCALE_USER_DEFAULT, NORM_IGNORECASE | SORT_STRINGSORT,
  3028. *str_a, wcscspn(*str_a, L"="), *str_b, wcscspn(*str_b, L"=") );
  3029. if( r == CSTR_LESS_THAN ) return -1;
  3030. if( r == CSTR_GREATER_THAN ) return 1;
  3031. return 0;
  3032. }
  3033. /****************************************************************************
  3034. * WCMD_setshow_sortenv
  3035. *
  3036. * sort variables into order for display
  3037. * Optionally only display those who start with a stub
  3038. * returns the count displayed
  3039. */
  3040. static int WCMD_setshow_sortenv(const WCHAR *s, const WCHAR *stub)
  3041. {
  3042. UINT count=0, len=0, i, displayedcount=0, stublen=0;
  3043. const WCHAR **str;
  3044. if (stub) stublen = lstrlenW(stub);
  3045. /* count the number of strings, and the total length */
  3046. while ( s[len] ) {
  3047. len += (lstrlenW(&s[len]) + 1);
  3048. count++;
  3049. }
  3050. /* add the strings to an array */
  3051. str = LocalAlloc (LMEM_FIXED | LMEM_ZEROINIT, count * sizeof (WCHAR*) );
  3052. if( !str )
  3053. return 0;
  3054. str[0] = s;
  3055. for( i=1; i<count; i++ )
  3056. str[i] = str[i-1] + lstrlenW(str[i-1]) + 1;
  3057. /* sort the array */
  3058. qsort( str, count, sizeof (WCHAR*), WCMD_compare );
  3059. /* print it */
  3060. for( i=0; i<count; i++ ) {
  3061. if (!stub || CompareStringW(LOCALE_USER_DEFAULT,
  3062. NORM_IGNORECASE | SORT_STRINGSORT,
  3063. str[i], stublen, stub, -1) == CSTR_EQUAL) {
  3064. /* Don't display special internal variables */
  3065. if (str[i][0] != '=') {
  3066. WCMD_output_asis(str[i]);
  3067. WCMD_output_asis(L"\r\n");
  3068. displayedcount++;
  3069. }
  3070. }
  3071. }
  3072. LocalFree( str );
  3073. return displayedcount;
  3074. }
  3075. /****************************************************************************
  3076. * WCMD_getprecedence
  3077. * Return the precedence of a particular operator
  3078. */
  3079. static int WCMD_getprecedence(const WCHAR in)
  3080. {
  3081. switch (in) {
  3082. case '!':
  3083. case '~':
  3084. case OP_POSITIVE:
  3085. case OP_NEGATIVE:
  3086. return 8;
  3087. case '*':
  3088. case '/':
  3089. case '%':
  3090. return 7;
  3091. case '+':
  3092. case '-':
  3093. return 6;
  3094. case '<':
  3095. case '>':
  3096. return 5;
  3097. case '&':
  3098. return 4;
  3099. case '^':
  3100. return 3;
  3101. case '|':
  3102. return 2;
  3103. case '=':
  3104. case OP_ASSSIGNMUL:
  3105. case OP_ASSSIGNDIV:
  3106. case OP_ASSSIGNMOD:
  3107. case OP_ASSSIGNADD:
  3108. case OP_ASSSIGNSUB:
  3109. case OP_ASSSIGNAND:
  3110. case OP_ASSSIGNNOT:
  3111. case OP_ASSSIGNOR:
  3112. case OP_ASSSIGNSHL:
  3113. case OP_ASSSIGNSHR:
  3114. return 1;
  3115. default:
  3116. return 0;
  3117. }
  3118. }
  3119. /****************************************************************************
  3120. * WCMD_pushnumber
  3121. * Push either a number or name (environment variable) onto the supplied
  3122. * stack
  3123. */
  3124. static void WCMD_pushnumber(WCHAR *var, int num, VARSTACK **varstack) {
  3125. VARSTACK *thisstack = heap_xalloc(sizeof(VARSTACK));
  3126. thisstack->isnum = (var == NULL);
  3127. if (var) {
  3128. thisstack->variable = var;
  3129. WINE_TRACE("Pushed variable %s\n", wine_dbgstr_w(var));
  3130. } else {
  3131. thisstack->value = num;
  3132. WINE_TRACE("Pushed number %d\n", num);
  3133. }
  3134. thisstack->next = *varstack;
  3135. *varstack = thisstack;
  3136. }
  3137. /****************************************************************************
  3138. * WCMD_peeknumber
  3139. * Returns the value of the top number or environment variable on the stack
  3140. * and leaves the item on the stack.
  3141. */
  3142. static int WCMD_peeknumber(VARSTACK **varstack) {
  3143. int result = 0;
  3144. VARSTACK *thisvar;
  3145. if (varstack) {
  3146. thisvar = *varstack;
  3147. if (!thisvar->isnum) {
  3148. WCHAR tmpstr[MAXSTRING];
  3149. if (GetEnvironmentVariableW(thisvar->variable, tmpstr, MAXSTRING)) {
  3150. result = wcstol(tmpstr,NULL,0);
  3151. }
  3152. WINE_TRACE("Envvar %s converted to %d\n", wine_dbgstr_w(thisvar->variable), result);
  3153. } else {
  3154. result = thisvar->value;
  3155. }
  3156. }
  3157. WINE_TRACE("Peeked number %d\n", result);
  3158. return result;
  3159. }
  3160. /****************************************************************************
  3161. * WCMD_popnumber
  3162. * Returns the value of the top number or environment variable on the stack
  3163. * and removes the item from the stack.
  3164. */
  3165. static int WCMD_popnumber(VARSTACK **varstack) {
  3166. int result = 0;
  3167. VARSTACK *thisvar;
  3168. if (varstack) {
  3169. thisvar = *varstack;
  3170. result = WCMD_peeknumber(varstack);
  3171. if (!thisvar->isnum) heap_free(thisvar->variable);
  3172. *varstack = thisvar->next;
  3173. heap_free(thisvar);
  3174. }
  3175. WINE_TRACE("Popped number %d\n", result);
  3176. return result;
  3177. }
  3178. /****************************************************************************
  3179. * WCMD_pushoperator
  3180. * Push an operator onto the supplied stack
  3181. */
  3182. static void WCMD_pushoperator(WCHAR op, int precedence, OPSTACK **opstack) {
  3183. OPSTACK *thisstack = heap_xalloc(sizeof(OPSTACK));
  3184. thisstack->precedence = precedence;
  3185. thisstack->op = op;
  3186. thisstack->next = *opstack;
  3187. WINE_TRACE("Pushed operator %c\n", op);
  3188. *opstack = thisstack;
  3189. }
  3190. /****************************************************************************
  3191. * WCMD_popoperator
  3192. * Returns the operator from the top of the stack and removes the item from
  3193. * the stack.
  3194. */
  3195. static WCHAR WCMD_popoperator(OPSTACK **opstack) {
  3196. WCHAR result = 0;
  3197. OPSTACK *thisop;
  3198. if (opstack) {
  3199. thisop = *opstack;
  3200. result = thisop->op;
  3201. *opstack = thisop->next;
  3202. heap_free(thisop);
  3203. }
  3204. WINE_TRACE("Popped operator %c\n", result);
  3205. return result;
  3206. }
  3207. /****************************************************************************
  3208. * WCMD_reduce
  3209. * Actions the top operator on the stack against the first and sometimes
  3210. * second value on the variable stack, and pushes the result
  3211. * Returns non-zero on error.
  3212. */
  3213. static int WCMD_reduce(OPSTACK **opstack, VARSTACK **varstack) {
  3214. WCHAR thisop;
  3215. int var1,var2;
  3216. int rc = 0;
  3217. if (!*opstack || !*varstack) {
  3218. WINE_TRACE("No operators for the reduce\n");
  3219. return WCMD_NOOPERATOR;
  3220. }
  3221. /* Remove the top operator */
  3222. thisop = WCMD_popoperator(opstack);
  3223. WINE_TRACE("Reducing the stacks - processing operator %c\n", thisop);
  3224. /* One variable operators */
  3225. var1 = WCMD_popnumber(varstack);
  3226. switch (thisop) {
  3227. case '!': WCMD_pushnumber(NULL, !var1, varstack);
  3228. break;
  3229. case '~': WCMD_pushnumber(NULL, ~var1, varstack);
  3230. break;
  3231. case OP_POSITIVE: WCMD_pushnumber(NULL, var1, varstack);
  3232. break;
  3233. case OP_NEGATIVE: WCMD_pushnumber(NULL, -var1, varstack);
  3234. break;
  3235. }
  3236. /* Two variable operators */
  3237. if (!*varstack) {
  3238. WINE_TRACE("No operands left for the reduce?\n");
  3239. return WCMD_NOOPERAND;
  3240. }
  3241. switch (thisop) {
  3242. case '!':
  3243. case '~':
  3244. case OP_POSITIVE:
  3245. case OP_NEGATIVE:
  3246. break; /* Handled above */
  3247. case '*': var2 = WCMD_popnumber(varstack);
  3248. WCMD_pushnumber(NULL, var2*var1, varstack);
  3249. break;
  3250. case '/': var2 = WCMD_popnumber(varstack);
  3251. if (var1 == 0) return WCMD_DIVIDEBYZERO;
  3252. WCMD_pushnumber(NULL, var2/var1, varstack);
  3253. break;
  3254. case '+': var2 = WCMD_popnumber(varstack);
  3255. WCMD_pushnumber(NULL, var2+var1, varstack);
  3256. break;
  3257. case '-': var2 = WCMD_popnumber(varstack);
  3258. WCMD_pushnumber(NULL, var2-var1, varstack);
  3259. break;
  3260. case '&': var2 = WCMD_popnumber(varstack);
  3261. WCMD_pushnumber(NULL, var2&var1, varstack);
  3262. break;
  3263. case '%': var2 = WCMD_popnumber(varstack);
  3264. if (var1 == 0) return WCMD_DIVIDEBYZERO;
  3265. WCMD_pushnumber(NULL, var2%var1, varstack);
  3266. break;
  3267. case '^': var2 = WCMD_popnumber(varstack);
  3268. WCMD_pushnumber(NULL, var2^var1, varstack);
  3269. break;
  3270. case '<': var2 = WCMD_popnumber(varstack);
  3271. /* Shift left has to be a positive number, 0-31 otherwise 0 is returned,
  3272. which differs from the compiler (for example gcc) so being explicit. */
  3273. if (var1 < 0 || var1 >= (8 * sizeof(INT))) {
  3274. WCMD_pushnumber(NULL, 0, varstack);
  3275. } else {
  3276. WCMD_pushnumber(NULL, var2<<var1, varstack);
  3277. }
  3278. break;
  3279. case '>': var2 = WCMD_popnumber(varstack);
  3280. WCMD_pushnumber(NULL, var2>>var1, varstack);
  3281. break;
  3282. case '|': var2 = WCMD_popnumber(varstack);
  3283. WCMD_pushnumber(NULL, var2|var1, varstack);
  3284. break;
  3285. case OP_ASSSIGNMUL:
  3286. case OP_ASSSIGNDIV:
  3287. case OP_ASSSIGNMOD:
  3288. case OP_ASSSIGNADD:
  3289. case OP_ASSSIGNSUB:
  3290. case OP_ASSSIGNAND:
  3291. case OP_ASSSIGNNOT:
  3292. case OP_ASSSIGNOR:
  3293. case OP_ASSSIGNSHL:
  3294. case OP_ASSSIGNSHR:
  3295. {
  3296. int i = 0;
  3297. /* The left of an equals must be one variable */
  3298. if (!(*varstack) || (*varstack)->isnum) {
  3299. return WCMD_NOOPERAND;
  3300. }
  3301. /* Make the number stack grow by inserting the value of the variable */
  3302. var2 = WCMD_peeknumber(varstack);
  3303. WCMD_pushnumber(NULL, var2, varstack);
  3304. WCMD_pushnumber(NULL, var1, varstack);
  3305. /* Make the operand stack grow by pushing the assign operator plus the
  3306. operator to perform */
  3307. while (calcassignments[i].op != ' ' &&
  3308. calcassignments[i].calculatedop != thisop) {
  3309. i++;
  3310. }
  3311. if (calcassignments[i].calculatedop == ' ') {
  3312. WINE_ERR("Unexpected operator %c\n", thisop);
  3313. return WCMD_NOOPERATOR;
  3314. }
  3315. WCMD_pushoperator('=', WCMD_getprecedence('='), opstack);
  3316. WCMD_pushoperator(calcassignments[i].op,
  3317. WCMD_getprecedence(calcassignments[i].op), opstack);
  3318. break;
  3319. }
  3320. case '=':
  3321. {
  3322. WCHAR result[MAXSTRING];
  3323. /* Build the result, then push it onto the stack */
  3324. swprintf(result, ARRAY_SIZE(result), L"%d", var1);
  3325. WINE_TRACE("Assigning %s a value %s\n", wine_dbgstr_w((*varstack)->variable),
  3326. wine_dbgstr_w(result));
  3327. SetEnvironmentVariableW((*varstack)->variable, result);
  3328. var2 = WCMD_popnumber(varstack);
  3329. WCMD_pushnumber(NULL, var1, varstack);
  3330. break;
  3331. }
  3332. default: WINE_ERR("Unrecognized operator %c\n", thisop);
  3333. }
  3334. return rc;
  3335. }
  3336. /****************************************************************************
  3337. * WCMD_handleExpression
  3338. * Handles an expression provided to set /a - If it finds brackets, it uses
  3339. * recursion to process the parts in brackets.
  3340. */
  3341. static int WCMD_handleExpression(WCHAR **expr, int *ret, int depth)
  3342. {
  3343. static const WCHAR mathDelims[] = L" \t()!~-*/%+<>&^|=,";
  3344. int rc = 0;
  3345. WCHAR *pos;
  3346. BOOL lastwasnumber = FALSE; /* FALSE makes a minus at the start of the expression easier to handle */
  3347. OPSTACK *opstackhead = NULL;
  3348. VARSTACK *varstackhead = NULL;
  3349. WCHAR foundhalf = 0;
  3350. /* Initialize */
  3351. WINE_TRACE("Handling expression '%s'\n", wine_dbgstr_w(*expr));
  3352. pos = *expr;
  3353. /* Iterate through until whole expression is processed */
  3354. while (pos && *pos) {
  3355. BOOL treatasnumber;
  3356. /* Skip whitespace to get to the next character to process*/
  3357. while (*pos && (*pos==' ' || *pos=='\t')) pos++;
  3358. if (!*pos) goto exprreturn;
  3359. /* If we have found anything other than an operator then it's a number/variable */
  3360. if (wcschr(mathDelims, *pos) == NULL) {
  3361. WCHAR *parmstart, *parm, *dupparm;
  3362. WCHAR *nextpos;
  3363. /* Cannot have an expression with var/number twice, without an operator
  3364. in-between, nor or number following a half constructed << or >> operator */
  3365. if (lastwasnumber || foundhalf) {
  3366. rc = WCMD_NOOPERATOR;
  3367. goto exprerrorreturn;
  3368. }
  3369. lastwasnumber = TRUE;
  3370. if (iswdigit(*pos)) {
  3371. /* For a number - just push it onto the stack */
  3372. int num = wcstoul(pos, &nextpos, 0);
  3373. WCMD_pushnumber(NULL, num, &varstackhead);
  3374. pos = nextpos;
  3375. /* Verify the number was validly formed */
  3376. if (*nextpos && (wcschr(mathDelims, *nextpos) == NULL)) {
  3377. rc = WCMD_BADHEXOCT;
  3378. goto exprerrorreturn;
  3379. }
  3380. } else {
  3381. /* For a variable - just push it onto the stack */
  3382. parm = WCMD_parameter_with_delims(pos, 0, &parmstart, FALSE, FALSE, mathDelims);
  3383. dupparm = heap_strdupW(parm);
  3384. WCMD_pushnumber(dupparm, 0, &varstackhead);
  3385. pos = parmstart + lstrlenW(dupparm);
  3386. }
  3387. continue;
  3388. }
  3389. /* We have found an operator. Some operators are one character, some two, and the minus
  3390. and plus signs need special processing as they can be either operators or just influence
  3391. the parameter which follows them */
  3392. if (foundhalf && (*pos != foundhalf)) {
  3393. /* Badly constructed operator pair */
  3394. rc = WCMD_NOOPERATOR;
  3395. goto exprerrorreturn;
  3396. }
  3397. treatasnumber = FALSE; /* We are processing an operand */
  3398. switch (*pos) {
  3399. /* > and < are special as they are double character operators (and spaces can be between them!)
  3400. If we see these for the first time, set a flag, and second time around we continue.
  3401. Note these double character operators are stored as just one of the characters on the stack */
  3402. case '>':
  3403. case '<': if (!foundhalf) {
  3404. foundhalf = *pos;
  3405. pos++;
  3406. break;
  3407. }
  3408. /* We have found the rest, so clear up the knowledge of the half completed part and
  3409. drop through to normal operator processing */
  3410. foundhalf = 0;
  3411. /* drop through */
  3412. case '=': if (*pos=='=') {
  3413. /* = is special cased as if the last was an operator then we may have e.g. += or
  3414. *= etc which we need to handle by replacing the operator that is on the stack
  3415. with a calculated assignment equivalent */
  3416. if (!lastwasnumber && opstackhead) {
  3417. int i = 0;
  3418. while (calcassignments[i].op != ' ' && calcassignments[i].op != opstackhead->op) {
  3419. i++;
  3420. }
  3421. if (calcassignments[i].op == ' ') {
  3422. rc = WCMD_NOOPERAND;
  3423. goto exprerrorreturn;
  3424. } else {
  3425. /* Remove the operator on the stack, it will be replaced with a ?= equivalent
  3426. when the general operator handling happens further down. */
  3427. *pos = calcassignments[i].calculatedop;
  3428. WCMD_popoperator(&opstackhead);
  3429. }
  3430. }
  3431. }
  3432. /* Drop though */
  3433. /* + and - are slightly special as they can be a numeric prefix, if they follow an operator
  3434. so if they do, convert the +/- (arithmetic) to +/- (numeric prefix for positive/negative) */
  3435. case '+': if (!lastwasnumber && *pos=='+') *pos = OP_POSITIVE;
  3436. /* drop through */
  3437. case '-': if (!lastwasnumber && *pos=='-') *pos = OP_NEGATIVE;
  3438. /* drop through */
  3439. /* Normal operators - push onto stack unless precedence means we have to calculate it now */
  3440. case '!': /* drop through */
  3441. case '~': /* drop through */
  3442. case '/': /* drop through */
  3443. case '%': /* drop through */
  3444. case '&': /* drop through */
  3445. case '^': /* drop through */
  3446. case '*': /* drop through */
  3447. case '|':
  3448. /* General code for handling most of the operators - look at the
  3449. precedence of the top item on the stack, and see if we need to
  3450. action the stack before we push something else onto it. */
  3451. {
  3452. int precedence = WCMD_getprecedence(*pos);
  3453. WINE_TRACE("Found operator %c precedence %d (head is %d)\n", *pos,
  3454. precedence, !opstackhead?-1:opstackhead->precedence);
  3455. /* In general, for things with the same precedence, reduce immediately
  3456. except for assignments and unary operators which do not */
  3457. while (!rc && opstackhead &&
  3458. ((opstackhead->precedence > precedence) ||
  3459. ((opstackhead->precedence == precedence) &&
  3460. (precedence != 1) && (precedence != 8)))) {
  3461. rc = WCMD_reduce(&opstackhead, &varstackhead);
  3462. }
  3463. if (rc) goto exprerrorreturn;
  3464. WCMD_pushoperator(*pos, precedence, &opstackhead);
  3465. pos++;
  3466. break;
  3467. }
  3468. /* comma means start a new expression, ie calculate what we have */
  3469. case ',':
  3470. {
  3471. int prevresult = -1;
  3472. WINE_TRACE("Found expression delimiter - reducing existing stacks\n");
  3473. while (!rc && opstackhead) {
  3474. rc = WCMD_reduce(&opstackhead, &varstackhead);
  3475. }
  3476. if (rc) goto exprerrorreturn;
  3477. /* If we have anything other than one number left, error
  3478. otherwise throw the number away */
  3479. if (!varstackhead || varstackhead->next) {
  3480. rc = WCMD_NOOPERATOR;
  3481. goto exprerrorreturn;
  3482. }
  3483. prevresult = WCMD_popnumber(&varstackhead);
  3484. WINE_TRACE("Expression resolved to %d\n", prevresult);
  3485. heap_free(varstackhead);
  3486. varstackhead = NULL;
  3487. pos++;
  3488. break;
  3489. }
  3490. /* Open bracket - use iteration to parse the inner expression, then continue */
  3491. case '(' : {
  3492. int exprresult = 0;
  3493. pos++;
  3494. rc = WCMD_handleExpression(&pos, &exprresult, depth+1);
  3495. if (rc) goto exprerrorreturn;
  3496. WCMD_pushnumber(NULL, exprresult, &varstackhead);
  3497. break;
  3498. }
  3499. /* Close bracket - we have finished this depth, calculate and return */
  3500. case ')' : {
  3501. pos++;
  3502. treatasnumber = TRUE; /* Things in brackets result in a number */
  3503. if (depth == 0) {
  3504. rc = WCMD_BADPAREN;
  3505. goto exprerrorreturn;
  3506. }
  3507. goto exprreturn;
  3508. }
  3509. default:
  3510. WINE_ERR("Unrecognized operator %c\n", *pos);
  3511. pos++;
  3512. }
  3513. lastwasnumber = treatasnumber;
  3514. }
  3515. exprreturn:
  3516. *expr = pos;
  3517. /* We need to reduce until we have a single number (or variable) on the
  3518. stack and set the return value to that */
  3519. while (!rc && opstackhead) {
  3520. rc = WCMD_reduce(&opstackhead, &varstackhead);
  3521. }
  3522. if (rc) goto exprerrorreturn;
  3523. /* If we have anything other than one number left, error
  3524. otherwise throw the number away */
  3525. if (!varstackhead || varstackhead->next) {
  3526. rc = WCMD_NOOPERATOR;
  3527. goto exprerrorreturn;
  3528. }
  3529. /* Now get the number (and convert if it's just a variable name) */
  3530. *ret = WCMD_popnumber(&varstackhead);
  3531. exprerrorreturn:
  3532. /* Free all remaining memory */
  3533. while (opstackhead) WCMD_popoperator(&opstackhead);
  3534. while (varstackhead) WCMD_popnumber(&varstackhead);
  3535. WINE_TRACE("Returning result %d, rc %d\n", *ret, rc);
  3536. return rc;
  3537. }
  3538. /****************************************************************************
  3539. * WCMD_setshow_env
  3540. *
  3541. * Set/Show the environment variables
  3542. */
  3543. void WCMD_setshow_env (WCHAR *s) {
  3544. LPVOID env;
  3545. WCHAR *p;
  3546. BOOL status;
  3547. WCHAR string[MAXSTRING];
  3548. if (param1[0] == 0x00 && quals[0] == 0x00) {
  3549. env = GetEnvironmentStringsW();
  3550. WCMD_setshow_sortenv( env, NULL );
  3551. return;
  3552. }
  3553. /* See if /P supplied, and if so echo the prompt, and read in a reply */
  3554. if (CompareStringW(LOCALE_USER_DEFAULT,
  3555. NORM_IGNORECASE | SORT_STRINGSORT,
  3556. s, 2, L"/P", -1) == CSTR_EQUAL) {
  3557. DWORD count;
  3558. s += 2;
  3559. while (*s && (*s==' ' || *s=='\t')) s++;
  3560. /* set /P "var=value"jim ignores anything after the last quote */
  3561. if (*s=='\"') {
  3562. WCHAR *lastquote;
  3563. lastquote = WCMD_strip_quotes(s);
  3564. if (lastquote) *lastquote = 0x00;
  3565. WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
  3566. }
  3567. /* If no parameter, or no '=' sign, return an error */
  3568. if (!(*s) || ((p = wcschr (s, '=')) == NULL )) {
  3569. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  3570. return;
  3571. }
  3572. /* Output the prompt */
  3573. *p++ = '\0';
  3574. if (*p) WCMD_output_asis(p);
  3575. /* Read the reply */
  3576. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
  3577. if (count > 1) {
  3578. string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
  3579. if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
  3580. WINE_TRACE("set /p: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
  3581. wine_dbgstr_w(string));
  3582. SetEnvironmentVariableW(s, string);
  3583. }
  3584. /* See if /A supplied, and if so calculate the results of all the expressions */
  3585. } else if (CompareStringW(LOCALE_USER_DEFAULT,
  3586. NORM_IGNORECASE | SORT_STRINGSORT,
  3587. s, 2, L"/A", -1) == CSTR_EQUAL) {
  3588. /* /A supplied, so evaluate expressions and set variables appropriately */
  3589. /* Syntax is set /a var=1,var2=var+4 etc, and it echos back the result */
  3590. /* of the final computation */
  3591. int result = 0;
  3592. int rc = 0;
  3593. WCHAR *thisexpr;
  3594. WCHAR *src,*dst;
  3595. /* Remove all quotes before doing any calculations */
  3596. thisexpr = heap_xalloc((lstrlenW(s+2)+1) * sizeof(WCHAR));
  3597. src = s+2;
  3598. dst = thisexpr;
  3599. while (*src) {
  3600. if (*src != '"') *dst++ = *src;
  3601. src++;
  3602. }
  3603. *dst = 0;
  3604. /* Now calculate the results of the expression */
  3605. src = thisexpr;
  3606. rc = WCMD_handleExpression(&src, &result, 0);
  3607. heap_free(thisexpr);
  3608. /* If parsing failed, issue the error message */
  3609. if (rc > 0) {
  3610. WCMD_output_stderr(WCMD_LoadMessage(rc));
  3611. return;
  3612. }
  3613. /* If we have no context (interactive or cmd.exe /c) print the final result */
  3614. if (!context) {
  3615. swprintf(string, ARRAY_SIZE(string), L"%d", result);
  3616. WCMD_output_asis(string);
  3617. }
  3618. } else {
  3619. DWORD gle;
  3620. /* set "var=value"jim ignores anything after the last quote */
  3621. if (*s=='\"') {
  3622. WCHAR *lastquote;
  3623. lastquote = WCMD_strip_quotes(s);
  3624. if (lastquote) *lastquote = 0x00;
  3625. WINE_TRACE("set: Stripped command line '%s'\n", wine_dbgstr_w(s));
  3626. }
  3627. p = wcschr (s, '=');
  3628. if (p == NULL) {
  3629. env = GetEnvironmentStringsW();
  3630. if (WCMD_setshow_sortenv( env, s ) == 0) {
  3631. WCMD_output_stderr(WCMD_LoadMessage(WCMD_MISSINGENV), s);
  3632. errorlevel = 1;
  3633. }
  3634. return;
  3635. }
  3636. *p++ = '\0';
  3637. if (!*p) p = NULL;
  3638. WINE_TRACE("set: Setting var '%s' to '%s'\n", wine_dbgstr_w(s),
  3639. wine_dbgstr_w(p));
  3640. status = SetEnvironmentVariableW(s, p);
  3641. gle = GetLastError();
  3642. if ((!status) & (gle == ERROR_ENVVAR_NOT_FOUND)) {
  3643. errorlevel = 1;
  3644. } else if (!status) WCMD_print_error();
  3645. else if (!interactive) errorlevel = 0;
  3646. }
  3647. }
  3648. /****************************************************************************
  3649. * WCMD_setshow_path
  3650. *
  3651. * Set/Show the path environment variable
  3652. */
  3653. void WCMD_setshow_path (const WCHAR *args) {
  3654. WCHAR string[1024];
  3655. DWORD status;
  3656. if (!*param1 && !*param2) {
  3657. status = GetEnvironmentVariableW(L"PATH", string, ARRAY_SIZE(string));
  3658. if (status != 0) {
  3659. WCMD_output_asis(L"PATH=");
  3660. WCMD_output_asis ( string);
  3661. WCMD_output_asis(L"\r\n");
  3662. }
  3663. else {
  3664. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOPATH));
  3665. }
  3666. }
  3667. else {
  3668. if (*args == '=') args++; /* Skip leading '=' */
  3669. status = SetEnvironmentVariableW(L"PATH", args);
  3670. if (!status) WCMD_print_error();
  3671. }
  3672. }
  3673. /****************************************************************************
  3674. * WCMD_setshow_prompt
  3675. *
  3676. * Set or show the command prompt.
  3677. */
  3678. void WCMD_setshow_prompt (void) {
  3679. WCHAR *s;
  3680. if (!*param1) {
  3681. SetEnvironmentVariableW(L"PROMPT", NULL);
  3682. }
  3683. else {
  3684. s = param1;
  3685. while ((*s == '=') || (*s == ' ') || (*s == '\t')) s++;
  3686. if (!*s) {
  3687. SetEnvironmentVariableW(L"PROMPT", NULL);
  3688. }
  3689. else SetEnvironmentVariableW(L"PROMPT", s);
  3690. }
  3691. }
  3692. /****************************************************************************
  3693. * WCMD_setshow_time
  3694. *
  3695. * Set/Show the system time
  3696. * FIXME: Can't change time yet
  3697. */
  3698. void WCMD_setshow_time (void) {
  3699. WCHAR curtime[64], buffer[64];
  3700. DWORD count;
  3701. SYSTEMTIME st;
  3702. if (!*param1) {
  3703. GetLocalTime(&st);
  3704. if (GetTimeFormatW(LOCALE_USER_DEFAULT, 0, &st, NULL, curtime, ARRAY_SIZE(curtime))) {
  3705. WCMD_output (WCMD_LoadMessage(WCMD_CURRENTTIME), curtime);
  3706. if (wcsstr(quals, L"/T") == NULL) {
  3707. WCMD_output (WCMD_LoadMessage(WCMD_NEWTIME));
  3708. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
  3709. if (count > 2) {
  3710. WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
  3711. }
  3712. }
  3713. }
  3714. else WCMD_print_error ();
  3715. }
  3716. else {
  3717. WCMD_output_stderr (WCMD_LoadMessage(WCMD_NYI));
  3718. }
  3719. }
  3720. /****************************************************************************
  3721. * WCMD_shift
  3722. *
  3723. * Shift batch parameters.
  3724. * Optional /n says where to start shifting (n=0-8)
  3725. */
  3726. void WCMD_shift (const WCHAR *args) {
  3727. int start;
  3728. if (context != NULL) {
  3729. WCHAR *pos = wcschr(args, '/');
  3730. int i;
  3731. if (pos == NULL) {
  3732. start = 0;
  3733. } else if (*(pos+1)>='0' && *(pos+1)<='8') {
  3734. start = (*(pos+1) - '0');
  3735. } else {
  3736. SetLastError(ERROR_INVALID_PARAMETER);
  3737. WCMD_print_error();
  3738. return;
  3739. }
  3740. WINE_TRACE("Shifting variables, starting at %d\n", start);
  3741. for (i=start;i<=8;i++) {
  3742. context -> shift_count[i] = context -> shift_count[i+1] + 1;
  3743. }
  3744. context -> shift_count[9] = context -> shift_count[9] + 1;
  3745. }
  3746. }
  3747. /****************************************************************************
  3748. * WCMD_start
  3749. */
  3750. void WCMD_start(WCHAR *args)
  3751. {
  3752. int argno;
  3753. int have_title;
  3754. WCHAR file[MAX_PATH];
  3755. WCHAR *cmdline, *cmdline_params;
  3756. STARTUPINFOW st;
  3757. PROCESS_INFORMATION pi;
  3758. GetSystemDirectoryW( file, MAX_PATH );
  3759. lstrcatW(file, L"\\start.exe");
  3760. cmdline = heap_xalloc( (lstrlenW(file) + lstrlenW(args) + 8) * sizeof(WCHAR) );
  3761. lstrcpyW( cmdline, file );
  3762. lstrcatW(cmdline, L" ");
  3763. cmdline_params = cmdline + lstrlenW(cmdline);
  3764. /* The start built-in has some special command-line parsing properties
  3765. * which will be outlined here.
  3766. *
  3767. * both '\t' and ' ' are argument separators
  3768. * '/' has a special double role as both separator and switch prefix, e.g.
  3769. *
  3770. * > start /low/i
  3771. * or
  3772. * > start "title"/i
  3773. *
  3774. * are valid ways to pass multiple options to start. In the latter case
  3775. * '/i' is not a part of the title but parsed as a switch.
  3776. *
  3777. * However, '=', ';' and ',' are not separators:
  3778. * > start "deus"=ex,machina
  3779. *
  3780. * will in fact open a console titled 'deus=ex,machina'
  3781. *
  3782. * The title argument parsing code is only interested in quotes themselves,
  3783. * it does not respect escaping of any kind and all quotes are dropped
  3784. * from the resulting title, therefore:
  3785. *
  3786. * > start "\"" hello"/low
  3787. *
  3788. * actually opens a console titled '\ hello' with low priorities.
  3789. *
  3790. * To not break compatibility with wine programs relying on
  3791. * wine's separate 'start.exe', this program's peculiar console
  3792. * title parsing is actually implemented in 'cmd.exe' which is the
  3793. * application native Windows programs will use to invoke 'start'.
  3794. *
  3795. * WCMD_parameter_with_delims will take care of everything for us.
  3796. */
  3797. have_title = FALSE;
  3798. for (argno=0; ; argno++) {
  3799. WCHAR *thisArg, *argN;
  3800. argN = NULL;
  3801. thisArg = WCMD_parameter_with_delims(args, argno, &argN, FALSE, FALSE, L" \t/");
  3802. /* No more parameters */
  3803. if (!argN)
  3804. break;
  3805. /* Found the title */
  3806. if (argN[0] == '"') {
  3807. TRACE("detected console title: %s\n", wine_dbgstr_w(thisArg));
  3808. have_title = TRUE;
  3809. /* Copy all of the cmdline processed */
  3810. memcpy(cmdline_params, args, sizeof(WCHAR) * (argN - args));
  3811. cmdline_params[argN - args] = '\0';
  3812. /* Add quoted title */
  3813. lstrcatW(cmdline_params, L"\"\\\"");
  3814. lstrcatW(cmdline_params, thisArg);
  3815. lstrcatW(cmdline_params, L"\\\"\"");
  3816. /* Concatenate remaining command-line */
  3817. thisArg = WCMD_parameter_with_delims(args, argno, &argN, TRUE, FALSE, L" \t/");
  3818. lstrcatW(cmdline_params, argN + lstrlenW(thisArg));
  3819. break;
  3820. }
  3821. /* Skipping a regular argument? */
  3822. else if (argN != args && argN[-1] == '/') {
  3823. continue;
  3824. /* Not an argument nor the title, start of program arguments,
  3825. * stop looking for title.
  3826. */
  3827. } else
  3828. break;
  3829. }
  3830. /* build command-line if not built yet */
  3831. if (!have_title) {
  3832. lstrcatW( cmdline, args );
  3833. }
  3834. memset( &st, 0, sizeof(STARTUPINFOW) );
  3835. st.cb = sizeof(STARTUPINFOW);
  3836. if (CreateProcessW( file, cmdline, NULL, NULL, TRUE, 0, NULL, NULL, &st, &pi ))
  3837. {
  3838. WaitForSingleObject( pi.hProcess, INFINITE );
  3839. GetExitCodeProcess( pi.hProcess, &errorlevel );
  3840. if (errorlevel == STILL_ACTIVE) errorlevel = 0;
  3841. CloseHandle(pi.hProcess);
  3842. CloseHandle(pi.hThread);
  3843. }
  3844. else
  3845. {
  3846. SetLastError(ERROR_FILE_NOT_FOUND);
  3847. WCMD_print_error ();
  3848. errorlevel = 9009;
  3849. }
  3850. heap_free(cmdline);
  3851. }
  3852. /****************************************************************************
  3853. * WCMD_title
  3854. *
  3855. * Set the console title
  3856. */
  3857. void WCMD_title (const WCHAR *args) {
  3858. SetConsoleTitleW(args);
  3859. }
  3860. /****************************************************************************
  3861. * WCMD_type
  3862. *
  3863. * Copy a file to standard output.
  3864. */
  3865. void WCMD_type (WCHAR *args) {
  3866. int argno = 0;
  3867. WCHAR *argN = args;
  3868. BOOL writeHeaders = FALSE;
  3869. if (param1[0] == 0x00) {
  3870. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  3871. return;
  3872. }
  3873. if (param2[0] != 0x00) writeHeaders = TRUE;
  3874. /* Loop through all args */
  3875. errorlevel = 0;
  3876. while (argN) {
  3877. WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
  3878. HANDLE h;
  3879. WCHAR buffer[512];
  3880. DWORD count;
  3881. if (!argN) break;
  3882. WINE_TRACE("type: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
  3883. h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
  3884. FILE_ATTRIBUTE_NORMAL, NULL);
  3885. if (h == INVALID_HANDLE_VALUE) {
  3886. WCMD_print_error ();
  3887. WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
  3888. errorlevel = 1;
  3889. } else {
  3890. if (writeHeaders) {
  3891. WCMD_output_stderr(L"\n%1\n\n\n", thisArg);
  3892. }
  3893. while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer) - 1, &count)) {
  3894. if (count == 0) break; /* ReadFile reports success on EOF! */
  3895. buffer[count] = 0;
  3896. WCMD_output_asis (buffer);
  3897. }
  3898. CloseHandle (h);
  3899. }
  3900. }
  3901. }
  3902. /****************************************************************************
  3903. * WCMD_more
  3904. *
  3905. * Output either a file or stdin to screen in pages
  3906. */
  3907. void WCMD_more (WCHAR *args) {
  3908. int argno = 0;
  3909. WCHAR *argN = args;
  3910. WCHAR moreStr[100];
  3911. WCHAR moreStrPage[100];
  3912. WCHAR buffer[512];
  3913. DWORD count;
  3914. /* Prefix the NLS more with '-- ', then load the text */
  3915. errorlevel = 0;
  3916. lstrcpyW(moreStr, L"-- ");
  3917. LoadStringW(hinst, WCMD_MORESTR, &moreStr[3], ARRAY_SIZE(moreStr)-3);
  3918. if (param1[0] == 0x00) {
  3919. /* Wine implements pipes via temporary files, and hence stdin is
  3920. effectively reading from the file. This means the prompts for
  3921. more are satisfied by the next line from the input (file). To
  3922. avoid this, ensure stdin is to the console */
  3923. HANDLE hstdin = GetStdHandle(STD_INPUT_HANDLE);
  3924. HANDLE hConIn = CreateFileW(L"CONIN$", GENERIC_READ | GENERIC_WRITE,
  3925. FILE_SHARE_READ, NULL, OPEN_EXISTING,
  3926. FILE_ATTRIBUTE_NORMAL, 0);
  3927. WINE_TRACE("No parms - working probably in pipe mode\n");
  3928. SetStdHandle(STD_INPUT_HANDLE, hConIn);
  3929. /* Warning: No easy way of ending the stream (ctrl+z on windows) so
  3930. once you get in this bit unless due to a pipe, it's going to end badly... */
  3931. wsprintfW(moreStrPage, L"%s --\n", moreStr);
  3932. WCMD_enter_paged_mode(moreStrPage);
  3933. while (WCMD_ReadFile(hstdin, buffer, ARRAY_SIZE(buffer)-1, &count)) {
  3934. if (count == 0) break; /* ReadFile reports success on EOF! */
  3935. buffer[count] = 0;
  3936. WCMD_output_asis (buffer);
  3937. }
  3938. WCMD_leave_paged_mode();
  3939. /* Restore stdin to what it was */
  3940. SetStdHandle(STD_INPUT_HANDLE, hstdin);
  3941. CloseHandle(hConIn);
  3942. return;
  3943. } else {
  3944. BOOL needsPause = FALSE;
  3945. /* Loop through all args */
  3946. WINE_TRACE("Parms supplied - working through each file\n");
  3947. WCMD_enter_paged_mode(moreStrPage);
  3948. while (argN) {
  3949. WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
  3950. HANDLE h;
  3951. if (!argN) break;
  3952. if (needsPause) {
  3953. /* Wait */
  3954. wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, 100);
  3955. WCMD_leave_paged_mode();
  3956. WCMD_output_asis(moreStrPage);
  3957. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), buffer, ARRAY_SIZE(buffer), &count);
  3958. WCMD_enter_paged_mode(moreStrPage);
  3959. }
  3960. WINE_TRACE("more: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
  3961. h = CreateFileW(thisArg, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
  3962. FILE_ATTRIBUTE_NORMAL, NULL);
  3963. if (h == INVALID_HANDLE_VALUE) {
  3964. WCMD_print_error ();
  3965. WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), thisArg);
  3966. errorlevel = 1;
  3967. } else {
  3968. ULONG64 curPos = 0;
  3969. ULONG64 fileLen = 0;
  3970. WIN32_FILE_ATTRIBUTE_DATA fileInfo;
  3971. /* Get the file size */
  3972. GetFileAttributesExW(thisArg, GetFileExInfoStandard, (void*)&fileInfo);
  3973. fileLen = (((ULONG64)fileInfo.nFileSizeHigh) << 32) + fileInfo.nFileSizeLow;
  3974. needsPause = TRUE;
  3975. while (WCMD_ReadFile(h, buffer, ARRAY_SIZE(buffer)-1, &count)) {
  3976. if (count == 0) break; /* ReadFile reports success on EOF! */
  3977. buffer[count] = 0;
  3978. curPos += count;
  3979. /* Update % count (would be used in WCMD_output_asis as prompt) */
  3980. wsprintfW(moreStrPage, L"%s (%2.2d%%) --\n", moreStr, (int) min(99, (curPos * 100)/fileLen));
  3981. WCMD_output_asis (buffer);
  3982. }
  3983. CloseHandle (h);
  3984. }
  3985. }
  3986. WCMD_leave_paged_mode();
  3987. }
  3988. }
  3989. /****************************************************************************
  3990. * WCMD_verify
  3991. *
  3992. * Display verify flag.
  3993. * FIXME: We don't actually do anything with the verify flag other than toggle
  3994. * it...
  3995. */
  3996. void WCMD_verify (const WCHAR *args) {
  3997. int count;
  3998. count = lstrlenW(args);
  3999. if (count == 0) {
  4000. if (verify_mode) WCMD_output(WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"ON");
  4001. else WCMD_output (WCMD_LoadMessage(WCMD_VERIFYPROMPT), L"OFF");
  4002. return;
  4003. }
  4004. if (lstrcmpiW(args, L"ON") == 0) {
  4005. verify_mode = TRUE;
  4006. return;
  4007. }
  4008. else if (lstrcmpiW(args, L"OFF") == 0) {
  4009. verify_mode = FALSE;
  4010. return;
  4011. }
  4012. else WCMD_output_stderr(WCMD_LoadMessage(WCMD_VERIFYERR));
  4013. }
  4014. /****************************************************************************
  4015. * WCMD_version
  4016. *
  4017. * Display version info.
  4018. */
  4019. void WCMD_version (void) {
  4020. WCMD_output_asis (version_string);
  4021. }
  4022. /****************************************************************************
  4023. * WCMD_volume
  4024. *
  4025. * Display volume information (set_label = FALSE)
  4026. * Additionally set volume label (set_label = TRUE)
  4027. * Returns 1 on success, 0 otherwise
  4028. */
  4029. int WCMD_volume(BOOL set_label, const WCHAR *path)
  4030. {
  4031. DWORD count, serial;
  4032. WCHAR string[MAX_PATH], label[MAX_PATH], curdir[MAX_PATH];
  4033. BOOL status;
  4034. if (!*path) {
  4035. status = GetCurrentDirectoryW(ARRAY_SIZE(curdir), curdir);
  4036. if (!status) {
  4037. WCMD_print_error ();
  4038. return 0;
  4039. }
  4040. status = GetVolumeInformationW(NULL, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
  4041. }
  4042. else {
  4043. if ((path[1] != ':') || (lstrlenW(path) != 2)) {
  4044. WCMD_output_stderr(WCMD_LoadMessage(WCMD_SYNTAXERR));
  4045. return 0;
  4046. }
  4047. wsprintfW (curdir, L"%s\\", path);
  4048. status = GetVolumeInformationW(curdir, label, ARRAY_SIZE(label), &serial, NULL, NULL, NULL, 0);
  4049. }
  4050. if (!status) {
  4051. WCMD_print_error ();
  4052. return 0;
  4053. }
  4054. if (label[0] != '\0') {
  4055. WCMD_output (WCMD_LoadMessage(WCMD_VOLUMELABEL),
  4056. curdir[0], label);
  4057. }
  4058. else {
  4059. WCMD_output (WCMD_LoadMessage(WCMD_VOLUMENOLABEL),
  4060. curdir[0]);
  4061. }
  4062. WCMD_output (WCMD_LoadMessage(WCMD_VOLUMESERIALNO),
  4063. HIWORD(serial), LOWORD(serial));
  4064. if (set_label) {
  4065. WCMD_output (WCMD_LoadMessage(WCMD_VOLUMEPROMPT));
  4066. WCMD_ReadFile(GetStdHandle(STD_INPUT_HANDLE), string, ARRAY_SIZE(string), &count);
  4067. if (count > 1) {
  4068. string[count-1] = '\0'; /* ReadFile output is not null-terminated! */
  4069. if (string[count-2] == '\r') string[count-2] = '\0'; /* Under Windoze we get CRLF! */
  4070. }
  4071. if (*path) {
  4072. if (!SetVolumeLabelW(curdir, string)) WCMD_print_error ();
  4073. }
  4074. else {
  4075. if (!SetVolumeLabelW(NULL, string)) WCMD_print_error ();
  4076. }
  4077. }
  4078. return 1;
  4079. }
  4080. /**************************************************************************
  4081. * WCMD_exit
  4082. *
  4083. * Exit either the process, or just this batch program
  4084. *
  4085. */
  4086. void WCMD_exit (CMD_LIST **cmdList) {
  4087. int rc = wcstol(param1, NULL, 10); /* Note: wcstol of empty parameter is 0 */
  4088. if (context && lstrcmpiW(quals, L"/B") == 0) {
  4089. errorlevel = rc;
  4090. context -> skip_rest = TRUE;
  4091. *cmdList = NULL;
  4092. } else {
  4093. ExitProcess(rc);
  4094. }
  4095. }
  4096. /*****************************************************************************
  4097. * WCMD_assoc
  4098. *
  4099. * Lists or sets file associations (assoc = TRUE)
  4100. * Lists or sets file types (assoc = FALSE)
  4101. */
  4102. void WCMD_assoc (const WCHAR *args, BOOL assoc) {
  4103. HKEY key;
  4104. DWORD accessOptions = KEY_READ;
  4105. WCHAR *newValue;
  4106. LONG rc = ERROR_SUCCESS;
  4107. WCHAR keyValue[MAXSTRING];
  4108. DWORD valueLen;
  4109. HKEY readKey;
  4110. /* See if parameter includes '=' */
  4111. errorlevel = 0;
  4112. newValue = wcschr(args, '=');
  4113. if (newValue) accessOptions |= KEY_WRITE;
  4114. /* Open a key to HKEY_CLASSES_ROOT for enumerating */
  4115. if (RegOpenKeyExW(HKEY_CLASSES_ROOT, L"", 0, accessOptions, &key) != ERROR_SUCCESS) {
  4116. WINE_FIXME("Unexpected failure opening HKCR key: %d\n", GetLastError());
  4117. return;
  4118. }
  4119. /* If no parameters then list all associations */
  4120. if (*args == 0x00) {
  4121. int index = 0;
  4122. /* Enumerate all the keys */
  4123. while (rc != ERROR_NO_MORE_ITEMS) {
  4124. WCHAR keyName[MAXSTRING];
  4125. DWORD nameLen;
  4126. /* Find the next value */
  4127. nameLen = MAXSTRING;
  4128. rc = RegEnumKeyExW(key, index++, keyName, &nameLen, NULL, NULL, NULL, NULL);
  4129. if (rc == ERROR_SUCCESS) {
  4130. /* Only interested in extension ones if assoc, or others
  4131. if not assoc */
  4132. if ((keyName[0] == '.' && assoc) ||
  4133. (!(keyName[0] == '.') && (!assoc)))
  4134. {
  4135. WCHAR subkey[MAXSTRING];
  4136. lstrcpyW(subkey, keyName);
  4137. if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
  4138. if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
  4139. valueLen = sizeof(keyValue);
  4140. rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
  4141. WCMD_output_asis(keyName);
  4142. WCMD_output_asis(L"=");
  4143. /* If no default value found, leave line empty after '=' */
  4144. if (rc == ERROR_SUCCESS) {
  4145. WCMD_output_asis(keyValue);
  4146. }
  4147. WCMD_output_asis(L"\r\n");
  4148. RegCloseKey(readKey);
  4149. }
  4150. }
  4151. }
  4152. }
  4153. } else {
  4154. /* Parameter supplied - if no '=' on command line, it's a query */
  4155. if (newValue == NULL) {
  4156. WCHAR *space;
  4157. WCHAR subkey[MAXSTRING];
  4158. /* Query terminates the parameter at the first space */
  4159. lstrcpyW(keyValue, args);
  4160. space = wcschr(keyValue, ' ');
  4161. if (space) *space=0x00;
  4162. /* Set up key name */
  4163. lstrcpyW(subkey, keyValue);
  4164. if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
  4165. if (RegOpenKeyExW(key, subkey, 0, accessOptions, &readKey) == ERROR_SUCCESS) {
  4166. valueLen = sizeof(keyValue);
  4167. rc = RegQueryValueExW(readKey, NULL, NULL, NULL, (LPBYTE)keyValue, &valueLen);
  4168. WCMD_output_asis(args);
  4169. WCMD_output_asis(L"=");
  4170. /* If no default value found, leave line empty after '=' */
  4171. if (rc == ERROR_SUCCESS) WCMD_output_asis(keyValue);
  4172. WCMD_output_asis(L"\r\n");
  4173. RegCloseKey(readKey);
  4174. } else {
  4175. WCHAR msgbuffer[MAXSTRING];
  4176. /* Load the translated 'File association not found' */
  4177. if (assoc) {
  4178. LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
  4179. } else {
  4180. LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
  4181. }
  4182. WCMD_output_stderr(msgbuffer, keyValue);
  4183. errorlevel = 2;
  4184. }
  4185. /* Not a query - it's a set or clear of a value */
  4186. } else {
  4187. WCHAR subkey[MAXSTRING];
  4188. /* Get pointer to new value */
  4189. *newValue = 0x00;
  4190. newValue++;
  4191. /* Set up key name */
  4192. lstrcpyW(subkey, args);
  4193. if (!assoc) lstrcatW(subkey, L"\\Shell\\Open\\Command");
  4194. /* If nothing after '=' then clear value - only valid for ASSOC */
  4195. if (*newValue == 0x00) {
  4196. if (assoc) rc = RegDeleteKeyW(key, args);
  4197. if (assoc && rc == ERROR_SUCCESS) {
  4198. WINE_TRACE("HKCR Key '%s' deleted\n", wine_dbgstr_w(args));
  4199. } else if (assoc && rc != ERROR_FILE_NOT_FOUND) {
  4200. WCMD_print_error();
  4201. errorlevel = 2;
  4202. } else {
  4203. WCHAR msgbuffer[MAXSTRING];
  4204. /* Load the translated 'File association not found' */
  4205. if (assoc) {
  4206. LoadStringW(hinst, WCMD_NOASSOC, msgbuffer, ARRAY_SIZE(msgbuffer));
  4207. } else {
  4208. LoadStringW(hinst, WCMD_NOFTYPE, msgbuffer, ARRAY_SIZE(msgbuffer));
  4209. }
  4210. WCMD_output_stderr(msgbuffer, args);
  4211. errorlevel = 2;
  4212. }
  4213. /* It really is a set value = contents */
  4214. } else {
  4215. rc = RegCreateKeyExW(key, subkey, 0, NULL, REG_OPTION_NON_VOLATILE,
  4216. accessOptions, NULL, &readKey, NULL);
  4217. if (rc == ERROR_SUCCESS) {
  4218. rc = RegSetValueExW(readKey, NULL, 0, REG_SZ,
  4219. (LPBYTE)newValue,
  4220. sizeof(WCHAR) * (lstrlenW(newValue) + 1));
  4221. RegCloseKey(readKey);
  4222. }
  4223. if (rc != ERROR_SUCCESS) {
  4224. WCMD_print_error();
  4225. errorlevel = 2;
  4226. } else {
  4227. WCMD_output_asis(args);
  4228. WCMD_output_asis(L"=");
  4229. WCMD_output_asis(newValue);
  4230. WCMD_output_asis(L"\r\n");
  4231. }
  4232. }
  4233. }
  4234. }
  4235. /* Clean up */
  4236. RegCloseKey(key);
  4237. }
  4238. /****************************************************************************
  4239. * WCMD_color
  4240. *
  4241. * Colors the terminal screen.
  4242. */
  4243. void WCMD_color (void) {
  4244. CONSOLE_SCREEN_BUFFER_INFO consoleInfo;
  4245. HANDLE hStdOut = GetStdHandle(STD_OUTPUT_HANDLE);
  4246. if (param1[0] != 0x00 && lstrlenW(param1) > 2) {
  4247. WCMD_output_stderr(WCMD_LoadMessage(WCMD_ARGERR));
  4248. return;
  4249. }
  4250. if (GetConsoleScreenBufferInfo(hStdOut, &consoleInfo))
  4251. {
  4252. COORD topLeft;
  4253. DWORD screenSize;
  4254. DWORD color = 0;
  4255. screenSize = consoleInfo.dwSize.X * (consoleInfo.dwSize.Y + 1);
  4256. topLeft.X = 0;
  4257. topLeft.Y = 0;
  4258. /* Convert the color hex digits */
  4259. if (param1[0] == 0x00) {
  4260. color = defaultColor;
  4261. } else {
  4262. color = wcstoul(param1, NULL, 16);
  4263. }
  4264. /* Fail if fg == bg color */
  4265. if (((color & 0xF0) >> 4) == (color & 0x0F)) {
  4266. errorlevel = 1;
  4267. return;
  4268. }
  4269. /* Set the current screen contents and ensure all future writes
  4270. remain this color */
  4271. FillConsoleOutputAttribute(hStdOut, color, screenSize, topLeft, &screenSize);
  4272. SetConsoleTextAttribute(hStdOut, color);
  4273. }
  4274. }
  4275. /****************************************************************************
  4276. * WCMD_mklink
  4277. */
  4278. void WCMD_mklink(WCHAR *args)
  4279. {
  4280. int argno = 0;
  4281. WCHAR *argN = args;
  4282. BOOL isdir = FALSE;
  4283. BOOL junction = FALSE;
  4284. BOOL hard = FALSE;
  4285. BOOL ret = FALSE;
  4286. WCHAR file1[MAX_PATH];
  4287. WCHAR file2[MAX_PATH];
  4288. if (param1[0] == 0x00 || param2[0] == 0x00) {
  4289. WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
  4290. return;
  4291. }
  4292. file1[0] = 0;
  4293. while (argN) {
  4294. WCHAR *thisArg = WCMD_parameter (args, argno++, &argN, FALSE, FALSE);
  4295. if (!argN) break;
  4296. WINE_TRACE("mklink: Processing arg '%s'\n", wine_dbgstr_w(thisArg));
  4297. if (lstrcmpiW(thisArg, L"/D") == 0)
  4298. isdir = TRUE;
  4299. else if (lstrcmpiW(thisArg, L"/H") == 0)
  4300. hard = TRUE;
  4301. else if (lstrcmpiW(thisArg, L"/J") == 0)
  4302. junction = TRUE;
  4303. else {
  4304. if(!file1[0])
  4305. lstrcpyW(file1, thisArg);
  4306. else
  4307. lstrcpyW(file2, thisArg);
  4308. }
  4309. }
  4310. if(hard)
  4311. ret = CreateHardLinkW(file1, file2, NULL);
  4312. else if(!junction)
  4313. ret = CreateSymbolicLinkW(file1, file2, isdir);
  4314. else
  4315. WINE_TRACE("Juction links currently not supported.\n");
  4316. if(!ret)
  4317. WCMD_output_stderr(WCMD_LoadMessage(WCMD_READFAIL), file1);
  4318. }