xref: /original-bsd/usr.sbin/amd/amd/util.c (revision 2281fc9b)
1 /*
2  * $Id: util.c,v 5.2 90/06/23 22:20:06 jsp Rel $
3  *
4  * Copyright (c) 1990 Jan-Simon Pendry
5  * Copyright (c) 1990 Imperial College of Science, Technology & Medicine
6  * Copyright (c) 1990 The Regents of the University of California.
7  * All rights reserved.
8  *
9  * This code is derived from software contributed to Berkeley by
10  * Jan-Simon Pendry at Imperial College, London.
11  *
12  * %sccs.include.redist.c%
13  *
14  *	@(#)util.c	5.1 (Berkeley) 06/29/90
15  */
16 
17 /*
18  * Utils
19  */
20 
21 #include "am.h"
22 #ifdef HAS_SYSLOG
23 #include <syslog.h>
24 #endif /* HAS_SYSLOG */
25 #include <ctype.h>
26 #include <sys/stat.h>
27 
28 #include <netdb.h>
29 
30 
31 INLINE
32 char *strnsave(str, len)
33 const char *str;
34 int len;
35 {
36 	char *sp = (char *) xmalloc(len+1);
37 
38 	bcopy(str, sp, len);
39 	sp[len] = 0;
40 
41 	return sp;
42 }
43 
44 char *strdup(s)
45 const char *s;
46 {
47 	return strnsave(s, strlen(s));
48 }
49 
50 /*
51  * Concatenate three strings and store in buffer pointed to
52  * by p, making p large enough to hold the strings
53  */
54 char *str3cat(p, s1, s2, s3)
55 char *p;
56 char *s1;
57 char *s2;
58 char *s3;
59 {
60 	int l1 = strlen(s1);
61 	int l2 = strlen(s2);
62 	int l3 = strlen(s3);
63 	p = (char *) xrealloc(p, l1 + l2 + l3 + 1);
64 	bcopy(s1, p, l1);
65 	bcopy(s2, p + l1, l2);
66 	bcopy(s3, p + l1 + l2, l3 + 1);
67 	return p;
68 }
69 
70 char *strealloc(p, s)
71 char *p;
72 char *s;
73 {
74 	int len = strlen(s) + 1;
75 
76 	p = (char *) xrealloc((voidp) p, len);
77 
78 	strcpy(p, s);
79 #ifdef DEBUG_MEM
80 	malloc_verify();
81 #endif /* DEBUG_MEM */
82 	return p;
83 }
84 
85 voidp xrealloc(ptr, len)
86 voidp ptr;
87 int len;
88 {
89 #if defined(DEBUG) && defined(DEBUG_MEM)
90 	Debug(D_MEM) plog(XLOG_DEBUG, "Reallocated size %d; block %#x", len, ptr);
91 #endif /* defined(DEBUG) && defined(DEBUG_MEM) */
92 
93 	if (ptr)
94 		ptr = (voidp) realloc(ptr, (unsigned) len);
95 	else
96 		ptr = (voidp) xmalloc((unsigned) len);
97 
98 	if (!ptr) {
99 		plog(XLOG_FATAL, "Out of memory in realloc");
100 		going_down(1);
101 		abort();
102 	}
103 	return ptr;
104 }
105 
106 char **strsplit(s, qc)
107 char *s;
108 int qc;
109 {
110 	char **ivec;
111 	int ic = 0;
112 	int done = 0;
113 
114 	ivec = (char **) xmalloc((ic+1)*sizeof(char *));
115 
116 	while (!done) {
117 		char *v;
118 		/*
119 		 * skip white space
120 		 */
121 		while (*s && isascii(*s) && isspace(*s))
122 			s++;
123 
124 		/*
125 		 * End of string?
126 		 */
127 		if (!*s)
128 			break;
129 
130 		/*
131 		 * remember start of string
132 		 */
133 		v = s;
134 
135 		/*
136 		 * skip to white space
137 		 */
138 		while (*s && (!isascii(*s) || !isspace(*s))) {
139 			if (*s++ == qc) {
140 				/*
141 				 * Skip past string.
142 				 */
143 				s++;
144 				while (*s && *s != qc)
145 				 	s++;
146 				if (*s == qc)
147 					s++;
148 			}
149 		}
150 
151 		if (!*s)
152 			done = 1;
153 		*s++ = '\0';
154 
155 		/*
156 		 * save string in new ivec slot
157 		 */
158 		ivec[ic++] = v;
159 		ivec = (char **) xrealloc(ivec, (ic+1)*sizeof(char *));
160 #ifdef DEBUG
161 		Debug(D_STR)
162 			plog(XLOG_DEBUG, "strsplit saved \"%s\"", v);
163 #endif /* DEBUG */
164 	}
165 
166 #ifdef DEBUG
167 	Debug(D_STR)
168 		plog(XLOG_DEBUG, "strsplit saved a total of %d strings", ic);
169 #endif /* DEBUG */
170 
171 	ivec[ic] = 0;
172 
173 	return ivec;
174 }
175 
176 /*
177  * Strip off the trailing part of a domain
178  * to produce a short-form domain relative
179  * to the local host domain.
180  * Note that this has no effect if the domain
181  * names do not have the same number of
182  * components.  If that restriction proves
183  * to be a problem then the loop needs recoding
184  * to skip from right to left and do partial
185  * matches along the way -- ie more expensive.
186  */
187 static void domain_strip P((char *otherdom, char *localdom));
188 static void domain_strip(otherdom, localdom)
189 char *otherdom, *localdom;
190 {
191         char *p1 = otherdom-1;
192 	char *p2 = localdom-1;
193 
194         do {
195                 if (p1 = strchr(p1+1, '.'))
196                 if (p2 = strchr(p2+1, '.'))
197                 if (strcmp(p1+1, p2+1) == 0) {
198                         *p1 = '\0';
199                         break;
200                 }
201         } while (p1 && p2);
202 }
203 
204 /*
205  * Normalize a host name
206  */
207 void host_normalize P((char **chp));
208 void host_normalize(chp)
209 char **chp;
210 {
211 	/*
212 	 * Normalize hosts is used to resolve host name aliases
213 	 * and replace them with the standard-form name.
214 	 * Invoked with "-n" command line option.
215 	 */
216 	if (normalize_hosts) {
217 		struct hostent *hp;
218 		clock_valid = 0;
219 		hp = gethostbyname(*chp);
220 		if (hp && hp->h_addrtype == AF_INET) {
221 #ifdef DEBUG
222 			dlog("Hostname %s normalized to %s", *chp, hp->h_name);
223 #endif /* DEBUG */
224 			*chp = strealloc(*chp, hp->h_name);
225 		}
226 	}
227 	domain_strip(*chp, hostd);
228 }
229 
230 /*
231  * Keys are not allowed to contain " ' ! or ; to avoid
232  * problems with macro expansions.
233  */
234 static char invalid_keys[] = "\"'!;@ \t\n";
235 int valid_key P((char *key));
236 int valid_key(key)
237 char *key;
238 {
239 	while (*key)
240 		if (strchr(invalid_keys, *key++))
241 			return FALSE;
242 	return TRUE;
243 }
244 
245 void going_down P((int rc));
246 void going_down(rc)
247 int rc;
248 {
249 	if (foreground) {
250 		if (amd_state != Start) {
251 			if (amd_state != Done)
252 				return;
253 			unregister_amq();
254 		}
255 	}
256 	if (foreground) {
257 		plog(XLOG_INFO, "Finishing with status %d", rc);
258 	} else {
259 #ifdef DEBUG
260 		dlog("background process exiting with status %d", rc);
261 #endif /* DEBUG */
262 	}
263 
264 	exit(rc);
265 }
266 
267 #ifdef DEBUG_MEM
268 static int mem_bytes;
269 static int orig_mem_bytes;
270 static void checkup_mem(P_void)
271 {
272 extern struct mallinfo __mallinfo;
273 	if (mem_bytes != __mallinfo.uordbytes) {
274 		if (orig_mem_bytes == 0)
275 			mem_bytes = orig_mem_bytes = __mallinfo.uordbytes;
276 		else {
277 			fprintf(logfp, "%s[%d]: ", progname, mypid);
278 			if (mem_bytes < __mallinfo.uordbytes) {
279 				fprintf(logfp, "ALLOC: %d bytes",
280 					__mallinfo.uordbytes - mem_bytes);
281 			} else {
282 				fprintf(logfp, "FREE: %d bytes",
283 					mem_bytes - __mallinfo.uordbytes);
284 			}
285 			mem_bytes = __mallinfo.uordbytes;
286 			fprintf(logfp, ", making %d missing\n",
287 				mem_bytes - orig_mem_bytes);
288 		}
289 	}
290 	malloc_verify();
291 }
292 #endif /* DEBUG_MEM */
293 
294 /*
295  * Take a log format string and expand occurences of %m
296  * with the current error code take from errno.
297  */
298 INLINE
299 static void expand_error(f, e)
300 char *f;
301 char *e;
302 {
303 	extern int sys_nerr;
304 	extern char *sys_errlist[];
305 	char *p;
306 	int error = errno;
307 
308 	for (p = f; *e = *p; e++, p++) {
309 		if (p[0] == '%' && p[1] == 'm') {
310 			char *errstr;
311 			if (error < 0 || error >= sys_nerr)
312 				errstr = 0;
313 			else
314 				errstr = sys_errlist[error];
315 			if (errstr)
316 				strcpy(e, errstr);
317 			else
318 				sprintf(e, "Error %d", error);
319 			e += strlen(e) - 1;
320 			p++;
321 		}
322 	}
323 }
324 
325 /*
326  * Output the time of day and hostname to the logfile
327  */
328 static void show_time_host_and_name(lvl)
329 int lvl;
330 {
331 static time_t last_t = 0;
332 static char *last_ctime = 0;
333 	time_t t = clocktime();
334 	char *sev;
335 	extern char *ctime();
336 
337 #if defined(DEBUG) && defined(PARANOID)
338 extern char **gargv;
339 #endif /* defined(DEBUG) && defined(PARANOID) */
340 
341 	if (t != last_t) {
342 		last_ctime = ctime(&t);
343 		last_t = t;
344 	}
345 
346 	switch (lvl) {
347 	case XLOG_FATAL:	sev = "fatal:"; break;
348 	case XLOG_ERROR: 	sev = "error:"; break;
349 	case XLOG_USER:		sev = "user: "; break;
350 	case XLOG_WARNING:	sev = "warn: "; break;
351 	case XLOG_INFO:		sev = "info: "; break;
352 	case XLOG_DEBUG:	sev = "debug:"; break;
353 	case XLOG_MAP:		sev = "map:  "; break;
354 	case XLOG_STATS:	sev = "stats:"; break;
355 	default:		sev = "hmm:  "; break;
356 	}
357 	fprintf(logfp, "%15.15s %s %s[%d]/%s ",
358 		last_ctime+4, hostname,
359 #if defined(DEBUG) && defined(PARANOID)
360 		gargv[0],
361 #else
362 		progname,
363 #endif /* defined(DEBUG) && defined(PARANOID) */
364 		mypid,
365 		sev);
366 }
367 
368 #ifdef DEBUG
369 /*VARARGS1*/
370 void dplog(fmt, j,s,_,p,e,n,d,r,y)
371 char *fmt;
372 char *j, *s, *_, *p, *e, *n, *d, *r, *y;
373 {
374 	plog(XLOG_DEBUG, fmt, j,s,_,p,e,n,d,r,y);
375 }
376 
377 #endif /* DEBUG */
378 /*VARARGS1*/
379 void plog(lvl, fmt, j,s,_,p,e,n,d,r,y)
380 int lvl;
381 char *fmt;
382 char *j, *s, *_, *p, *e, *n, *d, *r, *y;
383 {
384 	char msg[1024];
385 	char efmt[1024];
386 	char *ptr = msg;
387 
388 	if (!(xlog_level & lvl))
389 		return;
390 
391 #ifdef DEBUG_MEM
392 	checkup_mem();
393 #endif /* DEBUG_MEM */
394 
395 	expand_error(fmt, efmt);
396 	sprintf(ptr, efmt, j,s,_,p,e,n,d,r,y);
397 	ptr += strlen(ptr);
398 	if (ptr[-1] == '\n')
399 		*--ptr  = '\0';
400 #ifdef HAS_SYSLOG
401 	if (syslogging) {
402 		switch(lvl) {	/* from mike <mcooper@usc.edu> */
403 		case XLOG_FATAL:	lvl = LOG_CRIT; break;
404 		case XLOG_ERROR: 	lvl = LOG_ERR; break;
405 		case XLOG_USER:		lvl = LOG_WARNING; break;
406 		case XLOG_WARNING:	lvl = LOG_WARNING; break;
407 		case XLOG_INFO:		lvl = LOG_INFO; break;
408 		case XLOG_DEBUG:	lvl = LOG_DEBUG; break;
409 		case XLOG_MAP:		lvl = LOG_DEBUG; break;
410 		case XLOG_STATS:	lvl = LOG_INFO; break;
411 		default:		lvl = LOG_ERR; break;
412 		}
413 		syslog(lvl, "%s", msg);
414 		return;
415 	}
416 #endif /* HAS_SYSLOG */
417 
418 	*ptr++ = '\n';
419 	*ptr = '\0';
420 
421 	/*
422 	 * Mimic syslog header
423 	 */
424 	show_time_host_and_name(lvl);
425 	fwrite(msg, ptr - msg, 1, logfp);
426 	fflush(logfp);
427 }
428 
429 int bind_resv_port P((int so, u_short *pp));
430 int bind_resv_port(so, pp)
431 int so;
432 u_short *pp;
433 {
434 	struct sockaddr_in sin;
435 	int rc;
436 	unsigned short port;
437 
438 	bzero((voidp) &sin, sizeof(sin));
439 	sin.sin_family = AF_INET;
440 
441 	port = IPPORT_RESERVED;
442 
443 	do {
444 		--port;
445 		sin.sin_port = htons(port);
446 		rc = bind(so, (struct sockaddr *) &sin, sizeof(sin));
447 	} while (rc < 0 && port > IPPORT_RESERVED/2);
448 
449 	if (pp && rc == 0)
450 		*pp = port;
451 	return rc;
452 }
453 
454 void forcibly_timeout_mp P((am_node *mp));
455 void forcibly_timeout_mp(mp)
456 am_node *mp;
457 {
458 	mntfs *mf = mp->am_mnt;
459 	/*
460 	 * Arrange to timeout this node
461 	 */
462 	if (mf && ((mp->am_flags & AMF_ROOT) ||
463 		(mf->mf_flags & (MFF_MOUNTING|MFF_UNMOUNTING)))) {
464 		if (!(mf->mf_flags & MFF_UNMOUNTING))
465 			plog(XLOG_WARNING, "ignoring timeout request for active node %s", mp->am_path);
466 	} else {
467 		plog(XLOG_INFO, "\"%s\" forcibly timed out", mp->am_path);
468 		mp->am_flags &= ~AMF_NOTIMEOUT;
469 		mp->am_ttl = clocktime();
470 		reschedule_timeout_mp();
471 	}
472 }
473 
474 void am_mounted P((am_node *mp));
475 void am_mounted(mp)
476 am_node *mp;
477 {
478 	mntfs *mf = mp->am_mnt;
479 	int quoted;
480 	mf->mf_flags |= MFF_MOUNTED;
481 	mf->mf_error = 0;
482 
483 	/*
484 	 * Patch up path for direct mounts
485 	 */
486 	if (mp->am_parent && mp->am_parent->am_mnt->mf_ops == &dfs_ops)
487 		mp->am_path = str3cat(mp->am_path, mp->am_parent->am_path, "/", ".");
488 
489 	/*
490 	 * Check whether this mount should be cached permanently
491 	 */
492 	if (mf->mf_ops->fs_flags & FS_NOTIMEOUT) {
493 		mp->am_flags |= AMF_NOTIMEOUT;
494 	} else if (mf->mf_mount[1] == '\0' && mf->mf_mount[0] == '/') {
495 		mp->am_flags |= AMF_NOTIMEOUT;
496 	} else {
497 		struct mntent mnt;
498 		mnt.mnt_opts = mf->mf_fo->opt_opts;
499 		if (hasmntopt(&mnt, "nounmount"))
500 			mp->am_flags |= AMF_NOTIMEOUT;
501 		if ((mp->am_timeo = hasmntval(&mnt, "utimeout")) == 0)
502 			mp->am_timeo = am_timeo;
503 /*		if ((mf->mf_server->fs_pinger = hasmntval(&mnt, "ping")) == 0)
504 			mf->mf_server->fs_pinger = AM_PINGER;
505 */
506 	}
507 
508 	/*
509 	 * Do mounted callback
510 	 */
511 	if (mf->mf_ops->mounted)
512 		(*mf->mf_ops->mounted)(mf);
513 
514 	/*
515 	 * If this node is a symlink then
516 	 * compute the length of the returned string.
517 	 */
518 	if (mf->mf_fattr.type == NFLNK)
519 		mf->mf_fattr.size = strlen(mp->am_link ? mp->am_link : mp->am_mnt->mf_mount);
520 
521 	/*
522 	 * Record mount time
523 	 */
524 	mf->mf_fattr.mtime.seconds = mp->am_stats.s_mtime = clocktime();
525 	new_ttl(mp);
526 	/*
527 	 * Update mtime of parent node
528 	 */
529 	if (mp->am_parent && mp->am_parent->am_mnt)
530 		mp->am_parent->am_mnt->mf_fattr.mtime.seconds = mp->am_stats.s_mtime;
531 
532 	/*
533 	 * Log message
534 	 */
535 	quoted = strchr(mf->mf_info, ' ') != 0;
536 	plog(XLOG_INFO, "%s%s%s mounted fstype %s on %s",
537 		quoted ? "\"" : "",
538 		mf->mf_info,
539 		quoted ? "\"" : "",
540 		mf->mf_ops->fs_type, mf->mf_mount);
541 
542 	/*
543 	 * Update stats
544 	 */
545 	amd_stats.d_mok++;
546 }
547 
548 int mount_node P((am_node *mp));
549 int mount_node(mp)
550 am_node *mp;
551 {
552 	mntfs *mf = mp->am_mnt;
553 	int error;
554 
555 	mf->mf_flags |= MFF_MOUNTING;
556 	error = (*mf->mf_ops->mount_fs)(mp);
557 	mf = mp->am_mnt;
558 	mf->mf_flags &= ~MFF_MOUNTING;
559 	if (!error && !(mf->mf_ops->fs_flags & FS_MBACKGROUND)) {
560 		/* ...but see ifs_mount */
561 		am_mounted(mp);
562 	}
563 
564 	return error;
565 }
566 
567 void am_unmounted P((am_node *mp));
568 void am_unmounted(mp)
569 am_node *mp;
570 {
571 	mntfs *mf = mp->am_mnt;
572 
573 	if (!foreground) /* firewall - should never happen */
574 		return;
575 
576 #ifdef DEBUG
577 	/*dlog("in am_unmounted(), foreground = %d", foreground);*/
578 #endif /* DEBUG */
579 
580 	/*
581 	 * Do unmounted callback
582 	 */
583 	if (mf->mf_ops->umounted)
584 		(*mf->mf_ops->umounted)(mp);
585 
586 	/*
587 	 * Update mtime of parent node
588 	 */
589 	if (mp->am_parent && mp->am_parent->am_mnt)
590 		mp->am_parent->am_mnt->mf_fattr.mtime.seconds = clocktime();
591 
592 	free_map(mp);
593 }
594 
595 
596 /*
597  * Fork the automounter
598  *
599  * TODO: Need a better strategy for handling errors
600  */
601 static int dofork(P_void);
602 INLINE
603 static int dofork()
604 {
605 	int pid;
606 top:
607 	pid = fork();
608 
609 	if (pid < 0) {
610 		sleep(1);
611 		goto top;
612 	}
613 
614 	if (pid == 0) {
615 		mypid = getpid();
616 		foreground = 0;
617 	}
618 
619 	return pid;
620 }
621 
622 int background(P_void);
623 int background()
624 {
625 	int pid = dofork();
626 	if (pid == 0) {
627 #ifdef DEBUG
628 		dlog("backgrounded");
629 #endif /* DEBUG */
630 		foreground = 0;
631 	}
632 
633 	return pid;
634 }
635 
636 int mkdirs P((char *path, int mode));
637 int mkdirs(path, mode)
638 char *path;
639 int mode;
640 {
641 	/*
642 	 * take a copy in case path is in readonly store
643 	 */
644 	char *p2 = strdup(path);
645 	char *sp = p2;
646 	struct stat stb;
647 	int error_so_far = 0;
648 
649 	/*
650 	 * Skip through the string make the directories.
651 	 * Mostly ignore errors - the result is tested at the end.
652 	 *
653 	 * This assumes we are root so that we can do mkdir in a
654 	 * mode 555 directory...
655 	 */
656 	while (sp = strchr(sp+1, '/')) {
657 		*sp = '\0';
658 		if (mkdir(p2, mode) < 0) {
659 			error_so_far = errno;
660 		} else {
661 #ifdef DEBUG
662 			dlog("mkdir(%s)", p2);
663 #endif /* DEBUG */
664 		}
665 		*sp = '/';
666 	}
667 
668 	if (mkdir(p2, mode) < 0) {
669 		error_so_far = errno;
670 	} else {
671 #ifdef DEBUG
672 		dlog("mkdir(%s)", p2);
673 #endif /* DEBUG */
674 	}
675 
676 #ifdef SUNOS4_WORKAROUND
677 	/*
678 	 * Do a sync - if we do rmdirs() immediately
679 	 * and then the system crashes it leaves
680 	 * the filesystem in a state that fsck -p
681 	 * can't fix.  (Observed more than once on
682 	 * SunOS 4 ...)
683 	 *
684 	 * The problem was caused by a bug somewhere
685 	 * in the UFS code which has since been fixed
686 	 * (at least at Berkeley).
687 	 *
688 	 * Attempted workaround - XXX.
689 	 */
690 	sync();
691 #endif /* SUNOS4_WORKAROUND */
692 
693 	free(p2);
694 
695 	return stat(path, &stb) == 0 &&
696 		(stb.st_mode & S_IFMT) == S_IFDIR ? 0 : error_so_far;
697 }
698 
699 void rmdirs P((char *dir));
700 void rmdirs(dir)
701 char *dir;
702 {
703 	char *xdp = strdup(dir);
704 	char *dp;
705 
706 	do {
707 		struct stat stb;
708 		/*
709 		 * Try to find out whether this was
710 		 * created by amd.  Do this by checking
711 		 * for owner write permission.
712 		 */
713 		if (stat(xdp, &stb) == 0 && (stb.st_mode & 0200) == 0) {
714 			if (rmdir(xdp) < 0) {
715 				if (errno != ENOTEMPTY && errno != EBUSY && errno != EEXIST)
716 					plog(XLOG_ERROR, "rmdir(%s): %m", xdp);
717 				break;
718 			} else {
719 #ifdef DEBUG
720 				dlog("rmdir(%s)", xdp);
721 #endif /* DEBUG */
722 			}
723 		} else {
724 			break;
725 		}
726 		dp = strrchr(xdp, '/');
727 		if (dp)
728 			*dp = '\0';
729 	} while (dp && dp > xdp);
730 	free(xdp);
731 }
732 
733 /*
734  * Because the internal clock is only used for
735  * timing out mounts, it does not have to be
736  * particularly accurate, so long as it does not run
737  * ahead of the real time.  So, to reduce the system
738  * call overhead, repeated calls to gettimeofday()
739  * are replaced by calls to the macro clocktime().
740  * If the global time (clock_valid) is zero then
741  * update_clocktime() is called to obtain the real time.
742  * Before any system calls that are likely to block for a
743  * significant time, the clock_valid value is set
744  * so that the clock is recomputed next time it is
745  * needed.
746  */
747 
748 time_t clock_valid = 0;
749 #ifndef clocktime
750 time_t clocktime(P_void)
751 {
752 	return time(&clock_valid);
753 }
754 #endif /* clocktime */
755 
756 voidp xmalloc(len)
757 int len;
758 {
759 	voidp p;
760 	int retries = 600;
761 
762 	do {
763 		p = (voidp) malloc((unsigned) len);
764 		if (p) {
765 #if defined(DEBUG) && defined(DEBUG_MEM)
766 			Debug(D_MEM) plog(XLOG_DEBUG, "Allocated size %d; block %#x", len, p);
767 #endif /* defined(DEBUG) && defined(DEBUG_MEM) */
768 			return p;
769 		}
770 		if (retries > 0) {
771 			plog(XLOG_ERROR, "Retrying memory allocation");
772 			sleep(1);
773 		}
774 	} while (--retries);
775 
776 	plog(XLOG_FATAL, "Out of memory");
777 	going_down(1);
778 
779 	abort();
780 
781 	return 0;
782 }
783 
784 #if defined(DEBUG) && defined(DEBUG_MEM)
785 xfree(f, l, p)
786 char *f;
787 int l;
788 voidp p;
789 {
790 	Debug(D_MEM) plog(XLOG_DEBUG, "Free in %s:%d: block %#x", f, l, p);
791 #undef free
792 	free(p);
793 }
794 #endif /* defined(DEBUG) && defined(DEBUG_MEM) */
795