1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
/*
 *  BATCH.C - batch file processor for CMD.EXE.
 *
 *
 *  History:
 *
 *    ??/??/?? (Evan Jeffrey)
 *        started.
 *
 *    15 Jul 1995 (Tim Norman)
 *        modes and bugfixes.
 *
 *    08 Aug 1995 (Matt Rains)
 *        i have cleaned up the source code. changes now bring this
 *        source into guidelines for recommended programming practice.
 *
 *        i have added some constants to help making changes easier.
 *
 *    29 Jan 1996 (Steffan Kaiser)
 *        made a few cosmetic changes
 *
 *    05 Feb 1996 (Tim Norman)
 *        changed to comply with new first/rest calling scheme
 *
 *    14 Jun 1997 (Steffen Kaiser)
 *        bug fixes.  added error level expansion %?.  ctrl-break handling
 *
 *    16 Jul 1998 (Hans B Pufal)
 *        Totally reorganised in conjunction with COMMAND.C (cf) to
 *        implement proper BATCH file nesting and other improvements.
 *
 *    16 Jul 1998 (John P Price <linux-guru@gcfl.net>)
 *        Separated commands into individual files.
 *
 *    19 Jul 1998 (Hans B Pufal) [HBP_001]
 *        Preserve state of echo flag across batch calls.
 *
 *    19 Jul 1998 (Hans B Pufal) [HBP_002]
 *        Implementation of FOR command
 *
 *    20-Jul-1998 (John P Price <linux-guru@gcfl.net>)
 *        added error checking after cmd_alloc calls
 *
 *    27-Jul-1998 (John P Price <linux-guru@gcfl.net>)
 *        added config.h include
 *
 *    02-Aug-1998 (Hans B Pufal) [HBP_003]
 *        Fixed bug in ECHO flag restoration at exit from batch file
 *
 *    26-Jan-1999 Eric Kohl
 *        Replaced CRT io functions by Win32 io functions.
 *        Unicode safe!
 *
 *    23-Feb-2001 (Carl Nettelblad <cnettel@hem.passagen.es>)
 *        Fixes made to get "for" working.
 *
 *    02-Apr-2005 (Magnus Olsen <magnus@greatlord.com>)
 *        Remove all hardcoded strings in En.rc
 */

#include "precomp.h"

/* The stack of current batch contexts.
 * NULL when no batch is active
 */
LPBATCH_CONTEXT bc = NULL;

BOOL bEcho = TRUE;  /* The echo flag */



/* Buffer for reading Batch file lines */
TCHAR textline[BATCH_BUFFSIZE];


/*
 * Returns a pointer to the n'th parameter of the current batch file.
 * If no such parameter exists returns pointer to empty string.
 * If no batch file is current, returns NULL
 *
 */

LPTSTR FindArg(TCHAR Char, BOOL *IsParam0)
{
    LPTSTR pp;
    INT n = Char - _T('0');

    TRACE ("FindArg: (%d)\n", n);

    if (n < 0 || n > 9)
        return NULL;

    n = bc->shiftlevel[n];
    *IsParam0 = (n == 0);
    pp = bc->params;

    /* Step up the strings till we reach the end */
    /* or the one we want */
    while (*pp && n--)
        pp += _tcslen (pp) + 1;

    return pp;
}


/*
 * Batch_params builds a parameter list in newly allocated memory.
 * The parameters consist of null terminated strings with a final
 * NULL character signalling the end of the parameters.
 *
 */

LPTSTR BatchParams(LPTSTR s1, LPTSTR s2)
{
    LPTSTR dp = (LPTSTR)cmd_alloc ((_tcslen(s1) + _tcslen(s2) + 3) * sizeof (TCHAR));

    /* JPP 20-Jul-1998 added error checking */
    if (dp == NULL)
    {
        WARN("Cannot allocate memory for dp!\n");
        error_out_of_memory();
        return NULL;
    }

    if (s1 && *s1)
    {
        s1 = _stpcpy (dp, s1);
        *s1++ = _T('\0');
    }
    else
        s1 = dp;

    while (*s2)
    {
        BOOL inquotes = FALSE;

        /* Find next parameter */
        while (_istspace(*s2) || (*s2 && _tcschr(_T(",;="), *s2)))
            s2++;
        if (!*s2)
            break;

        /* Copy it */
        do
        {
            if (!inquotes && (_istspace(*s2) || _tcschr(_T(",;="), *s2)))
                break;
            inquotes ^= (*s2 == _T('"'));
            *s1++ = *s2++;
        } while (*s2);
        *s1++ = _T('\0');
    }

    *s1 = _T('\0');

    return dp;
}

