1 /*
2  * ----------------------------------------------------------------------------
3  * nmakehlp.c --
4  *
5  *	This is used to fix limitations within nmake and the environment.
6  *
7  * Copyright (c) 2002 by David Gravereaux.
8  * Copyright (c) 2006 by Pat Thoyts
9  *
10  * See the file "license.terms" for information on usage and redistribution of
11  * this file, and for a DISCLAIMER OF ALL WARRANTIES.
12  *
13  * ----------------------------------------------------------------------------
14  * RCS: @(#) $Id: nmakehlp.c,v 1.7 2008/06/18 11:01:42 patthoyts Exp $
15  * ----------------------------------------------------------------------------
16  */
17 
18 #define _CRT_SECURE_NO_DEPRECATE
19 #include <windows.h>
20 #define NO_SHLWAPI_GDI
21 #define NO_SHLWAPI_STREAM
22 #define NO_SHLWAPI_REG
23 #include <shlwapi.h>
24 #pragma comment (lib, "user32.lib")
25 #pragma comment (lib, "kernel32.lib")
26 #pragma comment (lib, "shlwapi.lib")
27 #include <stdio.h>
28 #include <math.h>
29 
30 /*
31  * This library is required for x64 builds with _some_ versions of MSVC
32  */
33 #if defined(_M_IA64) || defined(_M_AMD64)
34 #if _MSC_VER >= 1400 && _MSC_VER < 1500
35 #pragma comment(lib, "bufferoverflowU")
36 #endif
37 #endif
38 
39 /* ISO hack for dumb VC++ */
40 #ifdef _MSC_VER
41 #define   snprintf	_snprintf
42 #endif
43 
44 
45 
46 /* protos */
47 
48 int		CheckForCompilerFeature(const char *option);
49 int		CheckForLinkerFeature(const char *option);
50 int		IsIn(const char *string, const char *substring);
51 int		GrepForDefine(const char *file, const char *string);
52 int		SubstituteFile(const char *substs, const char *filename);
53 int		QualifyPath(const char *path);
54 const char *    GetVersionFromFile(const char *filename, const char *match);
55 DWORD WINAPI	ReadFromPipe(LPVOID args);
56 
57 /* globals */
58 
59 #define CHUNK	25
60 #define STATICBUFFERSIZE    1000
61 typedef struct {
62     HANDLE pipe;
63     char buffer[STATICBUFFERSIZE];
64 } pipeinfo;
65 
66 pipeinfo Out = {INVALID_HANDLE_VALUE, '\0'};
67 pipeinfo Err = {INVALID_HANDLE_VALUE, '\0'};
68 
69 /*
70  * exitcodes: 0 == no, 1 == yes, 2 == error
71  */
72 
73 int
main(int argc,char * argv[])74 main(
75     int argc,
76     char *argv[])
77 {
78     char msg[300];
79     DWORD dwWritten;
80     int chars;
81 
82     /*
83      * Make sure children (cl.exe and link.exe) are kept quiet.
84      */
85 
86     SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOOPENFILEERRORBOX);
87 
88     /*
89      * Make sure the compiler and linker aren't effected by the outside world.
90      */
91 
92     SetEnvironmentVariable("CL", "");
93     SetEnvironmentVariable("LINK", "");
94 
95     if (argc > 1 && *argv[1] == '-') {
96 	switch (*(argv[1]+1)) {
97 	case 'c':
98 	    if (argc != 3) {
99 		chars = snprintf(msg, sizeof(msg) - 1,
100 		        "usage: %s -c <compiler option>\n"
101 			"Tests for whether cl.exe supports an option\n"
102 			"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
103 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
104 			&dwWritten, NULL);
105 		return 2;
106 	    }
107 	    return CheckForCompilerFeature(argv[2]);
108 	case 'l':
109 	    if (argc != 3) {
110 		chars = snprintf(msg, sizeof(msg) - 1,
111 	       		"usage: %s -l <linker option>\n"
112 			"Tests for whether link.exe supports an option\n"
113 			"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
114 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
115 			&dwWritten, NULL);
116 		return 2;
117 	    }
118 	    return CheckForLinkerFeature(argv[2]);
119 	case 'f':
120 	    if (argc == 2) {
121 		chars = snprintf(msg, sizeof(msg) - 1,
122 			"usage: %s -f <string> <substring>\n"
123 			"Find a substring within another\n"
124 			"exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
125 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
126 			&dwWritten, NULL);
127 		return 2;
128 	    } else if (argc == 3) {
129 		/*
130 		 * If the string is blank, there is no match.
131 		 */
132 
133 		return 0;
134 	    } else {
135 		return IsIn(argv[2], argv[3]);
136 	    }
137 	case 'g':
138 	    if (argc == 2) {
139 		chars = snprintf(msg, sizeof(msg) - 1,
140 			"usage: %s -g <file> <string>\n"
141 			"grep for a #define\n"
142 			"exitcodes: integer of the found string (no decimals)\n",
143 			argv[0]);
144 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
145 			&dwWritten, NULL);
146 		return 2;
147 	    }
148 	    return GrepForDefine(argv[2], argv[3]);
149 	case 's':
150 	    if (argc == 2) {
151 		chars = snprintf(msg, sizeof(msg) - 1,
152 			"usage: %s -s <substitutions file> <file>\n"
153 			"Perform a set of string map type substutitions on a file\n"
154 			"exitcodes: 0\n",
155 			argv[0]);
156 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
157 			&dwWritten, NULL);
158 		return 2;
159 	    }
160 	    return SubstituteFile(argv[2], argv[3]);
161 	case 'V':
162 	    if (argc != 4) {
163 		chars = snprintf(msg, sizeof(msg) - 1,
164 		    "usage: %s -V filename matchstring\n"
165 		    "Extract a version from a file:\n"
166 		    "eg: pkgIndex.tcl \"package ifneeded http\"",
167 		    argv[0]);
168 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
169 		    &dwWritten, NULL);
170 		return 0;
171 	    }
172 	    printf("%s\n", GetVersionFromFile(argv[2], argv[3]));
173 	    return 0;
174 	case 'Q':
175 	    if (argc != 3) {
176 		chars = snprintf(msg, sizeof(msg) - 1,
177 		    "usage: %s -q path\n"
178 		    "Emit the fully qualified path\n"
179 		    "exitcodes: 0 == no, 1 == yes, 2 == error\n", argv[0]);
180 		WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars,
181 		    &dwWritten, NULL);
182 		return 2;
183 	    }
184 	    return QualifyPath(argv[2]);
185 	}
186     }
187     chars = snprintf(msg, sizeof(msg) - 1,
188 	    "usage: %s -c|-l|-f|-g|-V|-s|-Q ...\n"
189 	    "This is a little helper app to equalize shell differences between WinNT and\n"
190 	    "Win9x and get nmake.exe to accomplish its job.\n",
191 	    argv[0]);
192     WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg, chars, &dwWritten, NULL);
193     return 2;
194 }
195 
196 int
CheckForCompilerFeature(const char * option)197 CheckForCompilerFeature(
198     const char *option)
199 {
200     STARTUPINFO si;
201     PROCESS_INFORMATION pi;
202     SECURITY_ATTRIBUTES sa;
203     DWORD threadID;
204     char msg[300];
205     BOOL ok;
206     HANDLE hProcess, h, pipeThreads[2];
207     char cmdline[100];
208 
209     hProcess = GetCurrentProcess();
210 
211     ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
212     ZeroMemory(&si, sizeof(STARTUPINFO));
213     si.cb = sizeof(STARTUPINFO);
214     si.dwFlags   = STARTF_USESTDHANDLES;
215     si.hStdInput = INVALID_HANDLE_VALUE;
216 
217     ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
218     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
219     sa.lpSecurityDescriptor = NULL;
220     sa.bInheritHandle = FALSE;
221 
222     /*
223      * Create a non-inheritible pipe.
224      */
225 
226     CreatePipe(&Out.pipe, &h, &sa, 0);
227 
228     /*
229      * Dupe the write side, make it inheritible, and close the original.
230      */
231 
232     DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE,
233 	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
234 
235     /*
236      * Same as above, but for the error side.
237      */
238 
239     CreatePipe(&Err.pipe, &h, &sa, 0);
240     DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE,
241 	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
242 
243     /*
244      * Base command line.
245      */
246 
247     lstrcpy(cmdline, "cl.exe -nologo -c -TC -Zs -X -Fp.\\_junk.pch ");
248 
249     /*
250      * Append our option for testing
251      */
252 
253     lstrcat(cmdline, option);
254 
255     /*
256      * Filename to compile, which exists, but is nothing and empty.
257      */
258 
259     lstrcat(cmdline, " .\\nul");
260 
261     ok = CreateProcess(
262 	    NULL,	    /* Module name. */
263 	    cmdline,	    /* Command line. */
264 	    NULL,	    /* Process handle not inheritable. */
265 	    NULL,	    /* Thread handle not inheritable. */
266 	    TRUE,	    /* yes, inherit handles. */
267 	    DETACHED_PROCESS, /* No console for you. */
268 	    NULL,	    /* Use parent's environment block. */
269 	    NULL,	    /* Use parent's starting directory. */
270 	    &si,	    /* Pointer to STARTUPINFO structure. */
271 	    &pi);	    /* Pointer to PROCESS_INFORMATION structure. */
272 
273     if (!ok) {
274 	DWORD err = GetLastError();
275 	int chars = snprintf(msg, sizeof(msg) - 1,
276 		"Tried to launch: \"%s\", but got error [%u]: ", cmdline, err);
277 
278 	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|
279 		FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars],
280 		(300-chars), 0);
281 	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg,lstrlen(msg), &err,NULL);
282 	return 2;
283     }
284 
285     /*
286      * Close our references to the write handles that have now been inherited.
287      */
288 
289     CloseHandle(si.hStdOutput);
290     CloseHandle(si.hStdError);
291 
292     WaitForInputIdle(pi.hProcess, 5000);
293     CloseHandle(pi.hThread);
294 
295     /*
296      * Start the pipe reader threads.
297      */
298 
299     pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
300     pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
301 
302     /*
303      * Block waiting for the process to end.
304      */
305 
306     WaitForSingleObject(pi.hProcess, INFINITE);
307     CloseHandle(pi.hProcess);
308 
309     /*
310      * Wait for our pipe to get done reading, should it be a little slow.
311      */
312 
313     WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
314     CloseHandle(pipeThreads[0]);
315     CloseHandle(pipeThreads[1]);
316 
317     /*
318      * Look for the commandline warning code in both streams.
319      *  - in MSVC 6 & 7 we get D4002, in MSVC 8 we get D9002.
320      */
321 
322     return !(strstr(Out.buffer, "D4002") != NULL
323              || strstr(Err.buffer, "D4002") != NULL
324              || strstr(Out.buffer, "D9002") != NULL
325              || strstr(Err.buffer, "D9002") != NULL
326              || strstr(Out.buffer, "D2021") != NULL
327              || strstr(Err.buffer, "D2021") != NULL);
328 }
329 
330 int
CheckForLinkerFeature(const char * option)331 CheckForLinkerFeature(
332     const char *option)
333 {
334     STARTUPINFO si;
335     PROCESS_INFORMATION pi;
336     SECURITY_ATTRIBUTES sa;
337     DWORD threadID;
338     char msg[300];
339     BOOL ok;
340     HANDLE hProcess, h, pipeThreads[2];
341     char cmdline[100];
342 
343     hProcess = GetCurrentProcess();
344 
345     ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
346     ZeroMemory(&si, sizeof(STARTUPINFO));
347     si.cb = sizeof(STARTUPINFO);
348     si.dwFlags   = STARTF_USESTDHANDLES;
349     si.hStdInput = INVALID_HANDLE_VALUE;
350 
351     ZeroMemory(&sa, sizeof(SECURITY_ATTRIBUTES));
352     sa.nLength = sizeof(SECURITY_ATTRIBUTES);
353     sa.lpSecurityDescriptor = NULL;
354     sa.bInheritHandle = TRUE;
355 
356     /*
357      * Create a non-inheritible pipe.
358      */
359 
360     CreatePipe(&Out.pipe, &h, &sa, 0);
361 
362     /*
363      * Dupe the write side, make it inheritible, and close the original.
364      */
365 
366     DuplicateHandle(hProcess, h, hProcess, &si.hStdOutput, 0, TRUE,
367 	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
368 
369     /*
370      * Same as above, but for the error side.
371      */
372 
373     CreatePipe(&Err.pipe, &h, &sa, 0);
374     DuplicateHandle(hProcess, h, hProcess, &si.hStdError, 0, TRUE,
375 	    DUPLICATE_SAME_ACCESS | DUPLICATE_CLOSE_SOURCE);
376 
377     /*
378      * Base command line.
379      */
380 
381     lstrcpy(cmdline, "link.exe -nologo ");
382 
383     /*
384      * Append our option for testing.
385      */
386 
387     lstrcat(cmdline, option);
388 
389     ok = CreateProcess(
390 	    NULL,	    /* Module name. */
391 	    cmdline,	    /* Command line. */
392 	    NULL,	    /* Process handle not inheritable. */
393 	    NULL,	    /* Thread handle not inheritable. */
394 	    TRUE,	    /* yes, inherit handles. */
395 	    DETACHED_PROCESS, /* No console for you. */
396 	    NULL,	    /* Use parent's environment block. */
397 	    NULL,	    /* Use parent's starting directory. */
398 	    &si,	    /* Pointer to STARTUPINFO structure. */
399 	    &pi);	    /* Pointer to PROCESS_INFORMATION structure. */
400 
401     if (!ok) {
402 	DWORD err = GetLastError();
403 	int chars = snprintf(msg, sizeof(msg) - 1,
404 		"Tried to launch: \"%s\", but got error [%u]: ", cmdline, err);
405 
406 	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_IGNORE_INSERTS|
407 		FORMAT_MESSAGE_MAX_WIDTH_MASK, 0L, err, 0, (LPVOID)&msg[chars],
408 		(300-chars), 0);
409 	WriteFile(GetStdHandle(STD_ERROR_HANDLE), msg,lstrlen(msg), &err,NULL);
410 	return 2;
411     }
412 
413     /*
414      * Close our references to the write handles that have now been inherited.
415      */
416 
417     CloseHandle(si.hStdOutput);
418     CloseHandle(si.hStdError);
419 
420     WaitForInputIdle(pi.hProcess, 5000);
421     CloseHandle(pi.hThread);
422 
423     /*
424      * Start the pipe reader threads.
425      */
426 
427     pipeThreads[0] = CreateThread(NULL, 0, ReadFromPipe, &Out, 0, &threadID);
428     pipeThreads[1] = CreateThread(NULL, 0, ReadFromPipe, &Err, 0, &threadID);
429 
430     /*
431      * Block waiting for the process to end.
432      */
433 
434     WaitForSingleObject(pi.hProcess, INFINITE);
435     CloseHandle(pi.hProcess);
436 
437     /*
438      * Wait for our pipe to get done reading, should it be a little slow.
439      */
440 
441     WaitForMultipleObjects(2, pipeThreads, TRUE, 500);
442     CloseHandle(pipeThreads[0]);
443     CloseHandle(pipeThreads[1]);
444 
445     /*
446      * Look for the commandline warning code in the stderr stream.
447      */
448 
449     return !(strstr(Out.buffer, "LNK1117") != NULL ||
450 	    strstr(Err.buffer, "LNK1117") != NULL ||
451 	    strstr(Out.buffer, "LNK4044") != NULL ||
452 	    strstr(Err.buffer, "LNK4044") != NULL);
453 }
454 
455 DWORD WINAPI
ReadFromPipe(LPVOID args)456 ReadFromPipe(
457     LPVOID args)
458 {
459     pipeinfo *pi = (pipeinfo *) args;
460     char *lastBuf = pi->buffer;
461     DWORD dwRead;
462     BOOL ok;
463 
464   again:
465     if (lastBuf - pi->buffer + CHUNK > STATICBUFFERSIZE) {
466 	CloseHandle(pi->pipe);
467 	return (DWORD)-1;
468     }
469     ok = ReadFile(pi->pipe, lastBuf, CHUNK, &dwRead, 0L);
470     if (!ok || dwRead == 0) {
471 	CloseHandle(pi->pipe);
472 	return 0;
473     }
474     lastBuf += dwRead;
475     goto again;
476 
477     return 0;  /* makes the compiler happy */
478 }
479 
480 int
IsIn(const char * string,const char * substring)481 IsIn(
482     const char *string,
483     const char *substring)
484 {
485     return (strstr(string, substring) != NULL);
486 }
487 
488 /*
489  * Find a specified #define by name.
490  *
491  * If the line is '#define TCL_VERSION "8.5"', it returns 85 as the result.
492  */
493 
494 int
GrepForDefine(const char * file,const char * string)495 GrepForDefine(
496     const char *file,
497     const char *string)
498 {
499     char s1[51], s2[51], s3[51];
500     FILE *f = fopen(file, "rt");
501 
502     if (f == NULL) {
503 	return 0;
504     }
505 
506     do {
507 	int r = fscanf(f, "%50s", s1);
508 
509 	if (r == 1 && !strcmp(s1, "#define")) {
510 	    /*
511 	     * Get next two words.
512 	     */
513 
514 	    r = fscanf(f, "%50s %50s", s2, s3);
515 	    if (r != 2) {
516 		continue;
517 	    }
518 
519 	    /*
520 	     * Is the first word what we're looking for?
521 	     */
522 
523 	    if (!strcmp(s2, string)) {
524 		double d1;
525 
526 		fclose(f);
527 
528 		/*
529 		 * Add 1 past first double quote char. "8.5"
530 		 */
531 
532 		d1 = atof(s3 + 1);		  /*    8.5  */
533 		while (floor(d1) != d1) {
534 		    d1 *= 10.0;
535 		}
536 		return ((int) d1);		  /*    85   */
537 	    }
538 	}
539     } while (!feof(f));
540 
541     fclose(f);
542     return 0;
543 }
544 
545 /*
546  * GetVersionFromFile --
547  * 	Looks for a match string in a file and then returns the version
548  * 	following the match where a version is anything acceptable to
549  * 	package provide or package ifneeded.
550  */
551 
552 const char *
GetVersionFromFile(const char * filename,const char * match)553 GetVersionFromFile(
554     const char *filename,
555     const char *match)
556 {
557     size_t cbBuffer = 100;
558     static char szBuffer[100];
559     char *szResult = NULL;
560     FILE *fp = fopen(filename, "rt");
561 
562     if (fp != NULL) {
563 	/*
564 	 * Read data until we see our match string.
565 	 */
566 
567 	while (fgets(szBuffer, cbBuffer, fp) != NULL) {
568 	    LPSTR p, q;
569 
570 	    p = strstr(szBuffer, match);
571 	    if (p != NULL) {
572 		/*
573 		 * Skip to first digit.
574 		 */
575 
576 		while (*p && !isdigit(*p)) {
577 		    ++p;
578 		}
579 
580 		/*
581 		 * Find ending whitespace.
582 		 */
583 
584 		q = p;
585 		while (*q && (isalnum(*q) || *q == '.')) {
586 		    ++q;
587 		}
588 
589 		memcpy(szBuffer, p, q - p);
590 		szBuffer[q-p] = 0;
591 		szResult = szBuffer;
592 		break;
593 	    }
594 	}
595 	fclose(fp);
596     }
597     return szResult;
598 }
599 
600 /*
601  * List helpers for the SubstituteFile function
602  */
603 
604 typedef struct list_item_t {
605     struct list_item_t *nextPtr;
606     char * key;
607     char * value;
608 } list_item_t;
609 
610 /* insert a list item into the list (list may be null) */
611 static list_item_t *
list_insert(list_item_t ** listPtrPtr,const char * key,const char * value)612 list_insert(list_item_t **listPtrPtr, const char *key, const char *value)
613 {
614     list_item_t *itemPtr = malloc(sizeof(list_item_t));
615     if (itemPtr) {
616 	itemPtr->key = strdup(key);
617 	itemPtr->value = strdup(value);
618 	itemPtr->nextPtr = NULL;
619 
620 	while(*listPtrPtr) {
621 	    listPtrPtr = &(*listPtrPtr)->nextPtr;
622 	}
623 	*listPtrPtr = itemPtr;
624     }
625     return itemPtr;
626 }
627 
628 static void
list_free(list_item_t ** listPtrPtr)629 list_free(list_item_t **listPtrPtr)
630 {
631     list_item_t *tmpPtr, *listPtr = *listPtrPtr;
632     while (listPtr) {
633 	tmpPtr = listPtr;
634 	listPtr = listPtr->nextPtr;
635 	free(tmpPtr->key);
636 	free(tmpPtr->value);
637 	free(tmpPtr);
638     }
639 }
640 
641 /*
642  * SubstituteFile --
643  *	As windows doesn't provide anything useful like sed and it's unreliable
644  *	to use the tclsh you are building against (consider x-platform builds -
645  *	eg compiling AMD64 target from IX86) we provide a simple substitution
646  *	option here to handle autoconf style substitutions.
647  *	The substitution file is whitespace and line delimited. The file should
648  *	consist of lines matching the regular expression:
649  *	  \s*\S+\s+\S*$
650  *
651  *	Usage is something like:
652  *	  nmakehlp -S << $** > $@
653  *        @PACKAGE_NAME@ $(PACKAGE_NAME)
654  *        @PACKAGE_VERSION@ $(PACKAGE_VERSION)
655  *        <<
656  */
657 
658 int
SubstituteFile(const char * substitutions,const char * filename)659 SubstituteFile(
660     const char *substitutions,
661     const char *filename)
662 {
663     size_t cbBuffer = 1024;
664     static char szBuffer[1024], szCopy[1024];
665     char *szResult = NULL;
666     list_item_t *substPtr = NULL;
667     FILE *fp, *sp;
668 
669     fp = fopen(filename, "rt");
670     if (fp != NULL) {
671 
672 	/*
673 	 * Build a list of substutitions from the first filename
674 	 */
675 
676 	sp = fopen(substitutions, "rt");
677 	if (sp != NULL) {
678 	    while (fgets(szBuffer, cbBuffer, sp) != NULL) {
679 		char *ks, *ke, *vs, *ve;
680 		ks = szBuffer;
681 		while (ks && *ks && isspace(*ks)) ++ks;
682 		ke = ks;
683 		while (ke && *ke && !isspace(*ke)) ++ke;
684 		vs = ke;
685 		while (vs && *vs && isspace(*vs)) ++vs;
686 		ve = vs;
687 		while (ve && *ve && !(*ve == '\r' || *ve == '\n')) ++ve;
688 		*ke = 0, *ve = 0;
689 		list_insert(&substPtr, ks, vs);
690 	    }
691 	    fclose(sp);
692 	}
693 
694 	/* debug: dump the list */
695 #ifdef _DEBUG
696 	{
697 	    int n = 0;
698 	    list_item_t *p = NULL;
699 	    for (p = substPtr; p != NULL; p = p->nextPtr, ++n) {
700 		fprintf(stderr, "% 3d '%s' => '%s'\n", n, p->key, p->value);
701 	    }
702 	}
703 #endif
704 
705 	/*
706 	 * Run the substitutions over each line of the input
707 	 */
708 
709 	while (fgets(szBuffer, cbBuffer, fp) != NULL) {
710 	    list_item_t *p = NULL;
711 	    for (p = substPtr; p != NULL; p = p->nextPtr) {
712 		char *m = strstr(szBuffer, p->key);
713 		if (m) {
714 		    char *cp, *op, *sp;
715 		    cp = szCopy;
716 		    op = szBuffer;
717 		    while (op != m) *cp++ = *op++;
718 		    sp = p->value;
719 		    while (sp && *sp) *cp++ = *sp++;
720 		    op += strlen(p->key);
721 		    while (*op) *cp++ = *op++;
722 		    *cp = 0;
723 		    memcpy(szBuffer, szCopy, sizeof(szCopy));
724 		}
725 	    }
726 	    printf(szBuffer);
727 	}
728 
729 	list_free(&substPtr);
730     }
731     fclose(fp);
732     return 0;
733 }
734 
735 /*
736  * QualifyPath --
737  *
738  *	This composes the current working directory with a provided path
739  *	and returns the fully qualified and normalized path.
740  *	Mostly needed to setup paths for testing.
741  */
742 
743 int
QualifyPath(const char * szPath)744 QualifyPath(
745     const char *szPath)
746 {
747     char szCwd[MAX_PATH + 1];
748     char szTmp[MAX_PATH + 1];
749     char *p;
750     GetCurrentDirectory(MAX_PATH, szCwd);
751     while ((p = strchr(szPath, '/')) && *p)
752 	*p = '\\';
753     PathCombine(szTmp, szCwd, szPath);
754     PathCanonicalize(szCwd, szTmp);
755     printf("%s\n", szCwd);
756     return 0;
757 }
758 
759 /*
760  * Local variables:
761  *   mode: c
762  *   c-basic-offset: 4
763  *   fill-column: 78
764  *   indent-tabs-mode: t
765  *   tab-width: 8
766  * End:
767  */
768