aboutsummaryrefslogblamecommitdiff
path: root/usr.sbin/pmcannotate/pmcannotate.c
blob: 03f384d3f531c1a9be5a2ce49c1983019997ffb5 (plain) (tree)
1
2
3
   

                                                



































                                                                            
                  





                   
                                                                             

                   
 
                                    








































































                                                                              












































































































































































































                                                                            
                      





























                                                               
                                                                


















































































































                                                                              
                       




































































































































































































                                                                                

                                           
















































                                                                          










                                                                     

















































































                                                                             
/*-
 * SPDX-License-Identifier: BSD-2-Clause-FreeBSD
 *
 * Copyright (c) 2008 Nokia Corporation
 * All rights reserved.
 *
 * This software was developed by Attilio Rao for the IPSO project under
 * contract to Nokia Corporation.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice unmodified, this list of conditions, and the following
 *    disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");

#include <sys/param.h>
#include <sys/queue.h>

#include <ctype.h>
#include <paths.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include <unistd.h>

/* NB: Make sure FNBUFF is as large as LNBUFF, otherwise it could overflow */
#define	FNBUFF	512
#define	LNBUFF	512

#define	TMPNAME	"pmcannotate.XXXXXX"

#define	FATAL(ptr, x ...) do {						\
	fqueue_deleteall();						\
	general_deleteall();						\
	if ((ptr) != NULL)						\
		perror(ptr);						\
	fprintf(stderr, ##x);						\
	remove(tbfl);							\
	remove(tofl);							\
	exit(EXIT_FAILURE);						\
} while (0)

#define	PERCSAMP(x)	((x) * 100 / totalsamples)

struct entry {
        TAILQ_ENTRY(entry)	en_iter;
        char		*en_name;
	uintptr_t	en_pc;
	uintptr_t	en_ostart;
	uintptr_t	en_oend;
	u_int		en_nsamples;
};

struct aggent {
	TAILQ_ENTRY(aggent)	ag_fiter;
	long		ag_offset;
	uintptr_t	ag_ostart;
	uintptr_t	ag_oend;
	char		*ag_name;
	u_int		ag_nsamples;
};

static struct aggent	*agg_create(const char *name, u_int nsamples,
			    uintptr_t start, uintptr_t end);
static void		 agg_destroy(struct aggent *agg) __unused;
static void		 asmparse(FILE *fp);
static int		 cparse(FILE *fp);
static void		 entry_acqref(struct entry *entry);
static struct entry	*entry_create(const char *name, uintptr_t pc,
			    uintptr_t start, uintptr_t end);
static void		 entry_destroy(struct entry *entry) __unused;
static void		 fqueue_compact(float th);
static void		 fqueue_deleteall(void);
static struct aggent	*fqueue_findent_by_name(const char *name);
static int		 fqueue_getall(const char *bin, char *temp, int asmf);
static int		 fqueue_insertent(struct entry *entry);
static int		 fqueue_insertgen(void);
static void		 general_deleteall(void);
static struct entry	*general_findent(uintptr_t pc);
static void		 general_insertent(struct entry *entry);
static void		 general_printasm(FILE *fp, struct aggent *agg);
static int		 general_printc(FILE *fp, struct aggent *agg);
static int		 printblock(FILE *fp, struct aggent *agg);
static void		 usage(const char *progname) __dead2;

static TAILQ_HEAD(, entry) mainlst = TAILQ_HEAD_INITIALIZER(mainlst);
static TAILQ_HEAD(, aggent) fqueue = TAILQ_HEAD_INITIALIZER(fqueue);

/*
 * Use a float value in order to automatically promote operations
 * to return a float value rather than use casts.
 */
static float totalsamples;

/*
 * Identifies a string cointaining objdump's assembly printout.
 */
static inline int
isasminline(const char *str)
{
	void *ptr;
	int nbytes;

	if (sscanf(str, " %p%n", &ptr, &nbytes) != 1)
		return (0);
	if (str[nbytes] != ':' || isspace(str[nbytes + 1]) == 0)
		return (0);
	return (1);
}