/*
 * free the allocated memory of a batch file
 */
VOID ClearBatch(VOID)
{
    TRACE ("ClearBatch  mem = %08x    free = %d\n", bc->mem, bc->memfree);

    if (bc->mem && bc->memfree)
        cmd_free(bc->mem);

    if (bc->raw_params)
        cmd_free(bc->raw_params);

    if (bc->params)
        cmd_free(bc->params);
}

/*
 * If a batch file is current, exits it, freeing the context block and
 * chaining back to the previous one.
 *
 * If no new batch context is found, sets ECHO back ON.
 *
 * If the parameter is non-null or not empty, it is printed as an exit
 * message
 */

VOID ExitBatch(VOID)
{
    ClearBatch();

    TRACE ("ExitBatch\n");

    UndoRedirection(bc->RedirList, NULL);
    FreeRedirection(bc->RedirList);

    /* Preserve echo state across batch calls */
    bEcho = bc->bEcho;

    while (bc->setlocal)
        cmd_endlocal(_T(""));

    bc = bc->prev;
}

/*
 * Load batch file into memory
 *
 */
void BatchFile2Mem(HANDLE hBatchFile)
{
    TRACE ("BatchFile2Mem ()\n");

    bc->memsize = GetFileSize(hBatchFile, NULL);
    bc->mem     = (char *)cmd_alloc(bc->memsize+1);		/* 1 extra for '\0' */

    /* if memory is available, read it in and close the file */
    if (bc->mem != NULL)
    {
        TRACE ("BatchFile2Mem memory %08x - %08x\n",bc->mem,bc->memsize);
        SetFilePointer (hBatchFile, 0, NULL, FILE_BEGIN);
        ReadFile(hBatchFile, (LPVOID)bc->mem, bc->memsize,  &bc->memsize, NULL);
        bc->mem[bc->memsize]='\0';  /* end this, so you can dump it as a string */
        bc->memfree=TRUE;           /* this one needs to be freed */
    }
    else
    {
        bc->memsize=0;              /* this will prevent mem being accessed */
        bc->memfree=FALSE;
    }
    bc->mempos = 0;                 /* set position to the start */
}

/*
 * Start batch file execution
 *
 * The firstword parameter is the full filename of the batch file.
 *
 */
INT Batch(LPTSTR fullname, LPTSTR firstword, LPTSTR param, PARSED_COMMAND *Cmd)
{
    BATCH_CONTEXT new;
    LPFOR_CONTEXT saved_fc;
    INT i;
    INT ret = 0;
    BOOL same_fn = FALSE;

    HANDLE hFile = 0;
    SetLastError(0);
    if (bc && bc->mem)
    {
        TCHAR fpname[MAX_PATH];
        GetFullPathName(fullname, sizeof(fpname) / sizeof(TCHAR), fpname, NULL);
        if (_tcsicmp(bc->BatchFilePath,fpname)==0)
            same_fn=TRUE;
    }
    TRACE ("Batch: (\'%s\', \'%s\', \'%s\')  same_fn = %d\n",
        debugstr_aw(fullname), debugstr_aw(firstword), debugstr_aw(param), same_fn);

    if (!same_fn)
    {
        hFile = CreateFile(fullname, GENERIC_READ, FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE, NULL,
                           OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
                           FILE_FLAG_SEQUENTIAL_SCAN, NULL);

        if (hFile == INVALID_HANDLE_VALUE)
        {
            ConErrResPuts(STRING_BATCH_ERROR);
            return 1;
        }
    }

    if (bc != NULL && Cmd == bc->current)<--- Assuming that condition 'bc!=NULL' is not redundant<--- Assuming that condition 'bc!=NULL' is not redundant
    {
        /* Then we are transferring to another batch */
        ClearBatch();
        AddBatchRedirection(&Cmd->Redirections);
    }
    else
    {
        struct _SETLOCAL *setlocal = NULL;

        if (Cmd == NULL)
        {
            /* This is a CALL. CALL will set errorlevel to our return value, so
             * in order to keep the value of errorlevel unchanged in the case
             * of calling an empty batch file, we must return that same value. */
            ret = nErrorLevel;
        }
        else if (bc)
        {
            /* If a batch file runs another batch file as part of a compound command
             * (e.g. "x.bat & somethingelse") then the first file gets terminated. */

            /* Get its SETLOCAL stack so it can be migrated to the new context */
            setlocal = bc->setlocal;
            bc->setlocal = NULL;
            ExitBatch();
        }

        /* Create a new context. This function will not
         * return until this context has been exited */
        new.prev = bc;
        /* copy some fields in the new structure if it is the same file */
        if (same_fn)
        {
            new.mem     = bc->mem;<--- Null pointer dereference
            new.memsize = bc->memsize;<--- Null pointer dereference
            new.mempos  = 0;
            new.memfree = FALSE;    /* don't free this, being used before this */
        }
        bc = &new;
        bc->RedirList = NULL;
        bc->setlocal = setlocal;
    }

    GetFullPathName(fullname, sizeof(bc->BatchFilePath) / sizeof(TCHAR), bc->BatchFilePath, NULL);
    /*  if a new batch file, load it into memory and close the file */
    if (!same_fn)
    {
        BatchFile2Mem(hFile);
        CloseHandle(hFile);
    }

    bc->mempos = 0;    /* goto begin of batch file */
    bc->bEcho = bEcho; /* Preserve echo across batch calls */
    for (i = 0; i < 10; i++)
        bc->shiftlevel[i] = i;

    bc->params = BatchParams (firstword, param);
    //
    // Allocate enough memory to hold the params and copy them over without modifications
    //
    bc->raw_params = cmd_dup(param);
    if (bc->raw_params == NULL)
    {
        error_out_of_memory();
        return 1;
    }

    /* Check if this is a "CALL :label" */
    if (*firstword == _T(':'))
        cmd_goto(firstword);

    /* If we are calling from inside a FOR, hide the FOR variables */
    saved_fc = fc;
    fc = NULL;

    /* If we have created a new context, don't return
     * until this batch file has completed. */
    while (bc == &new && !bExit)
    {
        Cmd = ParseCommand(NULL);
        if (!Cmd)
            continue;

        /* JPP 19980807 */
        /* Echo the command and execute it */
        bc->current = Cmd;
        ret = ExecuteCommandWithEcho(Cmd);
        FreeCommand(Cmd);
    }

    /* Always return the current errorlevel */
    ret = nErrorLevel;

    TRACE ("Batch: returns TRUE\n");

    fc = saved_fc;
    return ret;
}

