Bug Summary

File:dmake/function.c
Location:line 451, column 7
Description:Null pointer passed as an argument to a 'nonnull' parameter

Annotated Source Code

1/*
2--
3-- SYNOPSIS
4-- GNU style functions for dmake.
5--
6-- DESCRIPTION
7-- All GNU style functions understood by dmake are implemented in this
8-- file. Currently the only such function is $(mktmp ...) which is
9-- not part of GNU-make is an extension provided by dmake.
10--
11-- AUTHOR
12-- Dennis Vadura, dvadura@dmake.wticorp.com
13--
14-- WWW
15-- http://dmake.wticorp.com/
16--
17-- COPYRIGHT
18-- Copyright (c) 1996,1997 by WTI Corp. All rights reserved.
19--
20-- This program is NOT free software; you can redistribute it and/or
21-- modify it under the terms of the Software License Agreement Provided
22-- in the file <distribution-root>/readme/license.txt.
23--
24-- LOG
25-- Use cvs log to obtain detailed change logs.
26*/
27
28#include "extern.h"
29
30static char *_exec_mktmp ANSI((char *, char *, char *))(char *, char *, char *);
31static char *_exec_subst ANSI((char *, char *, char *))(char *, char *, char *);
32static char *_exec_iseq ANSI((char *, char *, char *, int))(char *, char *, char *, int);
33static char *_exec_sort ANSI((char *))(char *);
34static char *_exec_echo ANSI((char *))(char *);
35static char *_exec_uniq ANSI((char *))(char *);
36static char *_exec_shell ANSI((char *, int))(char *, int);
37static char *_exec_call ANSI((char *, char *))(char *, char *);
38static char *_exec_assign ANSI((char *))(char *);
39static char *_exec_foreach ANSI((char *, char *, char *))(char *, char *, char *);
40static char *_exec_andor ANSI((char *, int))(char *, int);
41static char *_exec_not ANSI((char *))(char *);
42static int _mystrcmp ANSI((const DMPVOID, const DMPVOID))(const void *, const void *);
43
44
45PUBLIC char *
46Exec_function(buf)/*
47====================
48 Execute the function given by the value of args.
49
50 So far mktmp is the only valid function, anything else elicits and error
51 message. It is my hope to support the GNU style functions in this portion
52 of the code at some time in the future. */
53char *buf;
54{
55 char *fname;
56 char *args;
57 char *mod1;
58 char *mod2 = NIL(char)((char*)((void*)0));
59 int mod_count = 0;
60 char *res = NIL(char)((char*)((void*)0));
61
62 /* This must succeed since the presence of ' ', \t or \n is what
63 * determines if this function is called in the first place.
64 * Unfortunately this prohibits the use of whitespaces in parameters
65 * for macro functions. */
66 /* ??? Using ScanToken to find the next ' ', \t or \n and discarding
67 * the returned, evaluated result is a misuse of that function. */
68 FREE(ScanToken(buf, &args, FALSE))free((char*)(ScanToken(buf, &args, 0)));
69 fname = DmSubStr(buf, args);
70 /* args points to the whitespace after the found token, this leads
71 * to leading whitespaces. */
72 if( *args ) {
1
Taking false branch
73 args = DmStrSpn(args," \t"); /* strip whitespace before */
74 if( *args ) { /* ... and after value */
75 char *q;
76 for(q=args+strlen(args)-1; ((*q == ' ')||(*q == '\t')); q--);
77 *++q = '\0';
78 }
79 }
80
81 /* ??? Some function macros expect comma seperated parameters, but
82 * no decent parser is included. The desirable solution would be
83 * to parse fname for the correct number of parameters in fname
84 * when a function is recognized. We only count the parameters
85 * at the moment. Note "" is a valid parameter. */
86 if( (mod1 = strchr(fname,',')) != NIL(char)((char*)((void*)0)) ){
2
Taking false branch
87 *mod1 = '\0';
88 mod1++;
89 mod_count++;
90
91 if( (mod2 = strchr(mod1,',')) != NIL(char)((char*)((void*)0)) ){
92 *mod2 = '\0';
93 mod2++;
94 mod_count++;
95 }
96 }
97
98 /* ??? At the moment only the leading part of fname compared if it
99 * matches a known function macro. For example assignXXX or even
100 * assign,,,, is also erroneously accepted. */
101 switch( *fname ) {
3
Control jumps to 'case 115:' at line 199
102 case 'a':
103 if(strncmp(fname,"assign",6) == 0)
104 res = _exec_assign(args);
105 else if(strncmp(fname,"and",3) == 0)
106 res = _exec_andor(args, TRUE1);
107 else
108 res = _exec_call(fname,args);
109 break;
110
111 case 'e':
112 if(strncmp(fname,"eq",2) == 0)
113 if( mod_count == 2 )
114 res = _exec_iseq(mod1,mod2,args,TRUE1);
115 else
116 Fatal( "Two comma-seperated arguments expected in [%s].\n", buf );
117 else if (strncmp(fname,"echo",4) == 0)
118 res = _exec_echo(args);
119 else
120 res = _exec_call(fname,args);
121 break;
122
123 case 'f':
124 if(strncmp(fname,"foreach",7) == 0)
125 if( mod_count == 2 )
126 res = _exec_foreach(mod1,mod2,args);
127 else
128 Fatal( "Two comma-seperated arguments expected in [%s].\n", buf );
129 else
130 res = _exec_call(fname,args);
131 break;
132
133 case 'm':
134 if(strncmp(fname,"mktmp",5) == 0)
135 if( mod_count < 3 )
136 res = _exec_mktmp(mod1,mod2,args);
137 else
138 Fatal( "Maximal two comma-seperated arguments expected in [%s].\n", buf );
139 else
140 res = _exec_call(fname,args);
141 break;
142
143 case 'n':
144 if( strncmp(fname,"null", 4) == 0 )
145 res = _exec_iseq(mod1,NIL(char)((char*)((void*)0)),args,TRUE1);
146 else if (strncmp(fname,"nil",3) == 0 ) {
147 FREE(Expand(args))free((char*)(Expand(args)));
148 res = DmStrDup("");
149 }
150 else if (strncmp(fname,"not",3) == 0 )
151 res = _exec_not(args);
152 else if (strncmp(fname,"normpath",8) == 0 ) {
153 char *eargs = Expand(args);
154
155 if( mod_count == 0 ) {
156 res = exec_normpath(eargs);
157 }
158 else if( mod_count == 1 ) {
159 char *para = Expand(mod1);
160 int tmpUseWinpath = UseWinpath;
161
162 if( !*para || strcmp(para, "\"\"") == 0 ) {
163 UseWinpath = FALSE0;
164 } else {
165 UseWinpath = TRUE1;
166 }
167 res = exec_normpath(eargs);
168 UseWinpath = tmpUseWinpath;
169 FREE(para)free((char*)(para));
170 }
171 else
172 Fatal( "One or no comma-seperated arguments expected in [%s].\n", buf );
173
174 FREE(eargs)free((char*)(eargs));
175 }
176 else
177 res = _exec_call(fname,args);
178 break;
179
180 case '!':
181 if(strncmp(fname,"!null",5) == 0)
182 res = _exec_iseq(mod1,NIL(char)((char*)((void*)0)),args,FALSE0);
183 else if(strncmp(fname,"!eq",3) ==0)
184 if( mod_count == 2 )
185 res = _exec_iseq(mod1,mod2,args,FALSE0);
186 else
187 Fatal( "Two comma-seperated arguments expected in [%s].\n", buf );
188 else
189 res = _exec_call(fname,args);
190 break;
191
192 case 'o':
193 if(strncmp(fname,"or",2) == 0)
194 res = _exec_andor(args, FALSE0);
195 else
196 res = _exec_call(fname,args);
197 break;
198
199 case 's':
200 if(strncmp(fname,"sort",4) == 0)
4
Taking true branch
201 res = _exec_sort(args);
5
Calling '_exec_sort'
202 else if(strncmp(fname,"shell",5)==0)
203 if( mod_count == 0 ) {
204 res = _exec_shell(args, FALSE0);
205 }
206 else if( mod_count == 1 ) {
207 char *emod = Expand(mod1);
208 if(strncmp(emod,"expand",7)==0)
209 res = _exec_shell(args, TRUE1);
210 else
211 Fatal( "Unknown argument [%s] to shell in [%s].\n", emod, buf );
212 FREE(emod)free((char*)(emod));
213 }
214 else
215 Fatal( "One or no comma-seperated arguments expected in [%s].\n", buf );
216 else if(strncmp(fname,"strip",5)==0)
217 res = Tokenize(Expand(args)," ",'t',TRUE1);
218 else if(strncmp(fname,"subst",5)==0) {
219 if( mod_count == 2 )
220 res = _exec_subst(mod1,mod2,args);
221 else
222 Fatal( "Two comma-seperated arguments expected in [%s].\n", buf );
223 }
224 else
225 res = _exec_call(fname,args);
226 break;
227
228 case 'u':
229 if(strncmp(fname,"uniq",4) == 0)
230 res = _exec_uniq(args);
231 else
232 res = _exec_call(fname,args);
233 break;
234
235 default:
236 res = _exec_call(fname,args);
237 }
238
239 if( res == NIL(char)((char*)((void*)0)) ) res = DmStrDup("");
240
241 FREE(fname)free((char*)(fname));
242 return(res);
243}
244
245
246static char *
247_exec_assign( macrostring )
248char *macrostring;
249{
250 if ( !Parse_macro(macrostring, M_MULTI0x0004|M_FORCE0x0080) ) {
251 Error( "Dynamic macro assignment failed, while making [%s]\n",
252 Current_target ? Current_target->CE_NAMEce_name->ht_name : "NIL");
253 return(DmStrDup(""));
254 }
255
256 return(DmStrDup(LastMacName));
257}
258
259
260static char *
261_exec_echo(data)
262char *data;
263{
264 return(DmStrDup(DmStrSpn(data," \t")));
265}
266
267
268static char *
269_exec_call( var, list )/*
270=========================
271 Return the (recursively expanded) value of macro var. Expand list and
272 discard the result.
273*/
274char *var; /* Name of the macro (until first whitespace). */
275char *list; /* Rest data (after the whitespace). */
276{
277 char *res = NIL(char)((char*)((void*)0));
278
279 /* the argument part is expanded. */
280 FREE(Expand(list))free((char*)(Expand(list)));
281
282 /* Prepend '$(' and append ')' so that Expand will return the value
283 * of the 'var' macro. */
284 var = DmStrJoin(DmStrJoin("$(",var,-1,FALSE0),")",-1,TRUE1);
285 res = Expand(var);
286
287 FREE(var)free((char*)(var));
288 return(res);
289}
290
291
292static char *
293_exec_foreach( var, list, data )
294char *var;
295char *list;
296char *data;
297{
298 char *res = NIL(char)((char*)((void*)0));
299 char *s;
300 TKSTR tk;
301 HASHPTR hp;
302
303 var = Expand(var);
304 list = Expand(list);
305
306 data = DmStrSpn(data," \t\n");
307 SET_TOKEN(&tk,list)(&tk)->tk_str = (list); (&tk)->tk_cchar = *(list
); (&tk)->tk_quote = 1;
;
308 /* push previous macro definition and redefine. */
309 hp = Def_macro(var,"",M_MULTI0x0004|M_NOEXPORT0x0040|M_FORCE0x0080|M_PUSH0x0100);
310
311 while( *(s=Get_token(&tk, "", FALSE0)) != '\0' ) {
312 Def_macro(var,s,M_MULTI0x0004|M_NOEXPORT0x0040|M_FORCE0x0080);
313 res = DmStrAdd(res,Expand(data),TRUE1);
314 }
315
316 CLEAR_TOKEN(&tk)*(&tk)->tk_str = (&tk)->tk_cchar;
317 Pop_macro(hp); /* Get back old macro definition. */
318 FREE(hp->ht_name)free((char*)(hp->ht_name));
319 if(hp->ht_value) FREE(hp->ht_value)free((char*)(hp->ht_value));
320 FREE(hp)free((char*)(hp));
321 FREE(var)free((char*)(var));
322 FREE(list)free((char*)(list));
323
324 return(res);
325}
326
327
328static char *
329_exec_mktmp( file, text, data )
330char *file;
331char *text;
332char *data;
333{
334 char *tmpname;
335 char *name;
336 FILE *tmpfile = NIL(FILE)((FILE*)((void*)0));
337
338 /* This is only a test of the recipe line so prevent the tempfile side
339 * effects. */
340 if( Suppress_temp_file ) return(NIL(char)((char*)((void*)0)));
341
342 name = Current_target ? Current_target->CE_NAMEce_name->ht_name:"makefile text";
343
344 if( file && *file ) {
345 /* Expand the file parameter to mktmp if present. */
346 tmpname = Expand(file);
347
348 if( *tmpname ) {
349#ifdef HAVE_MKSTEMP1
350 /* Only use umask if we are also using mkstemp - this basically
351 * avoids using the incompatible implementation from MSVC. */
352 mode_t mask;
353
354 /* Create tempfile with 600 permissions. */
355 mask = umask(0066);
356#endif
357
358 if( (tmpfile = fopen(tmpname, "w")) == NIL(FILE)((FILE*)((void*)0)) )
359 Open_temp_error( tmpname, name );
360#ifdef HAVE_MKSTEMP1
361 umask(mask);
362#endif
363
364 Def_macro("TMPFILE", tmpname, M_EXPANDED0x0008|M_MULTI0x0004);
365 Link_temp( Current_target, tmpfile, tmpname );
366
367 /* Don't free tmpname if it is used. It is stored in a FILELIST
368 * member in Link_temp() and freed by Unlink_temp_files(). */
369 }
370 else
371 FREE(tmpname)free((char*)(tmpname));
372 }
373
374 /* If file expanded to a non empty value tmpfile is already opened,
375 * otherwise open it now. */
376 if( !tmpfile )
377 tmpfile = Start_temp( "", Current_target, &tmpname );
378
379 /* If the text parameter is given return its expanded value
380 * instead of the used filename. */
381 if( !text || !*text ) {
382 /* tmpname is freed by Unlink_temp_files(). */
383 text = DmStrDup(DO_WINPATH(tmpname)tmpname);
384 }
385 else {
386 text = Expand(text);
387 }
388
389 data = Expand(data);
390
391 Append_line( data, TRUE1, tmpfile, name, FALSE0, FALSE0 );
392 Close_temp( Current_target, tmpfile );
393 FREE(data)free((char*)(data));
394
395 return( text );
396}
397
398
399static char *
400_exec_iseq( lhs, rhs, data, eq )
401char *lhs;
402char *rhs;
403char *data;
404int eq;
405{
406 char *l = Expand(lhs);
407 char *r = Expand(rhs);
408 char *i = DmStrSpn(data, " \t\n");
409 char *e = strchr(i, ' ');
410 char *res = NIL(char)((char*)((void*)0));
411 int val = strcmp(l,r);
412
413 if( (!val && eq) || (val && !eq) ) {
414 if( e != NIL(char)((char*)((void*)0)) ) *e = '\0';
415 res = Expand(i);
416 }
417 else if( e != NIL(char)((char*)((void*)0)) ) {
418 e = DmStrSpn(e," \t\n");
419 if( *e ) res = Expand(e);
420 }
421
422 FREE(l)free((char*)(l));
423 FREE(r)free((char*)(r));
424 return(res);
425}
426
427
428static char *
429_exec_sort( args )
430char *args;
431{
432 char *res = NIL(char)((char*)((void*)0));
433 char *data = Expand(args);
434 char **tokens;
435 char *p;
436 char *white = " \t\n";
437 int j;
438 int i;
439
440 for(i=0,p=DmStrSpn(data,white);*p;p=DmStrSpn(DmStrPbrk(p,white),white),i++);
6
Loop condition is true. Entering loop body
7
Loop condition is false. Execution continues on line 442
441
442 if( i != 0 ) {
8
Taking true branch
443 TALLOC(tokens, i, char *)if ((tokens = (char **) calloc((unsigned int)(i), (size_t)sizeof
(char *))) == (char **)0) {No_ram();}
;
444
445 for( i=0,p=DmStrSpn(data,white); *p; p=DmStrSpn(p,white),i++){
9
Loop condition is false. Execution continues on line 451
446 tokens[i] = p;
447 p = DmStrPbrk(p,white);
448 if( *p ) *p++ = '\0';
449 }
450
451 qsort( tokens, i, sizeof(char *), _mystrcmp );
10
Null pointer passed as an argument to a 'nonnull' parameter
452
453 for( j=0; j<i; j++ ) res = DmStrApp(res, tokens[j]);
454 FREE(data)free((char*)(data));
455 FREE(tokens)free((char*)(tokens));
456 }
457
458 return(res);
459}
460
461
462static char *
463_exec_uniq( args )
464char *args;
465{
466 char *res = NIL(char)((char*)((void*)0));
467 char *data = Expand(args);
468 char **tokens;
469 char **tokens_after;
470 char *p;
471 char *white = " \t\n";
472 int j;
473 int i;
474 char *last = "";
475 int k = 0;
476
477 for(i=0,p=DmStrSpn(data,white);*p;p=DmStrSpn(DmStrPbrk(p,white),white),i++);
478
479 if( i != 0 ) {
480 TALLOC(tokens, i, char *)if ((tokens = (char **) calloc((unsigned int)(i), (size_t)sizeof
(char *))) == (char **)0) {No_ram();}
;
481 TALLOC(tokens_after, i, char *)if ((tokens_after = (char **) calloc((unsigned int)(i), (size_t
)sizeof(char *))) == (char **)0) {No_ram();}
;
482
483 for( i=0,p=DmStrSpn(data,white); *p; p=DmStrSpn(p,white),i++){
484 tokens[i] = p;
485 p = DmStrPbrk(p,white);
486 if( *p ) *p++ = '\0';
487 }
488
489 qsort( tokens, i, sizeof(char *), _mystrcmp );
490
491 for( j=0; j<i; j++ ) {
492 if (strcmp(tokens[j], last) != 0) {
493 tokens_after[k++] = tokens[j];
494 last = tokens[j];
495 }
496 }
497
498 for( j=0; j<k; j++ ) res = DmStrApp(res, tokens_after[j]);
499 FREE(tokens)free((char*)(tokens));
500 FREE(tokens_after)free((char*)(tokens_after));
501 }
502
503 FREE(data)free((char*)(data));
504 return(res);
505}
506
507static int
508_mystrcmp( p, q )
509const DMPVOIDvoid * p;
510const DMPVOIDvoid * q;
511{
512 return(strcmp(*((const char **)p),*((const char **)q)));
513}
514
515
516static char *
517_exec_subst( pat, subst, data )
518char *pat;
519char *subst;
520char *data;
521{
522 char *res;
523
524 pat = Expand(pat);
525 subst = Expand(subst);
526
527 /* This implies FREE(Expand(data)) */
528 res = Apply_edit( Expand(data), pat, subst, TRUE1, FALSE0 );
529 FREE(pat)free((char*)(pat));
530 FREE(subst)free((char*)(subst));
531
532 return(res);
533}
534
535
536static char *
537_exec_shell( data, expand )/*
538=============================
539 Capture the stdout of an execuded command.
540 If expand is TRUE expand the result. */
541char *data;
542int expand;
543{
544 extern char *tempnam();
545 int bsize;
546 char *buffer;
547 char *tmpnm;
548 FILE *old_stdout_redir = stdout_redir;
549
550 int wait = Wait_for_completion;
551 int old_is_exec_shell = Is_exec_shell;
552 CELLPTR old_Shell_exec_target = Shell_exec_target;
553 uint16 vflag = Verbose;
554 int tflag = Trace;
555 char *res = NIL(char)((char*)((void*)0));
556 CELL cell;
557 STRING rcp;
558 HASH cname;
559
560 if( Suppress_temp_file ) return(NIL(char)((char*)((void*)0)));
561
562 /* Set the temp CELL used for building prerequisite candidates to
563 * all zero so that we don't have to keep initializing all the
564 * fields. */
565 {
566 register char *s = (char *) &cell;
567 register int n = sizeof(CELL);
568 while( n ) { *s++ = '\0'; n--; }
569 }
570 rcp.st_string = DmStrSpn(data, " \t+-%@");
571 rcp.st_attr = Rcp_attribute( data );
572 rcp.st_next = NIL(STRING)((STRING*)((void*)0));
573 cname.ht_name = "Shell escape";
574 cell.ce_name = &cname;
575 cell.ce_all.cl_prq = &cell;
576 cell.ce_all.cl_next = NIL(LINK)((LINK*)((void*)0));
577 cell.ce_all.cl_flag = 0;
578 cell.ce_fname = cname.ht_name;
579 cell.ce_recipe = &rcp;
580 cell.ce_flag = F_TARGET0x0008|F_RULES0x0010;
581 /* Setting A_SILENT supresses the recipe output from Print_cmnd(). */
582 cell.ce_attr = A_PHONY0x04000|A_SILENT0x00002|A_SHELLESC0x40000000;
583
584 if( Measure & M_TARGET0x01 )
585 Do_profile_output( "s", M_TARGET0x01, &cell );
586
587 /* Print the shell escape command. */
588 if( Verbose & V_FORCEECHO0x80 ) {
589 printf( "%s: Executing shell macro: %s\n", Pname, data );
590 fflush(stdoutstdout);
591 }
592
593 if( (stdout_redir = Get_temp(&tmpnm, "w+")) == NIL(FILE)((FILE*)((void*)0)) )
594 Open_temp_error( tmpnm, cname.ht_name );
595
596 bsize = (Buffer_size < BUFSIZ8192)?BUFSIZ8192:Buffer_size;
597 buffer = MALLOC(bsize,char)(char*) malloc((unsigned int)(bsize)*(size_t)sizeof(char));
598
599 /* As this function redirects the output of stdout we have to make sure
600 * that only this single command is executed and all previous recipe lines
601 * that belong to the same target have finished. With Shell_exec_target and
602 * Wait_for_completion set this is realized. Current_target being NIL(CELL)
603 * outside of recipe lines makes sure that no waiting for previous recipe
604 * lines has to be done. */
605 Wait_for_completion = TRUE1;
606 Is_exec_shell = TRUE1;
607 Shell_exec_target = Current_target;
608 Verbose &= V_LEAVE_TMP0x01;
609 Trace = FALSE0;
610
611 /* The actual redirection happens in runargv(). */
612 Exec_commands( &cell );
613
614 Unlink_temp_files( &cell );
615
616 Trace = tflag;
617 Verbose = vflag;
618 Wait_for_completion = wait;
619 Is_exec_shell = old_is_exec_shell;
620 Shell_exec_target = old_Shell_exec_target;
621
622 /* Now we have to read the temporary file, get the tokens and return them
623 * as a string. */
624 rewind(stdout_redir);
625 while( fgets(buffer, bsize, stdout_redir) ) {
626 char *p = strchr(buffer, '\n');
627
628 if( p == NIL(char)((char*)((void*)0)) )
629 res = DmStrJoin(res,buffer,-1,TRUE1);
630 else {
631 *p = '\0';
632 /* You might encounter '\r\n' on windows, handle it. */
633 if( p > buffer && *(p-1) == '\r')
634 *(p-1) = '\0';
635 res = DmStrApp(res,buffer);
636 }
637 }
638
639 fclose(stdout_redir);
640 Remove_file(tmpnm);
641 FREE(tmpnm)free((char*)(tmpnm));
642 FREE(buffer)free((char*)(buffer));
643
644 stdout_redir = old_stdout_redir;
645
646 if ( expand ) {
647 char *exp_res;
648 exp_res = Expand(res);
649 FREE(res)free((char*)(res));
650 res = exp_res;
651 }
652
653 return(res);
654}
655
656
657static char *
658_exec_andor( args, doand )
659char *args;
660int doand;
661{
662 char *next;
663 char *p;
664 char *white = " \t\n";
665 int res=doand;
666
667 args = DmStrSpn(args,white);
668 do {
669 p=ScanToken(args, &next, TRUE1);
670
671 if (doand ? !*p : *p) {
672 res = !doand;
673 FREE(p)free((char*)(p));
674 break;
675 }
676
677 FREE(p)free((char*)(p));
678 }
679 while (*(args=DmStrSpn(next,white)));
680
681 return(res ? DmStrDup("t") : DmStrDup(""));
682}
683
684
685static char *
686_exec_not( args )
687char *args;
688{
689 char *white = " \t\n";
690 char *p=Expand(args);
691 int res = (*DmStrSpn(p,white) == '\0');
692
693 FREE(p)free((char*)(p));
694 return(res ? DmStrDup("t") : DmStrDup(""));
695}
696
697
698char *
699exec_normpath( args )/*
700=======================
701 Normalize token-wise. The normalised filenames are returned in a new
702 string, the original string is not freed. Quoted tokens remain quoted
703 after the normalizaton. */
704char *args;
705{
706 TKSTR str;
707 char *s, *res;
708
709 /* This honors .WINPATH . */
710 SET_TOKEN( &str, args )(&str)->tk_str = (args); (&str)->tk_cchar = *(args
); (&str)->tk_quote = 1;
;
711 res = NIL(char)((char*)((void*)0));
712 while( *(s = Get_token( &str, "", FALSE0 )) != '\0' ) {
713 if(str.tk_quote == 0) {
714 /* Add leading quote. */
715 res = DmStrApp(res, "\"");
716 res = DmStrJoin(res, DO_WINPATH(normalize_path(s))normalize_path(s), -1, TRUE1);
717 /* Append the trailing quote. */
718 res = DmStrJoin(res, "\"", 1, TRUE1);
719 } else {
720 res = DmStrApp(res, DO_WINPATH(normalize_path(s))normalize_path(s));
721 }
722 }
723 return res;
724}