/*
 * Identifies a string containing objdump's assembly printout
 * for a new function.
 */
static inline int
newfunction(const char *str)
{
	char fname[FNBUFF];
	void *ptr;
	int nbytes;

	if (isspace(str[0]))
		return (0);
	if (sscanf(str, "%p <%[^>:]>:%n", &ptr, fname, &nbytes) != 2)
		return (0);
	return (nbytes);
}

/*
 * Create a new first-level aggregation object for a specified
 * function.
 */
static struct aggent *
agg_create(const char *name, u_int nsamples, uintptr_t start, uintptr_t end)
{
	struct aggent *agg;

	agg = calloc(1, sizeof(struct aggent));
	if (agg == NULL)
		return (NULL);
	agg->ag_name = strdup(name);
	if (agg->ag_name == NULL) {
		free(agg);
		return (NULL);
	}
	agg->ag_nsamples = nsamples;
	agg->ag_ostart = start;
	agg->ag_oend = end;
	return (agg);
}

/*
 * Destroy a first-level aggregation object for a specified
 * function.
 */
static void
agg_destroy(struct aggent *agg)
{

	free(agg->ag_name);
	free(agg);
}

/*
 * Analyze the "objdump -d" output, locate functions and start
 * printing out the assembly functions content.
 * We do not use newfunction() because we actually need the
 * function name in available form, but the heurstic used is
 * the same.
 */
static void
asmparse(FILE *fp)
{
	char buffer[LNBUFF], fname[FNBUFF];
	struct aggent *agg;
	void *ptr;

	while (fgets(buffer, LNBUFF, fp) != NULL) {
		if (isspace(buffer[0]))
			continue;
		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
			continue;
		agg = fqueue_findent_by_name(fname);
		if (agg == NULL)
			continue;
		agg->ag_offset = ftell(fp);
	}

	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
			return;
		printf("Profile trace for function: %s() [%.2f%%]\n",
		    agg->ag_name, PERCSAMP(agg->ag_nsamples));
		general_printasm(fp, agg);
		printf("\n");
	}
}

/*
 * Analyze the "objdump -S" output, locate functions and start
 * printing out the C functions content.
 * We do not use newfunction() because we actually need the
 * function name in available form, but the heurstic used is
 * the same.
 * In order to maintain the printout sorted, on the first pass it
 * simply stores the file offsets in order to fastly moved later
 * (when the file is hot-cached also) when the real printout will
 * happen.
 */
static int
cparse(FILE *fp)
{
	char buffer[LNBUFF], fname[FNBUFF];
	struct aggent *agg;
	void *ptr;

	while (fgets(buffer, LNBUFF, fp) != NULL) {
		if (isspace(buffer[0]))
			continue;
		if (sscanf(buffer, "%p <%[^>:]>:", &ptr, fname) != 2)
			continue;
		agg = fqueue_findent_by_name(fname);
		if (agg == NULL)
			continue;
		agg->ag_offset = ftell(fp);
	}

	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
		if (fseek(fp, agg->ag_offset, SEEK_SET) == -1)
			return (-1);
		printf("Profile trace for function: %s() [%.2f%%]\n",
		    agg->ag_name, PERCSAMP(agg->ag_nsamples));
		if (general_printc(fp, agg) == -1)
			return (-1);
		printf("\n");
	}
	return (0);
}

/*
 * Bump the number of samples for any raw entry.
 */
static void
entry_acqref(struct entry *entry)
{

	entry->en_nsamples++;
}

/*
 * Create a new raw entry object for a specified function.
 */