VOID AddBatchRedirection(REDIRECTION **RedirList)
{
    REDIRECTION **ListEnd;

    /* Prepend the list to the batch context's list */
    ListEnd = RedirList;
    while (*ListEnd)
        ListEnd = &(*ListEnd)->Next;
    *ListEnd = bc->RedirList;
    bc->RedirList = *RedirList;

    /* Null out the pointer so that the list will not be cleared prematurely.
     * These redirections should persist until the batch file exits. */
    *RedirList = NULL;
}

/*
 *   Read a single line from the batch file from the current batch/memory position.
 *   Almost a copy of FileGetString with same UNICODE handling
 */
BOOL BatchGetString(LPTSTR lpBuffer, INT nBufferLength)
{
    LPSTR lpString;
    INT len = 0;
#ifdef _UNICODE
    lpString = cmd_alloc(nBufferLength);
    if (!lpString)
    {
        WARN("Cannot allocate memory for lpString\n");
        error_out_of_memory();
        return FALSE;
    }
#else
    lpString = lpBuffer;
#endif
    /* read all chars from memory until a '\n' is encountered */
    if (bc->mem)
    {
        for (; (bc->mempos < bc->memsize  &&  len < (nBufferLength-1)); len++)
        { 
            lpString[len] = bc->mem[bc->mempos++];
            if (lpString[len] == '\n' )
            {
                len++;
                break;
            }
        }
    }

    if (!len)
    {
#ifdef _UNICODE
        cmd_free(lpString);
#endif
        return FALSE;
    }

    lpString[len++] = '\0';
#ifdef _UNICODE
    MultiByteToWideChar(OutputCodePage, 0, lpString, -1, lpBuffer, len);
    cmd_free(lpString);
#endif
    return TRUE;
}

/*
 * Read and return the next executable line form the current batch file
 *
 * If no batch file is current or no further executable lines are found
 * return NULL.
 *
 * Set eflag to 0 if line is not to be echoed else 1
 */
LPTSTR ReadBatchLine(VOID)
{
    TRACE ("ReadBatchLine ()\n");

    /* User halt */
    if (CheckCtrlBreak (BREAK_BATCHFILE))
    {
        while (bc)
            ExitBatch();
        return NULL;
    }

    if (!BatchGetString (textline, sizeof (textline) / sizeof (textline[0]) - 1))
    {
        TRACE ("ReadBatchLine(): Reached EOF!\n");
        /* End of file.... */
        ExitBatch();
        return NULL;
    }

    TRACE ("ReadBatchLine(): textline: \'%s\'\n", debugstr_aw(textline));

    if (textline[_tcslen(textline) - 1] != _T('\n'))
        _tcscat(textline, _T("\n"));

    return textline;
}

/* EOF */