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