static struct entry *
entry_create(const char *name, uintptr_t pc, uintptr_t start, uintptr_t end)
{
	struct entry *obj;

	obj = calloc(1, sizeof(struct entry));
	if (obj == NULL)
		return (NULL);
	obj->en_name = strdup(name);
	if (obj->en_name == NULL) {
		free(obj);
		return (NULL);
	}
	obj->en_pc = pc;
	obj->en_ostart = start;
	obj->en_oend = end;
	obj->en_nsamples = 1;
	return (obj);
}

/*
 * Destroy a raw entry object for a specified function.
 */
static void
entry_destroy(struct entry *entry)
{

	free(entry->en_name);
	free(entry);
}

/*
 * Specify a lower bound in percentage and drop from the
 * first-level aggregation queue all the objects with a
 * smaller impact.
 */
static void
fqueue_compact(float th)
{
	u_int thi;
	struct aggent *agg, *tmpagg;

	if (totalsamples == 0)
		return;

	/* Revert the percentage calculation. */
	thi = th * totalsamples / 100;
	TAILQ_FOREACH_SAFE(agg, &fqueue, ag_fiter, tmpagg)
		if (agg->ag_nsamples < thi)
			TAILQ_REMOVE(&fqueue, agg, ag_fiter);
}

/*
 * Flush the first-level aggregates queue.
 */
static void
fqueue_deleteall(void)
{
	struct aggent *agg;

	while (TAILQ_EMPTY(&fqueue) == 0) {
		agg = TAILQ_FIRST(&fqueue);
		TAILQ_REMOVE(&fqueue, agg, ag_fiter);
	}
}

/*
 * Insert a raw entry into the aggregations queue.
 * If the respective first-level aggregation object
 * does not exist create it and maintain it sorted
 * in respect of the number of samples.
 */
static int
fqueue_insertent(struct entry *entry)
{
	struct aggent *obj, *tmp;
	int found;

	found = 0;
	TAILQ_FOREACH(obj, &fqueue, ag_fiter)
		if (!strcmp(obj->ag_name, entry->en_name)) {
			found = 1;
			obj->ag_nsamples += entry->en_nsamples;
			break;
		}

	/*
	 * If the first-level aggregation object already exists,
	 * just aggregate the samples and, if needed, resort
	 * it.
	 */
	if (found) {
		TAILQ_REMOVE(&fqueue, obj, ag_fiter);
		found = 0;
		TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
			if (obj->ag_nsamples > tmp->ag_nsamples) {
				found = 1;
				break;
			}
		if (found)
			TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
		else
			TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
		return (0);
	}

	/*
	 * If the first-level aggregation object does not
	 * exist, create it and put in the sorted queue.
	 * If this is the first object, we need to set the
	 * head of the queue.
	 */
	obj = agg_create(entry->en_name, entry->en_nsamples, entry->en_ostart,
	    entry->en_oend);
	if (obj == NULL)
		return (-1);
	if (TAILQ_EMPTY(&fqueue) != 0) {
		TAILQ_INSERT_HEAD(&fqueue, obj, ag_fiter);
		return (0);
	}
	TAILQ_FOREACH(tmp, &fqueue, ag_fiter)
		if (obj->ag_nsamples > tmp->ag_nsamples) {
			found = 1;
			break;
		}
	if (found)
		TAILQ_INSERT_BEFORE(tmp, obj, ag_fiter);
	else
		TAILQ_INSERT_TAIL(&fqueue, obj, ag_fiter);
	return (0);
}

/*
 * Lookup a first-level aggregation object by name.
 */
static struct aggent *
fqueue_findent_by_name(const char *name)
{
	struct aggent *obj;

	TAILQ_FOREACH(obj, &fqueue, ag_fiter)
		if (!strcmp(obj->ag_name, name))
			return (obj);
	return (NULL);
}

/*
 * Return the number of object in the first-level aggregations queue.
 */
