xref: /dragonfly/contrib/tcsh-6/tc.prompt.c (revision 6ca88057)
1 /* $Header: /p/tcsh/cvsroot/tcsh/tc.prompt.c,v 3.71 2014/08/23 09:07:57 christos Exp $ */
2 /*
3  * tc.prompt.c: Prompt printing stuff
4  */
5 /*-
6  * Copyright (c) 1980, 1991 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer in the
16  *    documentation and/or other materials provided with the distribution.
17  * 3. Neither the name of the University nor the names of its contributors
18  *    may be used to endorse or promote products derived from this software
19  *    without specific prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
22  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
25  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
27  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
28  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
30  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
31  * SUCH DAMAGE.
32  */
33 #include "sh.h"
34 
35 RCSID("$tcsh: tc.prompt.c,v 3.71 2014/08/23 09:07:57 christos Exp $")
36 
37 #include "ed.h"
38 #include "tw.h"
39 
40 /*
41  * kfk 21oct1983 -- add @ (time) and / ($cwd) in prompt.
42  * PWP 4/27/87 -- rearange for tcsh.
43  * mrdch@com.tau.edu.il 6/26/89 - added ~, T and .# - rearanged to switch()
44  *                 instead of if/elseif
45  * Luke Mewburn, <lukem@cs.rmit.edu.au>
46  *	6-Sep-91	changed date format
47  *	16-Feb-94	rewrote directory prompt code, added $ellipsis
48  *	29-Dec-96	added rprompt support
49  */
50 
51 static const char   *month_list[12];
52 static const char   *day_list[7];
53 
54 void
55 dateinit(void)
56 {
57 #ifdef notyet
58   int i;
59 
60   setlocale(LC_TIME, "");
61 
62   for (i = 0; i < 12; i++)
63       xfree((ptr_t) month_list[i]);
64   month_list[0] = strsave(_time_info->abbrev_month[0]);
65   month_list[1] = strsave(_time_info->abbrev_month[1]);
66   month_list[2] = strsave(_time_info->abbrev_month[2]);
67   month_list[3] = strsave(_time_info->abbrev_month[3]);
68   month_list[4] = strsave(_time_info->abbrev_month[4]);
69   month_list[5] = strsave(_time_info->abbrev_month[5]);
70   month_list[6] = strsave(_time_info->abbrev_month[6]);
71   month_list[7] = strsave(_time_info->abbrev_month[7]);
72   month_list[8] = strsave(_time_info->abbrev_month[8]);
73   month_list[9] = strsave(_time_info->abbrev_month[9]);
74   month_list[10] = strsave(_time_info->abbrev_month[10]);
75   month_list[11] = strsave(_time_info->abbrev_month[11]);
76 
77   for (i = 0; i < 7; i++)
78       xfree((ptr_t) day_list[i]);
79   day_list[0] = strsave(_time_info->abbrev_wkday[0]);
80   day_list[1] = strsave(_time_info->abbrev_wkday[1]);
81   day_list[2] = strsave(_time_info->abbrev_wkday[2]);
82   day_list[3] = strsave(_time_info->abbrev_wkday[3]);
83   day_list[4] = strsave(_time_info->abbrev_wkday[4]);
84   day_list[5] = strsave(_time_info->abbrev_wkday[5]);
85   day_list[6] = strsave(_time_info->abbrev_wkday[6]);
86 #else
87   month_list[0] = "Jan";
88   month_list[1] = "Feb";
89   month_list[2] = "Mar";
90   month_list[3] = "Apr";
91   month_list[4] = "May";
92   month_list[5] = "Jun";
93   month_list[6] = "Jul";
94   month_list[7] = "Aug";
95   month_list[8] = "Sep";
96   month_list[9] = "Oct";
97   month_list[10] = "Nov";
98   month_list[11] = "Dec";
99 
100   day_list[0] = "Sun";
101   day_list[1] = "Mon";
102   day_list[2] = "Tue";
103   day_list[3] = "Wed";
104   day_list[4] = "Thu";
105   day_list[5] = "Fri";
106   day_list[6] = "Sat";
107 #endif
108 }
109 
110 void
111 printprompt(int promptno, const char *str)
112 {
113     static  const Char *ocp = NULL;
114     static  const char *ostr = NULL;
115     time_t  lclock = time(NULL);
116     const Char *cp;
117 
118     switch (promptno) {
119     default:
120     case 0:
121 	cp = varval(STRprompt);
122 	break;
123     case 1:
124 	cp = varval(STRprompt2);
125 	break;
126     case 2:
127 	cp = varval(STRprompt3);
128 	break;
129     case 3:
130 	if (ocp != NULL) {
131 	    cp = ocp;
132 	    str = ostr;
133 	}
134 	else
135 	    cp = varval(STRprompt);
136 	break;
137     }
138 
139     if (promptno < 2) {
140 	ocp = cp;
141 	ostr = str;
142     }
143 
144     xfree(Prompt);
145     Prompt = NULL;
146     Prompt = tprintf(FMT_PROMPT, cp, str, lclock, NULL);
147     if (!editing) {
148 	for (cp = Prompt; *cp ; )
149 	    (void) putwraw(*cp++);
150 	SetAttributes(0);
151 	flush();
152     }
153 
154     xfree(RPrompt);
155     RPrompt = NULL;
156     if (promptno == 0) {	/* determine rprompt if using main prompt */
157 	cp = varval(STRrprompt);
158 	RPrompt = tprintf(FMT_PROMPT, cp, NULL, lclock, NULL);
159 				/* if not editing, put rprompt after prompt */
160 	if (!editing && RPrompt[0] != '\0') {
161 	    for (cp = RPrompt; *cp ; )
162 		(void) putwraw(*cp++);
163 	    SetAttributes(0);
164 	    putraw(' ');
165 	    flush();
166 	}
167     }
168 }
169 
170 static void
171 tprintf_append_mbs(struct Strbuf *buf, const char *mbs, Char attributes)
172 {
173     while (*mbs != 0) {
174 	Char wc;
175 
176 	mbs += one_mbtowc(&wc, mbs, MB_LEN_MAX);
177 	Strbuf_append1(buf, wc | attributes);
178     }
179 }
180 
181 Char *
182 tprintf(int what, const Char *fmt, const char *str, time_t tim, ptr_t info)
183 {
184     struct Strbuf buf = Strbuf_INIT;
185     Char   *z, *q;
186     Char    attributes = 0;
187     static int print_prompt_did_ding = 0;
188     char *cz;
189 
190     Char *p;
191     const Char *cp = fmt;
192     Char Scp;
193     struct tm *t = localtime(&tim);
194 
195 			/* prompt stuff */
196     static Char *olduser = NULL;
197     int updirs;
198     size_t pdirs;
199 
200     cleanup_push(&buf, Strbuf_cleanup);
201     for (; *cp; cp++) {
202 	if ((*cp == '%') && ! (cp[1] == '\0')) {
203 	    cp++;
204 	    switch (*cp) {
205 	    case 'R':
206 		if (what == FMT_HISTORY) {
207 		    cz = fmthist('R', info);
208 		    tprintf_append_mbs(&buf, cz, attributes);
209 		    xfree(cz);
210 		} else {
211 		    if (str != NULL)
212 			tprintf_append_mbs(&buf, str, attributes);
213 		}
214 		break;
215 	    case '#':
216 #ifdef __CYGWIN__
217 		/* Check for being member of the Administrators group */
218 		{
219 			gid_t grps[NGROUPS_MAX];
220 			int grp, gcnt;
221 
222 			gcnt = getgroups(NGROUPS_MAX, grps);
223 # define DOMAIN_GROUP_RID_ADMINS 544
224 			for (grp = 0; grp < gcnt; ++grp)
225 				if (grps[grp] == DOMAIN_GROUP_RID_ADMINS)
226 					break;
227 			Scp = (grp < gcnt) ? PRCHROOT : PRCH;
228 		}
229 #else
230 		Scp = (uid == 0 || euid == 0) ? PRCHROOT : PRCH;
231 #endif
232 		if (Scp != '\0')
233 		    Strbuf_append1(&buf, attributes | Scp);
234 		break;
235 	    case '!':
236 	    case 'h':
237 		switch (what) {
238 		case FMT_HISTORY:
239 		    cz = fmthist('h', info);
240 		    break;
241 		case FMT_SCHED:
242 		    cz = xasprintf("%d", *(int *)info);
243 		    break;
244 		default:
245 		    cz = xasprintf("%d", eventno + 1);
246 		    break;
247 		}
248 		tprintf_append_mbs(&buf, cz, attributes);
249 		xfree(cz);
250 		break;
251 	    case 'T':		/* 24 hour format	 */
252 	    case '@':
253 	    case 't':		/* 12 hour am/pm format */
254 	    case 'p':		/* With seconds	*/
255 	    case 'P':
256 		{
257 		    char    ampm = 'a';
258 		    int     hr = t->tm_hour;
259 
260 		    /* addition by Hans J. Albertsson */
261 		    /* and another adapted from Justin Bur */
262 		    if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
263 			if (hr >= 12) {
264 			    if (hr > 12)
265 				hr -= 12;
266 			    ampm = 'p';
267 			}
268 			else if (hr == 0)
269 			    hr = 12;
270 		    }		/* else do a 24 hour clock */
271 
272 		    /* "DING!" stuff by Hans also */
273 		    if (t->tm_min || print_prompt_did_ding ||
274 			what != FMT_PROMPT || adrof(STRnoding)) {
275 			if (t->tm_min)
276 			    print_prompt_did_ding = 0;
277 			/*
278 			 * Pad hour to 2 characters if padhour is set,
279 			 * by ADAM David Alan Martin
280 			 */
281 			p = Itoa(hr, adrof(STRpadhour) ? 2 : 0, attributes);
282 			Strbuf_append(&buf, p);
283 			xfree(p);
284 			Strbuf_append1(&buf, attributes | ':');
285 			p = Itoa(t->tm_min, 2, attributes);
286 			Strbuf_append(&buf, p);
287 			xfree(p);
288 			if (*cp == 'p' || *cp == 'P') {
289 			    Strbuf_append1(&buf, attributes | ':');
290 			    p = Itoa(t->tm_sec, 2, attributes);
291 			    Strbuf_append(&buf, p);
292 			    xfree(p);
293 			}
294 			if (adrof(STRampm) || (*cp != 'T' && *cp != 'P')) {
295 			    Strbuf_append1(&buf, attributes | ampm);
296 			    Strbuf_append1(&buf, attributes | 'm');
297 			}
298 		    }
299 		    else {	/* we need to ding */
300 			size_t i;
301 
302 			for (i = 0; STRDING[i] != 0; i++)
303 			    Strbuf_append1(&buf, attributes | STRDING[i]);
304 			print_prompt_did_ding = 1;
305 		    }
306 		}
307 		break;
308 
309 	    case 'M':
310 #ifndef HAVENOUTMP
311 		if (what == FMT_WHO)
312 		    cz = who_info(info, 'M');
313 		else
314 #endif /* HAVENOUTMP */
315 		    cz = getenv("HOST");
316 		/*
317 		 * Bug pointed out by Laurent Dami <dami@cui.unige.ch>: don't
318 		 * derefrence that NULL (if HOST is not set)...
319 		 */
320 		if (cz != NULL)
321 		    tprintf_append_mbs(&buf, cz, attributes);
322 		if (what == FMT_WHO)
323 		    xfree(cz);
324 		break;
325 
326 	    case 'm': {
327 		char *scz = NULL;
328 #ifndef HAVENOUTMP
329 		if (what == FMT_WHO)
330 		    scz = cz = who_info(info, 'm');
331 		else
332 #endif /* HAVENOUTMP */
333 		    cz = getenv("HOST");
334 
335 		if (cz != NULL)
336 		    while (*cz != 0 && (what == FMT_WHO || *cz != '.')) {
337 			Char wc;
338 
339 			cz += one_mbtowc(&wc, cz, MB_LEN_MAX);
340 			Strbuf_append1(&buf, wc | attributes);
341 		    }
342 		if (scz)
343 		    xfree(scz);
344 		break;
345 	    }
346 
347 			/* lukem: new directory prompt code */
348 	    case '~':
349 	    case '/':
350 	    case '.':
351 	    case 'c':
352 	    case 'C':
353 		Scp = *cp;
354 		if (Scp == 'c')		/* store format type (c == .) */
355 		    Scp = '.';
356 		if ((z = varval(STRcwd)) == STRNULL)
357 		    break;		/* no cwd, so don't do anything */
358 
359 			/* show ~ whenever possible - a la dirs */
360 		if (Scp == '~' || Scp == '.' ) {
361 		    static Char *olddir = NULL;
362 
363 		    if (tlength == 0 || olddir != z) {
364 			olddir = z;		/* have we changed dir? */
365 			olduser = getusername(&olddir);
366 		    }
367 		    if (olduser)
368 			z = olddir;
369 		}
370 		updirs = pdirs = 0;
371 
372 			/* option to determine fixed # of dirs from path */
373 		if (Scp == '.' || Scp == 'C') {
374 		    int skip;
375 #ifdef WINNT_NATIVE
376 		    Char *oldz = z;
377 		    if (z[1] == ':') {
378 			Strbuf_append1(&buf, attributes | *z++);
379 			Strbuf_append1(&buf, attributes | *z++);
380 		    }
381 		    if (*z == '/' && z[1] == '/') {
382 			Strbuf_append1(&buf, attributes | *z++);
383 			Strbuf_append1(&buf, attributes | *z++);
384 			do {
385 			    Strbuf_append1(&buf, attributes | *z++);
386 			} while(*z != '/');
387 		    }
388 #endif /* WINNT_NATIVE */
389 		    q = z;
390 		    while (*z)				/* calc # of /'s */
391 			if (*z++ == '/')
392 			    updirs++;
393 
394 #ifdef WINNT_NATIVE
395 		    /*
396 		     * for format type c, prompt will be following...
397 		     * c:/path                => c:/path
398 		     * c:/path/to             => c:to
399 		     * //machine/share        => //machine/share
400 		     * //machine/share/folder => //machine:folder
401 		     */
402 		    if (oldz[0] == '/' && oldz[1] == '/' && updirs > 1)
403 			Strbuf_append1(&buf, attributes | ':');
404 #endif /* WINNT_NATIVE */
405 		    if ((Scp == 'C' && *q != '/'))
406 			updirs++;
407 
408 		    if (cp[1] == '0') {			/* print <x> or ...  */
409 			pdirs = 1;
410 			cp++;
411 		    }
412 		    if (cp[1] >= '1' && cp[1] <= '9') {	/* calc # to skip  */
413 			skip = cp[1] - '0';
414 			cp++;
415 		    }
416 		    else
417 			skip = 1;
418 
419 		    updirs -= skip;
420 		    while (skip-- > 0) {
421 			while ((z > q) && (*z != '/'))
422 			    z--;			/* back up */
423 			if (skip && z > q)
424 			    z--;
425 		    }
426 		    if (*z == '/' && z != q)
427 			z++;
428 		} /* . || C */
429 
430 							/* print ~[user] */
431 		if ((olduser) && ((Scp == '~') ||
432 		     (Scp == '.' && (pdirs || (!pdirs && updirs <= 0))) )) {
433 		    Strbuf_append1(&buf, attributes | '~');
434 		    for (q = olduser; *q; q++)
435 			Strbuf_append1(&buf, attributes | *q);
436 		}
437 
438 			/* RWM - tell you how many dirs we've ignored */
439 			/*       and add '/' at front of this         */
440 		if (updirs > 0 && pdirs) {
441 		    if (adrof(STRellipsis)) {
442 			Strbuf_append1(&buf, attributes | '.');
443 			Strbuf_append1(&buf, attributes | '.');
444 			Strbuf_append1(&buf, attributes | '.');
445 		    } else {
446 			Strbuf_append1(&buf, attributes | '/');
447 			Strbuf_append1(&buf, attributes | '<');
448 			if (updirs > 9) {
449 			    Strbuf_append1(&buf, attributes | '9');
450 			    Strbuf_append1(&buf, attributes | '+');
451 			} else
452 			    Strbuf_append1(&buf, attributes | ('0' + updirs));
453 			Strbuf_append1(&buf, attributes | '>');
454 		    }
455 		}
456 
457 		while (*z)
458 		    Strbuf_append1(&buf, attributes | *z++);
459 		break;
460 			/* lukem: end of new directory prompt code */
461 
462 	    case 'n':
463 #ifndef HAVENOUTMP
464 		if (what == FMT_WHO) {
465 		    cz = who_info(info, 'n');
466 		    tprintf_append_mbs(&buf, cz, attributes);
467 		    xfree(cz);
468 		}
469 		else
470 #endif /* HAVENOUTMP */
471 		{
472 		    if ((z = varval(STRuser)) != STRNULL)
473 			while (*z)
474 			    Strbuf_append1(&buf, attributes | *z++);
475 		}
476 		break;
477 	    case 'N':
478 		if ((z = varval(STReuser)) != STRNULL)
479 		    while (*z)
480 			Strbuf_append1(&buf, attributes | *z++);
481 		break;
482 	    case 'l':
483 #ifndef HAVENOUTMP
484 		if (what == FMT_WHO) {
485 		    cz = who_info(info, 'l');
486 		    tprintf_append_mbs(&buf, cz, attributes);
487 		    xfree(cz);
488 		}
489 		else
490 #endif /* HAVENOUTMP */
491 		{
492 		    if ((z = varval(STRtty)) != STRNULL)
493 			while (*z)
494 			    Strbuf_append1(&buf, attributes | *z++);
495 		}
496 		break;
497 	    case 'd':
498 		tprintf_append_mbs(&buf, day_list[t->tm_wday], attributes);
499 		break;
500 	    case 'D':
501 		p = Itoa(t->tm_mday, 2, attributes);
502 		Strbuf_append(&buf, p);
503 		xfree(p);
504 		break;
505 	    case 'w':
506 		tprintf_append_mbs(&buf, month_list[t->tm_mon], attributes);
507 		break;
508 	    case 'W':
509 		p = Itoa(t->tm_mon + 1, 2, attributes);
510 		Strbuf_append(&buf, p);
511 		xfree(p);
512 		break;
513 	    case 'y':
514 		p = Itoa(t->tm_year % 100, 2, attributes);
515 		Strbuf_append(&buf, p);
516 		xfree(p);
517 		break;
518 	    case 'Y':
519 		p = Itoa(t->tm_year + 1900, 4, attributes);
520 		Strbuf_append(&buf, p);
521 		xfree(p);
522 		break;
523 	    case 'S':		/* start standout */
524 		attributes |= STANDOUT;
525 		break;
526 	    case 'B':		/* start bold */
527 		attributes |= BOLD;
528 		break;
529 	    case 'U':		/* start underline */
530 		attributes |= UNDER;
531 		break;
532 	    case 's':		/* end standout */
533 		attributes &= ~STANDOUT;
534 		break;
535 	    case 'b':		/* end bold */
536 		attributes &= ~BOLD;
537 		break;
538 	    case 'u':		/* end underline */
539 		attributes &= ~UNDER;
540 		break;
541 	    case 'L':
542 		ClearToBottom();
543 		break;
544 
545 	    case 'j':
546 		{
547 		    int njobs = -1;
548 		    struct process *pp;
549 
550 		    for (pp = proclist.p_next; pp; pp = pp->p_next)
551 			njobs++;
552 		    if (njobs == -1)
553 			njobs++;
554 		    p = Itoa(njobs, 1, attributes);
555 		    Strbuf_append(&buf, p);
556 		    xfree(p);
557 		    break;
558 		}
559 	    case '?':
560 		if ((z = varval(STRstatus)) != STRNULL)
561 		    while (*z)
562 			Strbuf_append1(&buf, attributes | *z++);
563 		break;
564 	    case '$':
565 		expdollar(&buf, &cp, attributes);
566 		/* cp should point the last char of current % sequence */
567 		cp--;
568 		break;
569 	    case '%':
570 		Strbuf_append1(&buf, attributes | '%');
571 		break;
572 	    case '{':		/* literal characters start */
573 #if LITERAL == 0
574 		/*
575 		 * No literal capability, so skip all chars in the literal
576 		 * string
577 		 */
578 		while (*cp != '\0' && (cp[-1] != '%' || *cp != '}'))
579 		    cp++;
580 #endif				/* LITERAL == 0 */
581 		attributes |= LITERAL;
582 		break;
583 	    case '}':		/* literal characters end */
584 		attributes &= ~LITERAL;
585 		break;
586 	    default:
587 #ifndef HAVENOUTMP
588 		if (*cp == 'a' && what == FMT_WHO) {
589 		    cz = who_info(info, 'a');
590 		    tprintf_append_mbs(&buf, cz, attributes);
591 		    xfree(cz);
592 		}
593 		else
594 #endif /* HAVENOUTMP */
595 		{
596 		    Strbuf_append1(&buf, attributes | '%');
597 		    Strbuf_append1(&buf, attributes | *cp);
598 		}
599 		break;
600 	    }
601 	}
602 	else if (*cp == '\\' || *cp == '^')
603 	    Strbuf_append1(&buf, attributes | parseescape(&cp));
604 	else if (*cp == HIST) {	/* EGS: handle '!'s in prompts */
605 	    if (what == FMT_HISTORY)
606 		cz = fmthist('h', info);
607 	    else
608 		cz = xasprintf("%d", eventno + 1);
609 	    tprintf_append_mbs(&buf, cz, attributes);
610 	    xfree(cz);
611 	}
612 	else
613 	    Strbuf_append1(&buf, attributes | *cp); /* normal character */
614     }
615     cleanup_ignore(&buf);
616     cleanup_until(&buf);
617     return Strbuf_finish(&buf);
618 }
619 
620 int
621 expdollar(struct Strbuf *buf, const Char **srcp, Char attr)
622 {
623     struct varent *vp;
624     const Char *src = *srcp;
625     Char *var, *val;
626     size_t i;
627     int curly = 0;
628 
629     /* found a variable, expand it */
630     var = xmalloc((Strlen(src) + 1) * sizeof (*var));
631     for (i = 0; ; i++) {
632 	var[i] = *++src & TRIM;
633 	if (i == 0 && var[i] == '{') {
634 	    curly = 1;
635 	    var[i] = *++src & TRIM;
636 	}
637 	if (!alnum(var[i]) && var[i] != '_') {
638 
639 	    var[i] = '\0';
640 	    break;
641 	}
642     }
643     if (curly && (*src & TRIM) == '}')
644 	src++;
645 
646     vp = adrof(var);
647     if (vp && vp->vec) {
648 	for (i = 0; vp->vec[i] != NULL; i++) {
649 	    for (val = vp->vec[i]; *val; val++)
650 		if (*val != '\n' && *val != '\r')
651 		    Strbuf_append1(buf, *val | attr);
652 	    if (vp->vec[i+1])
653 		Strbuf_append1(buf, ' ' | attr);
654 	}
655     }
656     else {
657 	val = (!vp) ? tgetenv(var) : NULL;
658 	if (val) {
659 	    for (; *val; val++)
660 		if (*val != '\n' && *val != '\r')
661 		    Strbuf_append1(buf, *val | attr);
662 	} else {
663 	    *srcp = src;
664 	    xfree(var);
665 	    return 0;
666 	}
667     }
668 
669     *srcp = src;
670     xfree(var);
671     return 1;
672 }
673