static int
fqueue_getall(const char *bin, char *temp, int asmf)
{
	char tmpf[MAXPATHLEN * 2 + 50];
	struct aggent *agg;
	uintptr_t start, end;

	if (mkstemp(temp) == -1)
		return (-1);
	TAILQ_FOREACH(agg, &fqueue, ag_fiter) {
		bzero(tmpf, sizeof(tmpf));
		start = agg->ag_ostart;
		end = agg->ag_oend;

		/*
		 * Fix-up the end address in order to show it in the objdump's
		 * trace.
		 */
		end++;
		if (asmf)
			snprintf(tmpf, sizeof(tmpf),
			    "objdump --start-address=%p "
			    "--stop-address=%p -d %s >> %s", (void *)start,
			    (void *)end, bin, temp);
		else
			snprintf(tmpf, sizeof(tmpf),
			    "objdump --start-address=%p "
			    "--stop-address=%p -S %s >> %s", (void *)start,
			    (void *)end, bin, temp);
		if (system(tmpf) != 0)
			return (-1);
	}
	return (0);
}

/*
 * Insert all the raw entries present in the general queue
 * into the first-level aggregations queue.
 */
static int
fqueue_insertgen(void)
{
	struct entry *obj;

	TAILQ_FOREACH(obj, &mainlst, en_iter)
		if (fqueue_insertent(obj) == -1)
			return (-1);
	return (0);
}

/*
 * Flush the raw entries general queue.
 */
static void
general_deleteall(void)
{
	struct entry *obj;

	while (TAILQ_EMPTY(&mainlst) == 0) {
		obj = TAILQ_FIRST(&mainlst);
		TAILQ_REMOVE(&mainlst, obj, en_iter);
	}
}

/*
 * Lookup a raw entry by the PC.
 */
static struct entry *
general_findent(uintptr_t pc)
{
	struct entry *obj;

	TAILQ_FOREACH(obj, &mainlst, en_iter)
		if (obj->en_pc == pc)
			return (obj);
	return (NULL);
}

/*
 * Insert a new raw entry in the general queue.
 */
static void
general_insertent(struct entry *entry)
{

	TAILQ_INSERT_TAIL(&mainlst, entry, en_iter);
}

/*
 * Printout the body of an "objdump -d" assembly function.
 * It does simply stops when a new function is encountered,
 * bringing back the file position in order to not mess up
 * subsequent analysis.
 * C lines and others not recognized are simply skipped.
 */
static void
general_printasm(FILE *fp, struct aggent *agg)
{
	char buffer[LNBUFF];
	struct entry *obj;
	int nbytes;
	void *ptr;

	while (fgets(buffer, LNBUFF, fp) != NULL) {
		if ((nbytes = newfunction(buffer)) != 0) {
			fseek(fp, nbytes * -1, SEEK_CUR);
			break;
		}
		if (!isasminline(buffer))
			continue;
		if (sscanf(buffer, " %p:", &ptr) != 1)
			continue;
		obj = general_findent((uintptr_t)ptr);
		if (obj == NULL)
			printf("\t| %s", buffer);
		else
			printf("%.2f%%\t| %s",
			    (float)obj->en_nsamples * 100 / agg->ag_nsamples,
			    buffer);
	}
}

/*
 * Printout the body of an "objdump -S" function.
 * It does simply stops when a new function is encountered,
 * bringing back the file position in order to not mess up
 * subsequent analysis.
 * It expect from the starting to the end to find, always, valid blocks
 * (see below for an explanation of the "block" concept).
 */
static int
general_printc(FILE *fp, struct aggent *agg)
{
	char buffer[LNBUFF];

	while (fgets(buffer, LNBUFF, fp) != NULL) {
		fseek(fp, strlen(buffer) * -1, SEEK_CUR);
		if (newfunction(buffer) != 0)
			break;
		if (printblock(fp, agg) == -1)
			return (-1);
	}
	return (0);
}

/*
 * Printout a single block inside an "objdump -S" function.
 * The block is composed of a first part in C and subsequent translation
 * in assembly.
 * This code also operates a second-level aggregation packing together
 * samples relative to PCs into a (lower bottom) block with their
 * C (higher half) counterpart.
 */
static int
printblock(FILE *fp, struct aggent *agg)
{
	char buffer[LNBUFF];
	long lstart;
	struct entry *obj;
	u_int tnsamples;
	int done, nbytes, sentinel;
	void *ptr;

	/*
	 * We expect the first thing of the block is C code, so simply give
	 * up if asm line is found.
	 */
	lstart = ftell(fp);
	sentinel = 0;
	for (;;) {
		if (fgets(buffer, LNBUFF, fp) == NULL)
			return (0);
		if (isasminline(buffer) != 0)
			break;
		sentinel = 1;
		nbytes = newfunction(buffer);
		if (nbytes != 0) {
			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
				return (-1);
			return (0);
		}
	}

	/*
	 * If the sentinel is not set, it means it did not match any
	 * "high half" for this code so simply give up.
	 * Operates the second-level aggregation.
	 */
	tnsamples = 0;
	do {
		if (sentinel == 0)
			return (-1);
		if (sscanf(buffer, " %p:", &ptr) != 1)
			return (-1);
		obj = general_findent((uintptr_t)ptr);
		if (obj != NULL)
			tnsamples += obj->en_nsamples;
	} while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) != 0);

	/* Rewind to the start of the block in order to start the printout. */
	if (fseek(fp, lstart, SEEK_SET) == -1)
		return (-1);

	/* Again the high half of the block rappresenting the C part. */
	done = 0;
	while (fgets(buffer, LNBUFF, fp) != NULL && isasminline(buffer) == 0) {
		if (tnsamples == 0 || done != 0)
			printf("\t| %s", buffer);
		else {
			done = 1;
			printf("%.2f%%\t| %s",
			    (float)tnsamples * 100 / agg->ag_nsamples, buffer);
		}
	}

	/*
	 * Again the low half of the block rappresenting the asm
	 * translation part.
	 */
	for (;;) {
		if (fgets(buffer, LNBUFF, fp) == NULL)
			return (0);
		if (isasminline(buffer) == 0)
			break;
		nbytes = newfunction(buffer);
		if (nbytes != 0) {
			if (fseek(fp, nbytes * -1, SEEK_CUR) == -1)
				return (-1);
			return (0);
		}
	}
	if (fseek(fp, strlen(buffer) * -1, SEEK_CUR) == -1)
		return (-1);
	return (0);
}

/*
 * Helper printout functions.
 */
static void
usage(const char *progname)
{

	fprintf(stderr,
	    "usage: %s [-a] [-h] [-k kfile] [-l lb] pmcraw.out binary\n",
	    progname);
	exit(EXIT_SUCCESS);
}

int
main(int argc, char *argv[])
{
	char buffer[LNBUFF], fname[FNBUFF];
	char *tbfl, *tofl, *tmpdir;
	char tmpf[MAXPATHLEN * 2 + 50];
	float limit;
	char *bin, *exec, *kfile, *ofile;
	struct entry *obj;
	FILE *gfp, *bfp;
	void *ptr, *hstart, *hend;
	uintptr_t tmppc, ostart, oend;
	int cget, asmsrc;

	exec = argv[0];
	ofile = NULL;
	bin = NULL;
	kfile = NULL;
	asmsrc = 0;
	limit = 0.5;
	while ((cget = getopt(argc, argv, "ahl:k:")) != -1)
		switch(cget) {
		case 'a':
			asmsrc = 1;
			break;
		case 'k':
			kfile = optarg;
			break;
		case 'l':
			limit = (float)atof(optarg);
			break;
		case 'h':
		case '?':
		default:
			usage(exec);
		}
	argc -= optind;
	argv += optind;
	if (argc != 2)
		usage(exec);
	ofile = argv[0];
	bin = argv[1];

	if (access(bin, R_OK | F_OK) == -1)
		FATAL(exec, "%s: Impossible to locate the binary file\n",
		    exec);
	if (access(ofile, R_OK | F_OK) == -1)
		FATAL(exec, "%s: Impossible to locate the pmcstat file\n",
		    exec);
	if (kfile != NULL && access(kfile, R_OK | F_OK) == -1)
		FATAL(exec, "%s: Impossible to locate the kernel file\n",
		    exec);

	bzero(tmpf, sizeof(tmpf));
	tmpdir = getenv("TMPDIR");
	if (tmpdir == NULL) {
		asprintf(&tbfl, "%s/%s", _PATH_TMP, TMPNAME);
		asprintf(&tofl, "%s/%s", _PATH_TMP, TMPNAME);
	} else {
		asprintf(&tbfl, "%s/%s", tmpdir, TMPNAME);
		asprintf(&tofl, "%s/%s", tmpdir, TMPNAME);
	}
	if (tofl == NULL || tbfl == NULL)
		FATAL(exec, "%s: Cannot create tempfile templates\n",
		    exec);
	if (mkstemp(tofl) == -1)
		FATAL(exec, "%s: Impossible to create the tmp file\n",
		    exec);
	if (kfile != NULL)
		snprintf(tmpf, sizeof(tmpf), "pmcstat -k %s -R %s -m %s",
		    kfile, ofile, tofl);
	else
		snprintf(tmpf, sizeof(tmpf), "pmcstat -R %s -m %s", ofile,
		    tofl);
	if (system(tmpf) != 0)
		FATAL(exec, "%s: Impossible to create the tmp file\n",
		    exec);

	gfp = fopen(tofl, "r");
	if (gfp == NULL)
		FATAL(exec, "%s: Impossible to open the map file\n",
		    exec);

	/*
	 * Make the collection of raw entries from a pmcstat mapped file.
	 * The heuristic here wants strings in the form:
	 * "addr funcname startfaddr endfaddr".
	 */
	while (fgets(buffer, LNBUFF, gfp) != NULL) {
		if (isspace(buffer[0]))
			continue;
		if (sscanf(buffer, "%p %s %p %p\n", &ptr, fname,
		    &hstart, &hend) != 4)
			FATAL(NULL,
			    "%s: Invalid scan of function in the map file\n",
			    exec);
		ostart = (uintptr_t)hstart;
		oend = (uintptr_t)hend;
		tmppc = (uintptr_t)ptr;
		totalsamples++;
		obj = general_findent(tmppc);
		if (obj != NULL) {
			entry_acqref(obj);
			continue;
		}
		obj = entry_create(fname, tmppc, ostart, oend);
		if (obj == NULL)
			FATAL(exec,
			    "%s: Impossible to create a new object\n", exec);
		general_insertent(obj);
	}
	if (fclose(gfp) == EOF)
		FATAL(exec, "%s: Impossible to close the filedesc\n",
		    exec);
	if (remove(tofl) == -1)
                FATAL(exec, "%s: Impossible to remove the tmpfile\n",
                    exec);

	/*
	 * Remove the loose end objects and feed the first-level aggregation
	 * queue.
	 */
	if (fqueue_insertgen() == -1)
		FATAL(exec, "%s: Impossible to generate an analysis\n",
		    exec);
	fqueue_compact(limit);
	if (fqueue_getall(bin, tbfl, asmsrc) == -1)
		FATAL(exec, "%s: Impossible to create the tmp file\n",
		    exec);

	bfp = fopen(tbfl, "r");
	if (bfp == NULL)
		FATAL(exec, "%s: Impossible to open the binary file\n",
		    exec);

	if (asmsrc != 0)
		asmparse(bfp);
	else if (cparse(bfp) == -1)
		FATAL(NULL, "%s: Invalid format for the C file\n", exec);
	if (fclose(bfp) == EOF)
                FATAL(exec, "%s: Impossible to close the filedesc\n",
                    exec);
	if (remove(tbfl) == -1)
                FATAL(exec, "%s: Impossible to remove the tmpfile\n",
                    exec);
	return (0);
}