1    	/*
2    	 * pmlogrewrite - config-driven stream editor for PCP archives
3    	 *
4    	 * Copyright (c) 1997-2002 Silicon Graphics, Inc.  All Rights Reserved.
5    	 * Copyright (c) 2011 Ken McDonell.  All Rights Reserved.
6    	 * 
7    	 * This program is free software; you can redistribute it and/or modify it
8    	 * under the terms of the GNU General Public License as published by the
9    	 * Free Software Foundation; either version 2 of the License, or (at your
10   	 * option) any later version.
11   	 * 
12   	 * This program is distributed in the hope that it will be useful, but
13   	 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
14   	 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15   	 * for more details.
16   	 */
17   	
18   	#include <math.h>
19   	#include <ctype.h>
20   	#include <sys/stat.h>
21   	#include "pmapi.h"
22   	#include "impl.h"
23   	#include "logger.h"
24   	#include <assert.h>
25   	
26   	global_t	global;
27   	indomspec_t	*indom_root = NULL;
28   	metricspec_t	*metric_root = NULL;
29   	int		lineno;
30   	
31   	/*
32   	 *  Usage
33   	 */
34   	static void
35   	usage(void)
36   	{
37   	    fprintf(stderr,
38   	"Usage: %s [options] input-archive [output-archive]\n\
39   	\n\
40   	Options:\n\
41   	  -c config   file or directory to load rules from\n\
42   	  -C          parse config file(s) and quit (sets -v and -w also)\n\
43   	  -d          desperate, save output archive even after error\n\
44   	  -i          rewrite in place, input-archive will be over-written\n\
45   	  -q          quick mode, no output if no change\n\
46   	  -s          do scale conversion\n\
47   	  -v          verbose\n\
48   	  -w          emit warnings [default is silence]\n\
49   	\n\
50   	output-archive is required unless -i is specified\n",
51   		pmProgname);
52   	}
53   	
54   	/*
55   	 *  Global variables
56   	 */
57   	static int	first_datarec = 1;	/* first record flag */
58   	static char	bak_base[MAXPATHLEN+1];	/* basename for backup with -i */
59   	
60   	off_t		new_log_offset;		/* new log offset */
61   	off_t		new_meta_offset;	/* new meta offset */
62   	
63   	
64   	/* archive control stuff */
65   	inarch_t		inarch;		/* input archive control */
66   	outarch_t		outarch;	/* output archive control */
67   	
68   	/* command line args */
69   	int	nconf = 0;			/* number of config files */
70   	char	**conf = NULL;			/* list of config files */
71   	char	*configfile = NULL;		/* current config file */
72   	int	Cflag = 0;			/* -C parse config and quit */
73   	int	dflag = 0;			/* -d desperate */
74   	int	iflag = 0;			/* -i in-place */
75   	int	qflag = 0;			/* -q quick or quiet */
76   	int	sflag = 0;			/* -s scale values */
77   	int	vflag = 0;			/* -v verbosity */
78   	int	wflag = 0;			/* -w emit warnings */
79   	
80   	/*
81   	 *  report that archive is corrupted
82   	 */
83   	static void
84   	_report(FILE *fp)
85   	{
86   	    off_t	here;
87   	    struct stat	sbuf;
88   	
89   	    here = lseek(fileno(fp), 0L, SEEK_CUR);
90   	    fprintf(stderr, "%s: Error occurred at byte offset %ld into a file of",
91   		    pmProgname, (long)here);
92   	    if (fstat(fileno(fp), &sbuf) < 0)
93   		fprintf(stderr, ": stat: %s\n", osstrerror());
94   	    else
95   		fprintf(stderr, " %ld bytes.\n", (long)sbuf.st_size);
96   	    if (dflag)
97   		fprintf(stderr, "The last record, and the remainder of this file will not be processed.\n");
98   	    abandon();
99   	}
100  	
101  	/*
102  	 *  switch output volumes
103  	 */
104  	void
105  	newvolume(int vol)
106  	{
107  	    FILE		*newfp;
108  	
109  	    if ((newfp = __pmLogNewFile(outarch.name, vol)) != NULL) {
110  		fclose(outarch.logctl.l_mfp);
111  		outarch.logctl.l_mfp = newfp;
112  		outarch.logctl.l_label.ill_vol = outarch.logctl.l_curvol = vol;
113  		__pmLogWriteLabel(outarch.logctl.l_mfp, &outarch.logctl.l_label);
114  		fflush(outarch.logctl.l_mfp);
115  	    }
116  	    else {
117  		fprintf(stderr, "%s: __pmLogNewFile(%s,%d) Error: %s\n",
118  			pmProgname, outarch.name, vol, pmErrStr(-oserror()));
119  		abandon();
120  	    }
121  	}
122  	
123  	/* construct new archive label */
124  	static void
125  	newlabel(void)
126  	{
127  	    __pmLogLabel	*lp = &outarch.logctl.l_label;
128  	
129  	    /* copy magic number, pid, host and timezone */
130  	    lp->ill_magic = inarch.label.ll_magic;
131  	    lp->ill_pid = inarch.label.ll_pid;
132  	    if (global.flags & GLOBAL_CHANGE_HOSTNAME)
133  		strncpy(lp->ill_hostname, global.hostname, PM_LOG_MAXHOSTLEN);
134  	    else
135  		strncpy(lp->ill_hostname, inarch.label.ll_hostname, PM_LOG_MAXHOSTLEN);
136  	    if (global.flags & GLOBAL_CHANGE_TZ)
137  		strncpy(lp->ill_tz, global.tz, PM_TZ_MAXLEN);
138  	    else
139  		strncpy(lp->ill_tz, inarch.label.ll_tz, PM_TZ_MAXLEN);
140  	}
141  	
142  	/*
143  	 * write label records at the start of each physical file
144  	 */
145  	void
146  	writelabel(int do_rewind)
147  	{
148  	    off_t	old_offset;
149  	
150  	    if (do_rewind) {
151  		old_offset = ftell(outarch.logctl.l_tifp);
152  		rewind(outarch.logctl.l_tifp);
153  	    }
154  	    outarch.logctl.l_label.ill_vol = PM_LOG_VOL_TI;
155  	    __pmLogWriteLabel(outarch.logctl.l_tifp, &outarch.logctl.l_label);
156  	    if (do_rewind)
157  		fseek(outarch.logctl.l_tifp, (long)old_offset, SEEK_SET);
158  	
159  	    if (do_rewind) {
160  		old_offset = ftell(outarch.logctl.l_mdfp);
161  		rewind(outarch.logctl.l_mdfp);
162  	    }
163  	    outarch.logctl.l_label.ill_vol = PM_LOG_VOL_META;
164  	    __pmLogWriteLabel(outarch.logctl.l_mdfp, &outarch.logctl.l_label);
165  	    if (do_rewind)
166  		fseek(outarch.logctl.l_mdfp, (long)old_offset, SEEK_SET);
167  	
168  	    if (do_rewind) {
169  		old_offset = ftell(outarch.logctl.l_mfp);
170  		rewind(outarch.logctl.l_mfp);
171  	    }
172  	    outarch.logctl.l_label.ill_vol = 0;
173  	    __pmLogWriteLabel(outarch.logctl.l_mfp, &outarch.logctl.l_label);
174  	    if (do_rewind)
175  		fseek(outarch.logctl.l_mfp, (long)old_offset, SEEK_SET);
176  	}
177  	
178  	/*
179  	 * read next metadata record 
180  	 */
181  	static int
182  	nextmeta()
183  	{
184  	    int			sts;
185  	    __pmLogCtl		*lcp;
186  	
187  	    lcp = inarch.ctxp->c_archctl->ac_log;
188  	    if ((sts = _pmLogGet(lcp, PM_LOG_VOL_META, &inarch.metarec)) < 0) {
189  		if (sts != PM_ERR_EOL) {
190  		    fprintf(stderr, "%s: Error: _pmLogGet[meta %s]: %s\n",
191  			    pmProgname, inarch.name, pmErrStr(sts));
192  		    _report(lcp->l_mdfp);
193  		}
194  		return -1;
195  	    }
196  	
197  	    return ntohl(inarch.metarec[1]);
198  	}
199  	
200  	
201  	/*
202  	 * read next log record
203  	 *
204  	 * return status is
205  	 * 0		ok
206  	 * 1		ok, but volume switched
207  	 * PM_ERR_EOL	end of file
208  	 * -1		fatal error
209  	 */
210  	static int
211  	nextlog(void)
212  	{
213  	    int			sts;
214  	    __pmLogCtl		*lcp;
215  	    int			old_vol;
216  	
217  	
218  	    lcp = inarch.ctxp->c_archctl->ac_log;
219  	    old_vol = inarch.ctxp->c_archctl->ac_log->l_curvol;
220  	
221  	    if ((sts = __pmLogRead(lcp, PM_MODE_FORW, NULL, &inarch.rp)) < 0) {
222  		if (sts != PM_ERR_EOL) {
223  		    fprintf(stderr, "%s: Error: __pmLogRead[log %s]: %s\n",
224  			    pmProgname, inarch.name, pmErrStr(sts));
225  		    _report(lcp->l_mfp);
226  		}
227  		return -1;
228  	    }
229  	
230  	    return old_vol == inarch.ctxp->c_archctl->ac_log->l_curvol ? 0 : 1;
231  	}
232  	
233  	#ifdef IS_MINGW
234  	#define S_ISLINK(mode) 0	/* no symlink support */
235  	#else
236  	#ifndef S_ISLINK
237  	#define S_ISLINK(mode) ((mode & S_IFMT) == S_IFLNK)
238  	#endif
239  	#endif
240  	
241  	/*
242  	 * parse command line arguments
243  	 */
244  	int
245  	parseargs(int argc, char *argv[])
246  	{
247  	    int			c;
248  	    int			sts;
249  	    int			sep = __pmPathSeparator();
250  	    int			errflag = 0;
251  	    struct stat		sbuf;
252  	
253  	    while ((c = getopt(argc, argv, "c:CdD:iqsvw?")) != EOF) {
254  		switch (c) {
255  	
256  		case 'c':	/* config file */
257  		    if (stat(optarg, &sbuf) < 0) {
258  			fprintf(stderr, "%s: stat(%s) failed: %s\n",
259  				pmProgname, optarg, osstrerror());
260  			errflag++;
261  			break;
262  		    }
263  		    if (S_ISREG(sbuf.st_mode) || S_ISLINK(sbuf.st_mode)) {
264  			nconf++;
265  			if ((conf = (char **)realloc(conf, nconf*sizeof(conf[0]))) != NULL)
266  			    conf[nconf-1] = optarg;
267  		    }
268  		    else if (S_ISDIR(sbuf.st_mode)) {
269  			DIR		*dirp;
270  			struct dirent	*dp;
271  			char		path[MAXPATHLEN+1];
272  	
Event alloc_fn: Calling allocation function "opendir".
Event var_assign: Assigning: "dirp" = storage returned from "opendir(optarg)".
Also see events: [noescape][leaked_storage]
At conditional (1): "(dirp = opendir(optarg)) == NULL": Taking false branch.
273  			if ((dirp = opendir(optarg)) == NULL) {
274  			    fprintf(stderr, "%s: opendir(%s) failed: %s\n", pmProgname, optarg, osstrerror());
275  			    errflag++;
276  			}
Event noescape: Variable "dirp" is not freed or pointed-to in function "readdir".
Also see events: [alloc_fn][var_assign][leaked_storage]
At conditional (2): "(dp = readdir(dirp)) != NULL": Taking true branch.
277  			else while ((dp = readdir(dirp)) != NULL) {
278  			    /* skip ., .. and "hidden" files */
At conditional (3): "dp->d_name[0] == 46": Taking false branch.
279  			    if (dp->d_name[0] == '.') continue;
280  			    snprintf(path, sizeof(path), "%s%c%s", optarg, sep, dp->d_name);
At conditional (4): "stat(path, &sbuf) < 0": Taking false branch.
281  			    if (stat(path, &sbuf) < 0) {
282  				fprintf(stderr, "%s: %s: %s\n",
283  					pmProgname, path, osstrerror());
284  				errflag++;
285  			    }
At conditional (5): "(sbuf.st_mode & 0xf000U) == 0x8000U": Taking true branch.
286  			    if (S_ISREG(sbuf.st_mode) || S_ISLINK(sbuf.st_mode)) {
287  				nconf++;
At conditional (6): "(conf = (char **)realloc(conf, nconf * sizeof (conf[0]) /*8*/)) == NULL": Taking true branch.
288  				if ((conf = (char **)realloc(conf, nconf*sizeof(conf[0]))) == NULL)
289  				    break;
290  				if ((conf[nconf-1] = strdup(path)) == NULL) {
291  				    fprintf(stderr, "conf[%d] strdup(%s) failed: %s\n", nconf-1, path, strerror(errno));
292  				    abandon();
293  				}
294  	
295  			    }
296  			}
Event leaked_storage: Variable "dirp" going out of scope leaks the storage it points to.
Also see events: [alloc_fn][var_assign][noescape]
297  		    }
298  		    else {
299  			fprintf(stderr, "Error: -c config %s is not a file or directory\n", optarg);
300  			errflag++;
301  		    }
302  		    if (nconf > 0 && conf == NULL) {
303  			fprintf(stderr, "conf[%d] realloc(%d) failed: %s\n", nconf, (int)(nconf*sizeof(conf[0])), strerror(errno));
304  			abandon();
305  		    }
306  		    break;
307  	
308  		case 'C':	/* parse configs and quit */
309  		    Cflag = 1;
310  		    vflag = 1;
311  		    wflag = 1;
312  		    break;
313  	
314  		case 'd':	/* desperate */
315  		    dflag = 1;
316  		    break;
317  	
318  		case 'D':	/* debug flag */
319  		    sts = __pmParseDebug(optarg);
320  		    if (sts < 0) {
321  			fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
322  			    pmProgname, optarg);
323  			errflag++;
324  		    }
325  		    else
326  			pmDebug |= sts;
327  		    break;
328  	
329  		case 'i':	/* in-place, over-write input archive */
330  		    iflag = 1;
331  		    break;
332  	
333  		case 'q':	/* quick or quiet */
334  		    qflag = 1;
335  		    break;
336  	
337  		case 's':	/* do scale conversions */
338  		    sflag = 1;
339  		    break;
340  	
341  		case 'v':	/* verbosity */
342  		    vflag++;
343  		    break;
344  	
345  		case 'w':	/* print warnings */
346  		    wflag = 1;
347  		    break;
348  	
349  		case '?':
350  		default:
351  		    errflag++;
352  		    break;
353  		}
354  	    }
355  	
356  	    if (errflag == 0) {
357  		if ((iflag == 0 && optind != argc-2) ||
358  		    (iflag == 1 && optind != argc-1))
359  		    errflag++;
360  	    }
361  	
362  	    return -errflag;
363  	}
364  	
365  	static void
366  	parseconfig(char *file)
367  	{
368  	    extern FILE * yyin;
369  	
370  	    configfile = file;
371  	    if ((yyin = fopen(configfile, "r")) == NULL) {
372  		fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
373  			pmProgname, configfile, osstrerror());
374  		exit(1);
375  	    }
376  	    if (vflag > 1)
377  		fprintf(stderr, "Start configfile: %s\n", file);
378  	    lineno = 1;
379  	
380  	    if (yyparse() != 0)
381  		exit(1);
382  	
383  	    fclose(yyin);
384  	    yyin = NULL;
385  	
386  	    return;
387  	}
388  	
389  	char *
390  	SemStr(int sem)
391  	{
392  	    static char	buf[20];
393  	
394  	    if (sem == PM_SEM_COUNTER) snprintf(buf, sizeof(buf), "counter");
395  	    else if (sem == PM_SEM_INSTANT) snprintf(buf, sizeof(buf), "instant");
396  	    else if (sem == PM_SEM_DISCRETE) snprintf(buf, sizeof(buf), "discrete");
397  	    else snprintf(buf, sizeof(buf), "bad sem? %d", sem);
398  	
399  	    return buf;
400  	}
401  	
402  	static void
403  	reportconfig(void)
404  	{
405  	    indomspec_t		*ip;
406  	    metricspec_t	*mp;
407  	    int			i;
408  	    int			change = 0;
409  	
410  	    printf("PCP Archive Log Rewrite Specifications Summary\n");
411  	    change |= (global.flags != 0);
412  	    if (global.flags & GLOBAL_CHANGE_HOSTNAME)
413  		printf("Hostname:\t%s -> %s\n", inarch.label.ll_hostname, global.hostname);
414  	    if (global.flags & GLOBAL_CHANGE_TZ)
415  		printf("Timezone:\t%s -> %s\n", inarch.label.ll_tz, global.tz);
416  	    if (global.flags & GLOBAL_CHANGE_TIME) {
417  		static struct tm	*tmp;
418  		char			*sign = "";
419  		time_t			time;
420  		if (global.time.tv_sec < 0) {
421  		    time = (time_t)(-global.time.tv_sec);
422  		    sign = "-";
423  		}
424  		else
425  		    time = (time_t)global.time.tv_sec;
426  		tmp = gmtime(&time);
427  		tmp->tm_hour += 24 * tmp->tm_yday;
428  		if (tmp->tm_hour < 10)
429  		    printf("Delta:\t\t-> %s%02d:%02d:%02d.%06d\n", sign, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)global.time.tv_usec);
430  		else
431  		    printf("Delta:\t\t-> %s%d:%02d:%02d.%06d\n", sign, tmp->tm_hour, tmp->tm_min, tmp->tm_sec, (int)global.time.tv_usec);
432  	    }
433  	    for (ip = indom_root; ip != NULL; ip = ip->i_next) {
434  		int		hdr_done = 0;
435  		if (ip->new_indom != ip->old_indom) {
436  		    printf("\nInstance Domain: %s\n", pmInDomStr(ip->old_indom));
437  		    hdr_done = 1;
438  		    printf("pmInDom:\t-> %s\n", pmInDomStr(ip->new_indom));
439  		    change |= 1;
440  		}
441  		for (i = 0; i < ip->numinst; i++) {
442  		    change |= (ip->flags[i] != 0);
443  		    if (ip->flags[i]) {
444  			if (hdr_done == 0) {
445  			    printf("\nInstance Domain: %s\n", pmInDomStr(ip->old_indom));
446  			    hdr_done = 1;
447  			}
448  			printf("Instance:\t\[%d] \"%s\" -> ", ip->old_inst[i], ip->old_iname[i]);
449  			if (ip->flags[i] & INST_DELETE)
450  			    printf("DELETE\n");
451  			else {
452  			    if (ip->flags[i] & INST_CHANGE_INST)
453  				printf("[%d] ", ip->new_inst[i]);
454  			    else
455  				printf("[%d] ", ip->old_inst[i]);
456  			    if (ip->flags[i] & INST_CHANGE_INAME)
457  				printf("\"%s\"\n", ip->new_iname[i]);
458  			    else
459  				printf("\"%s\"\n", ip->old_iname[i]);
460  			}
461  		    }
462  		}
463  	    }
464  	    for (mp = metric_root; mp != NULL; mp = mp->m_next) {
465  		if (mp->flags != 0 || mp->ip != NULL) {
466  		    change |= 1;
467  		    printf("\nMetric: %s (%s)\n", mp->old_name, pmIDStr(mp->old_desc.pmid));
468  		}
469  		if (mp->flags & METRIC_CHANGE_PMID) {
470  		    printf("pmID:\t\t%s ->", pmIDStr(mp->old_desc.pmid));
471  		    printf(" %s\n", pmIDStr(mp->new_desc.pmid));
472  		}
473  		if (mp->flags & METRIC_CHANGE_NAME)
474  		    printf("Name:\t\t%s -> %s\n", mp->old_name, mp->new_name);
475  		if (mp->flags & METRIC_CHANGE_TYPE) {
476  		    printf("Type:\t\t%s ->", pmTypeStr(mp->old_desc.type));
477  		    printf(" %s\n", pmTypeStr(mp->new_desc.type));
478  		}
479  		if (mp->flags & METRIC_CHANGE_INDOM) {
480  		    printf("InDom:\t\t%s ->", pmInDomStr(mp->old_desc.indom));
481  		    printf(" %s\n", pmInDomStr(mp->new_desc.indom));
482  		    if (mp->output != OUTPUT_ALL) {
483  			printf("Output:\t\t");
484  			switch (mp->output) {
485  			    case OUTPUT_ONE:
486  				if (mp->old_desc.indom != PM_INDOM_NULL) {
487  				    printf("value for instance");
488  				    if (mp->one_inst != PM_IN_NULL)
489  					printf(" %d", mp->one_inst);
490  				    if (mp->one_name != NULL)
491  					printf(" \"%s\"", mp->one_name);
492  				    putchar('\n');
493  				}
494  				else
495  				    printf("the only value (output instance %d)\n", mp->one_inst);
496  				break;
497  			    case OUTPUT_FIRST:
498  				if (mp->old_desc.indom != PM_INDOM_NULL)
499  				    printf("first value\n");
500  				else {
501  				    if (mp->one_inst != PM_IN_NULL)
502  					printf("first and only value (output instance %d)\n", mp->one_inst);
503  				    else
504  					printf("first and only value (output instance \"%s\")\n", mp->one_name);
505  				}
506  				break;
507  			    case OUTPUT_LAST:
508  				if (mp->old_desc.indom != PM_INDOM_NULL)
509  				    printf("last value\n");
510  				else
511  				    printf("last and only value (output instance %d)\n", mp->one_inst);
512  				break;
513  			    case OUTPUT_MIN:
514  				if (mp->old_desc.indom != PM_INDOM_NULL)
515  				    printf("smallest value\n");
516  				else
517  				    printf("smallest and only value (output instance %d)\n", mp->one_inst);
518  				break;
519  			    case OUTPUT_MAX:
520  				if (mp->old_desc.indom != PM_INDOM_NULL)
521  				    printf("largest value\n");
522  				else
523  				    printf("largest and only value (output instance %d)\n", mp->one_inst);
524  				break;
525  			    case OUTPUT_SUM:
526  				if (mp->old_desc.indom != PM_INDOM_NULL)
527  				    printf("sum value (output instance %d)\n", mp->one_inst);
528  				else
529  				    printf("sum and only value (output instance %d)\n", mp->one_inst);
530  				break;
531  			    case OUTPUT_AVG:
532  				if (mp->old_desc.indom != PM_INDOM_NULL)
533  				    printf("average value (output instance %d)\n", mp->one_inst);
534  				else
535  				    printf("average and only value (output instance %d)\n", mp->one_inst);
536  				break;
537  			}
538  		    }
539  		}
540  		if (mp->ip != NULL)
541  		    printf("Inst Changes:\t<- InDom %s", pmInDomStr(mp->ip->old_indom));
542  		if (mp->flags & METRIC_CHANGE_SEM) {
543  		    printf("Semantics:\t%s ->", SemStr(mp->old_desc.sem));
544  		    printf(" %s\n", SemStr(mp->new_desc.sem));
545  		}
546  		if (mp->flags & METRIC_CHANGE_UNITS) {
547  		    printf("Units:\t\t%s ->", pmUnitsStr(&mp->old_desc.units));
548  		    printf(" %s", pmUnitsStr(&mp->new_desc.units));
549  		    if (mp->flags & METRIC_RESCALE)
550  			printf(" (rescale)");
551  		    putchar('\n');
552  		}
553  		if (mp->flags & METRIC_DELETE)
554  		    printf("DELETE\n");
555  	    }
556  	    if (change == 0)
557  		printf("No changes\n");
558  	}
559  	
560  	static int
561  	anychange(void)
562  	{
563  	    indomspec_t		*ip;
564  	    metricspec_t	*mp;
565  	    int			i;
566  	
567  	    if (global.flags != 0)
568  		return 1;
569  	    for (ip = indom_root; ip != NULL; ip = ip->i_next) {
570  		if (ip->new_indom != ip->old_indom)
571  		    return 1;
572  		for (i = 0; i < ip->numinst; i++) {
573  		    if (ip->flags[i])
574  			return 1;
575  		}
576  	    }
577  	    for (mp = metric_root; mp != NULL; mp = mp->m_next) {
578  		if (mp->flags != 0 || mp->ip != NULL)
579  		    return 1;
580  	    }
581  	    
582  	    return 0;
583  	}
584  	
585  	static int
586  	fixstamp(struct timeval *tvp)
587  	{
588  	    if (global.flags & GLOBAL_CHANGE_TIME) {
589  		if (global.time.tv_sec > 0) {
590  		    tvp->tv_sec += global.time.tv_sec;
591  		    tvp->tv_usec += global.time.tv_usec;
592  		    if (tvp->tv_usec > 1000000) {
593  			tvp->tv_sec++;
594  			tvp->tv_usec -= 1000000;
595  		    }
596  		    return 1;
597  		}
598  		else if (global.time.tv_sec < 0) {
599  		    /* parser makes tv_sec < 0 and tv_usec >= 0 */
600  		    tvp->tv_sec += global.time.tv_sec;
601  		    tvp->tv_usec -= global.time.tv_usec;
602  		    if (tvp->tv_usec < 0) {
603  			tvp->tv_sec--;
604  			tvp->tv_usec += 1000000;
605  		    }
606  		    return 1;
607  		}
608  	    }
609  	    return 0;
610  	}
611  	
612  	/*
613  	 * Link metricspec_t entries to corresponding indom_t entry if there
614  	 * are changes to instance identifiers or instance names (includes
615  	 * instance deletion)
616  	 */
617  	static void
618  	link_entries(void)
619  	{
620  	    indomspec_t		*ip;
621  	    metricspec_t	*mp;
622  	    __pmHashCtl		*hcp;
623  	    __pmHashNode	*this;
624  	    int			i;
625  	    int			change;
626  	
627  	    hcp = &inarch.ctxp->c_archctl->ac_log->l_hashpmid;
628  	    for (ip = indom_root; ip != NULL; ip = ip->i_next) {
629  		change = 0;
630  		for (i = 0; i < ip->numinst; i++)
631  		    change |= (ip->flags[i] != 0);
632  		if (change == 0 && ip->new_indom == ip->old_indom)
633  		    continue;
634  	
635  		for (this = __pmHashWalk(hcp, W_START); this != NULL; this = __pmHashWalk(hcp, W_NEXT)) {
636  		    mp = start_metric((pmID)(this->key));
637  		    if (mp->old_desc.indom == ip->old_indom) {
638  			if (change)
639  			    mp->ip = ip;
640  			if (ip->new_indom != ip->old_indom) {
641  			    if (mp->flags & METRIC_CHANGE_INDOM) {
642  				/* indom already changed via metric clause */
643  				if (mp->new_desc.indom != ip->new_indom) {
644  				    char	strbuf[80];
645  				    snprintf(strbuf, sizeof(strbuf), "%s", pmInDomStr(mp->new_desc.indom));
646  				    snprintf(mess, sizeof(mess), "Conflicting indom change for metric %s (%s from metric clause, %s from indom clause)", mp->old_name, strbuf, pmInDomStr(ip->new_indom));
647  				    yysemantic(mess);
648  				}
649  			    }
650  			    else {
651  				mp->flags |= METRIC_CHANGE_INDOM;
652  				mp->new_desc.indom = ip->new_indom;
653  			    }
654  			}
655  		    }
656  		}
657  	    }
658  	}
659  	
660  	static void
661  	check_indoms()
662  	{
663  	    /*
664  	     * For each metric, make sure the output instance domain will be in
665  	     * the output archive.
666  	     * Called after link_entries(), so if an input metric is associated
667  	     * with an instance domain that has any instance rewriting, we're OK.
668  	     * The case to be checked here is a rewritten metric with an indom
669  	     * clause and no associated indomspec_t (so no instance domain changes,
670  	     * but the new indom may not match any indom in the archive.
671  	     */
672  	    metricspec_t	*mp;
673  	    indomspec_t		*ip;
674  	    __pmHashCtl		*hcp;
675  	    __pmHashNode	*this;
676  	
677  	
678  	    hcp = &inarch.ctxp->c_archctl->ac_log->l_hashindom;
679  	
680  	    for (mp = metric_root; mp != NULL; mp = mp->m_next) {
681  		if (mp->ip != NULL)
682  		    /* associated indom has instance changes, we're OK */
683  		    continue;
684  		if ((mp->flags & METRIC_CHANGE_INDOM) && mp->new_desc.indom != PM_INDOM_NULL) {
685  		    for (this = __pmHashWalk(hcp, W_START); this != NULL; this = __pmHashWalk(hcp, W_NEXT)) {
686  			/*
687  			 * if this indom has an indomspec_t, check that, else
688  			 * this indom will go to the archive without change
689  			 */
690  			for (ip = indom_root; ip != NULL; ip = ip->i_next) {
691  			    if (ip->old_indom == mp->old_desc.indom)
692  				break;
693  			}
694  			if (ip == NULL) {
695  			    if ((pmInDom)(this->key) == mp->new_desc.indom)
696  				/* we're OK */
697  				break;
698  			}
699  			else {
700  			    if (ip->new_indom != ip->old_indom &&
701  			        ip->new_indom == mp->new_desc.indom)
702  				/* we're OK */
703  				break;
704  			}
705  		    }
706  		    if (this == NULL) {
707  			snprintf(mess, sizeof(mess), "New indom (%s) for metric %s is not in the output archive", pmInDomStr(mp->new_desc.indom), mp->old_name);
708  			yysemantic(mess);
709  		    }
710  		}
711  	    }
712  	
713  	    /*
714  	     * For each modified instance domain, make sure instances are
715  	     * still unique and instance names are unique to the first
716  	     * space.
717  	     */
718  	    for (ip = indom_root; ip != NULL; ip = ip->i_next) {
719  		int	i;
720  		for (i = 0; i < ip->numinst; i++) {
721  		    int		insti;
722  		    char	*namei;
723  		    int		j;
724  		    if (ip->flags[i] & INST_CHANGE_INST)
725  			insti = ip->new_inst[i];
726  		    else
727  			insti = ip->old_inst[i];
728  		    if (ip->flags[i] & INST_CHANGE_INAME)
729  			namei = ip->new_iname[i];
730  		    else
731  			namei = ip->old_iname[i];
732  		    for (j = 0; j < ip->numinst; j++) {
733  			int	instj;
734  			char	*namej;
735  			if (i == j)
736  			    continue;
737  			if (ip->flags[j] & INST_CHANGE_INST)
738  			    instj = ip->new_inst[j];
739  			else
740  			    instj = ip->old_inst[j];
741  			if (ip->flags[j] & INST_CHANGE_INAME)
742  			    namej = ip->new_iname[j];
743  			else
744  			    namej = ip->old_iname[j];
745  			if (insti == instj) {
746  			    snprintf(mess, sizeof(mess), "Duplicate instance id %d (\"%s\" and \"%s\") for indom %s", insti, namei, namej, pmInDomStr(ip->old_indom));
747  			    yysemantic(mess);
748  			}
749  			if (inst_name_eq(namei, namej) > 0) {
750  			    snprintf(mess, sizeof(mess), "Duplicate instance name \"%s\" (%d) and \"%s\" (%d) for indom %s", namei, insti, namej, instj, pmInDomStr(ip->old_indom));
751  			    yysemantic(mess);
752  			}
753  		    }
754  		}
755  	    }
756  	}
757  	
758  	static void
759  	check_output()
760  	{
761  	    /*
762  	     * For each metric, if there is an INDOM clause, perform some
763  	     * additional semantic checks and perhaps a name -> instance id
764  	     * mapping.
765  	     *
766  	     * Note instance renumbering happens _after_ value selction from
767  	     * 		the INDOM -> ,,,, OUTPUT clause, so all references to
768  	     * 		instance names and instance ids are relative to the
769  	     * 		"old" set.
770  	     */
771  	    metricspec_t	*mp;
772  	    indomspec_t		*ip;
773  	
774  	    for (mp = metric_root; mp != NULL; mp = mp->m_next) {
775  		if ((mp->flags & METRIC_CHANGE_INDOM)) {
776  		    if (mp->output == OUTPUT_ONE || mp->output == OUTPUT_FIRST) {
777  			/*
778  			 * cases here are
779  			 * INAME "name"
780  			 * 	=> one_name == "name" and one_inst == PM_IN_NULL
781  			 * INST id
782  			 * 	=> one_name == NULL and one_inst = id
783  			 */
784  			if (mp->old_desc.indom != PM_INDOM_NULL && mp->output == OUTPUT_ONE) {
785  			    /*
786  			     * old metric is not singular, so one_name and one_inst
787  			     * are used to pick the value
788  			     * also map one_name -> one_inst 
789  			     */
790  			    int		i;
791  			    ip = start_indom(mp->old_desc.indom);
792  			    for (i = 0; i < ip->numinst; i++) {
793  				if (mp->one_name != NULL) {
794  				    if (inst_name_eq(ip->old_iname[i], mp->one_name) > 0) {
795  					mp->one_name = NULL;
796  					mp->one_inst = ip->old_inst[i];
797  					break;
798  				    }
799  				}
800  				else if (ip->old_inst[i] == mp->one_inst)
801  				    break;
802  			    }
803  			    if (i == ip->numinst) {
804  				if (wflag) {
805  				    if (mp->one_name != NULL)
806  					snprintf(mess, sizeof(mess), "Instance \"%s\" from OUTPUT clause not found in old indom %s", mp->one_name, pmInDomStr(mp->old_desc.indom));
807  				    else
808  					snprintf(mess, sizeof(mess), "Instance %d from OUTPUT clause not found in old indom %s", mp->one_inst, pmInDomStr(mp->old_desc.indom));
809  				    yywarn(mess);
810  				}
811  			    }
812  			}
813  			if (mp->new_desc.indom != PM_INDOM_NULL) {
814  			    /*
815  			     * new metric is not singular, so one_inst should be
816  			     * found in the new instance domain ... ignore one_name
817  			     * other than to map one_name -> one_inst if one_inst
818  			     * is not already known
819  			     */
820  			    int		i;
821  			    ip = start_indom(mp->new_desc.indom);
822  			    for (i = 0; i < ip->numinst; i++) {
823  				if (mp->one_name != NULL) {
824  				    if (inst_name_eq(ip->old_iname[i], mp->one_name) > 0) {
825  					mp->one_name = NULL;
826  					mp->one_inst = ip->old_inst[i];
827  					break;
828  				    }
829  				}
830  				else if (ip->old_inst[i] == mp->one_inst)
831  				    break;
832  			    }
833  			    if (i == ip->numinst) {
834  				if (wflag) {
835  				    if (mp->one_name != NULL)
836  					snprintf(mess, sizeof(mess), "Instance \"%s\" from OUTPUT clause not found in new indom %s", mp->one_name, pmInDomStr(mp->new_desc.indom));
837  				    else
838  					snprintf(mess, sizeof(mess), "Instance %d from OUTPUT clause not found in new indom %s", mp->one_inst, pmInDomStr(mp->new_desc.indom));
839  				    yywarn(mess);
840  				}
841  			    }
842  			    /*
843  			     * use default rule (id 0) if INAME not found and
844  			     * and instance id is needed for output value
845  			     */
846  			    if (mp->old_desc.indom == PM_INDOM_NULL && mp->one_inst == PM_IN_NULL)
847  				mp->one_inst = 0;
848  			}
849  		    }
850  		}
851  	    }
852  	}
853  	
854  	
855  	int
856  	main(int argc, char **argv)
857  	{
858  	    int		sts;
859  	    int		stslog;			/* sts from nextlog() */
860  	    int		stsmeta = 0;		/* sts from nextmeta() */
861  	    int		i;
862  	    int		ti_idx;			/* next slot for input temporal index */
863  	    int		needti = 0;
864  	    int		doneti = 0;
865  	    __pmTimeval	tstamp;			/* for last log record */
866  	    off_t	old_log_offset = 0;	/* log offset before last log record */
867  	    off_t	old_meta_offset;
868  	
869  	    __pmSetProgname(argv[0]);
870  	
871  	    /* process cmd line args */
872  	    if (parseargs(argc, argv) < 0) {
873  		usage();
874  		exit(1);
875  	    }
876  	
877  	    /* input archive */
878  	    if (iflag == 0)
879  		inarch.name = argv[argc-2];
880  	    else
881  		inarch.name = argv[argc-1];
882  	    inarch.logrec = inarch.metarec = NULL;
883  	    inarch.mark = 0;
884  	    inarch.rp = NULL;
885  	
886  	    if ((inarch.ctx = pmNewContext(PM_CONTEXT_ARCHIVE, inarch.name)) < 0) {
887  		fprintf(stderr, "%s: Error: cannot open archive \"%s\": %s\n",
888  			pmProgname, inarch.name, pmErrStr(inarch.ctx));
889  		exit(1);
890  	    }
891  	    inarch.ctxp = __pmHandleToPtr(inarch.ctx);
892  	    assert(inarch.ctxp != NULL);
893  	
894  	    if ((sts = pmGetArchiveLabel(&inarch.label)) < 0) {
895  		fprintf(stderr, "%s: Error: cannot get archive label record (%s): %s\n", pmProgname, inarch.name, pmErrStr(sts));
896  		exit(1);
897  	    }
898  	
899  	    if ((inarch.label.ll_magic & 0xff) != PM_LOG_VERS02) {
900  		fprintf(stderr,"%s: Error: illegal version number %d in archive (%s)\n",
901  			pmProgname, inarch.label.ll_magic & 0xff, inarch.name);
902  		exit(1);
903  	    }
904  	
905  	    /* output archive */
906  	    if (iflag && Cflag == 0) {
907  		/*
908  		 * -i (in place) method outline
909  		 *
910  		 * + create one temporary base filename in the same directory is
911  		 *   the input archive, keep a copy of this name this accessed
912  		 *   via outarch.name
913  		 * + create a second (and different) temporary base file name
914  		 *   in the same directory, keep this name in bak_base[]
915  		 * + close the temporary file descriptors and unlink the basename
916  		 *   files
917  		 * + create the output as per normal in outarch.name
918  		 * + rename the _input_ archive files using the _second_ temporary
919  		 *   basename
920  		 * + rename the output archive files to the basename of the input
921  		 *   archive ... if this step fails for any reason, restore the
922  		 *   original input files
923  		 * + unlink all the (old) input archive files
924  		 */
925  		char	path[MAXPATHLEN+1];
926  		char	dname[MAXPATHLEN+1];
927  		int	tmp_f1;			/* fd for first temp basename */
928  		int	tmp_f2;			/* fd for second temp basename */
929  		strncpy(path, argv[argc-1], sizeof(path));
930  		strncpy(dname, dirname(path), sizeof(dname));
931  	#if HAVE_MKSTEMP
932  		sprintf(path, "%s%cXXXXXX", dname, __pmPathSeparator());
933  		tmp_f1 = mkstemp(path);
934  		outarch.name = strdup(path);
935  		if (outarch.name == NULL) {
936  		    fprintf(stderr, "temp file strdup(%s) failed: %s\n", path, strerror(errno));
937  		    abandon();
938  		}
939  		sprintf(bak_base, "%s%cXXXXXX", dname, __pmPathSeparator());
940  		tmp_f2 = mkstemp(bak_base);
941  	#else
942  		char	fname[MAXPATHLEN+1];
943  		char	*s;
944  		strncpy(path, argv[argc-1], sizeof(path));
945  		strncpy(fname, basename(path)m sizeof(fname));
946  		if ((s = tempnam(dname, fname)) == NULL) {
947  		    fprintf(stderr, "Error: first tempnam() failed: %s\n", strerror(errno));
948  		    abandon();
949  		}
950  		else {
951  		    outarch.name = strdup(s);
952  		    if (outarch.name == NULL) {
953  			fprintf(stderr, "temp file strdup(%s) failed: %s\n", s, strerror(errno));
954  			abandon();
955  		    }
956  		    tmp_f1 = open(outarch.name, O_WRONLY|O_CREAT|O_EXCL, 0600);
957  		}
958  		if ((s = tempnam(dname, fname)) == NULL) {
959  		    fprintf(stderr, "Error: second tempnam() failed: %s\n", strerror(errno));
960  		    abandon();
961  		}
962  		else {
963  		    strcpy(bak_base, s);
964  		    tmp_f2 = open(bak_base, O_WRONLY|O_CREAT|O_EXCL, 0600);
965  		}
966  	#endif
967  		if (tmp_f1 < 0) {
968  		    fprintf(stderr, "Error: create first temp (%s) failed: %s\n", outarch.name, strerror(errno));
969  		    abandon();
970  		}
971  		if (tmp_f2 < 0) {
972  		    fprintf(stderr, "Error: create second temp (%s) failed: %s\n", bak_base, strerror(errno));
973  		    abandon();
974  		}
975  		close(tmp_f1);
976  		close(tmp_f2);
977  		unlink(outarch.name);
978  		unlink(bak_base);
979  	    }
980  	    else
981  		outarch.name = argv[argc-1];
982  	
983  	    /*
984  	     * process config file(s)
985  	     */
986  	    for (i = 0; i < nconf; i++) {
987  		parseconfig(conf[i]);
988  	    }
989  	
990  	    /*
991  	     * cross-specification dependencies and semantic checks once all
992  	     * config files have been processed
993  	     */
994  	    link_entries();
995  	    check_indoms();
996  	    check_output();
997  	
998  	    if (vflag)
999  		reportconfig();
1000 	
1001 	    if (Cflag)
1002 		exit(0);
1003 	
1004 	    if (qflag && anychange() == 0)
1005 		exit(0);
1006 	
1007 	    /* create output log - must be done before writing label */
1008 	    if ((sts = __pmLogCreate("", outarch.name, PM_LOG_VERS02, &outarch.logctl)) < 0) {
1009 		fprintf(stderr, "%s: Error: __pmLogCreate(%s): %s\n",
1010 			pmProgname, outarch.name, pmErrStr(sts));
1011 		abandon();
1012 	    }
1013 	
1014 	    /* initialize and write label records */
1015 	    newlabel();
1016 	    outarch.logctl.l_state = PM_LOG_STATE_INIT;
1017 	    writelabel(0);
1018 	
1019 	    first_datarec = 1;
1020 	    ti_idx = 0;
1021 	
1022 	    /*
1023 	     * loop
1024 	     *	- get next log record
1025 	     *	- write out new/changed meta data required by this log record
1026 	     *	- write out log
1027 	     *	- do ti update if necessary
1028 	     */
1029 	    while (1) {
1030 		static long	in_offset;		/* for -Dappl0 */
1031 	
1032 		fflush(outarch.logctl.l_mdfp);
1033 		old_meta_offset = ftell(outarch.logctl.l_mdfp);
1034 	
1035 		in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mfp);
1036 		stslog = nextlog();
1037 		if (stslog < 0) {
1038 	#if PCP_DEBUG
1039 		    if (pmDebug & DBG_TRACE_APPL0)
1040 			fprintf(stderr, "Log: read EOF @ offset=%ld\n", in_offset);
1041 	#endif
1042 		    break;
1043 		}
1044 		if (stslog == 1) {
1045 		    /* volume change */
1046 		    if (inarch.ctxp->c_archctl->ac_log->l_curvol >= outarch.logctl.l_curvol+1)
1047 			/* track input volume numbering */
1048 			newvolume(inarch.ctxp->c_archctl->ac_log->l_curvol);
1049 		    else
1050 			/*
1051 			 * output archive volume number is ahead, probably because
1052 			 * rewriting has forced an earlier volume change
1053 			 */
1054 			newvolume(outarch.logctl.l_curvol+1);
1055 		}
1056 	#if PCP_DEBUG
1057 		if (pmDebug & DBG_TRACE_APPL0) {
1058 		    struct timeval	stamp;
1059 		    fprintf(stderr, "Log: read ");
1060 		    stamp.tv_sec = inarch.rp->timestamp.tv_sec;
1061 		    stamp.tv_usec = inarch.rp->timestamp.tv_usec;
1062 		    __pmPrintStamp(stderr, &stamp);
1063 		    fprintf(stderr, " numpmid=%d @ offset=%ld\n", inarch.rp->numpmid, in_offset);
1064 		}
1065 	#endif
1066 	
1067 		if (ti_idx < inarch.ctxp->c_archctl->ac_log->l_numti) {
1068 		    __pmLogTI	*tip = &inarch.ctxp->c_archctl->ac_log->l_ti[ti_idx];
1069 		    if (tip->ti_stamp.tv_sec == inarch.rp->timestamp.tv_sec &&
1070 		        tip->ti_stamp.tv_usec == inarch.rp->timestamp.tv_usec) {
1071 			/*
1072 			 * timestamp on input pmResult matches next temporal index
1073 			 * entry for input archive ... make sure matching temporal
1074 			 * index entry added to output archive
1075 			 */
1076 			needti = 1;
1077 			ti_idx++;
1078 		    }
1079 		}
1080 	
1081 		/*
1082 		 * optionally rewrite timestamp in pmResult for global time
1083 		 * adjustment ... flows to output pmResult, indom entries in
1084 		 * metadata, temporal index entries and label records
1085 		 * */
1086 		fixstamp(&inarch.rp->timestamp);
1087 	
1088 		/*
1089 		 * process metadata until find an indom record with timestamp
1090 		 * after the current log record, or a metric record for a pmid
1091 		 * that is not in the current log record
1092 		 */
1093 		for ( ; ; ) {
1094 		    pmID	pmid;			/* pmid for TYPE_DESC */
1095 		    pmInDom	indom;			/* indom for TYPE_INDOM */
1096 	
1097 		    if (stsmeta == 0) {
1098 			in_offset = ftell(inarch.ctxp->c_archctl->ac_log->l_mdfp);
1099 			stsmeta = nextmeta();
1100 	#if PCP_DEBUG
1101 			if (stsmeta < 0 && pmDebug & DBG_TRACE_APPL0)
1102 			    fprintf(stderr, "Metadata: read EOF @ offset=%ld\n", in_offset);
1103 	#endif
1104 		    }
1105 		    if (stsmeta < 0) {
1106 			break;
1107 		    }
1108 		    if (stsmeta == TYPE_DESC) {
1109 			int	i;
1110 			pmid = __ntohpmID(inarch.metarec[2]);
1111 	#if PCP_DEBUG
1112 			if (pmDebug & DBG_TRACE_APPL0)
1113 			    fprintf(stderr, "Metadata: read PMID %s @ offset=%ld\n", pmIDStr(pmid), in_offset);
1114 	#endif
1115 			/*
1116 			 * if pmid not in next pmResult, we're done ...
1117 			 */
1118 			for (i = 0; i < inarch.rp->numpmid; i++) {
1119 			    if (pmid == inarch.rp->vset[i]->pmid)
1120 				break;
1121 			}
1122 			if (i == inarch.rp->numpmid)
1123 			    break;
1124 			/*
1125 			 * rewrite if needed, delete if needed else output
1126 			 */
1127 			do_desc();
1128 		    }
1129 		    else if (stsmeta == TYPE_INDOM) {
1130 			struct timeval	stamp;
1131 			__pmTimeval	*tvp = (__pmTimeval *)&inarch.metarec[2];
1132 			indom = __ntohpmInDom((unsigned int)inarch.metarec[4]);
1133 	#if PCP_DEBUG
1134 			if (pmDebug & DBG_TRACE_APPL0)
1135 			    fprintf(stderr, "Metadata: read InDom %s @ offset=%ld\n", pmInDomStr(indom), in_offset);
1136 	#endif
1137 			stamp.tv_sec = ntohl(tvp->tv_sec);
1138 			stamp.tv_usec = ntohl(tvp->tv_usec);
1139 			if (fixstamp(&stamp)) {
1140 			    /* global time adjustment specified */
1141 			    tvp->tv_sec = htonl(stamp.tv_sec);
1142 			    tvp->tv_usec = htonl(stamp.tv_usec);
1143 			}
1144 			/* if time of indom > next pmResult stop processing metadata */
1145 			if (stamp.tv_sec > inarch.rp->timestamp.tv_sec)
1146 			    break;
1147 			if (stamp.tv_sec == inarch.rp->timestamp.tv_sec &&
1148 			    stamp.tv_usec > inarch.rp->timestamp.tv_usec)
1149 			    break;
1150 			needti = 1;
1151 			do_indom();
1152 		    }
1153 		    else {
1154 			fprintf(stderr, "%s: Error: unrecognised meta data type: %d\n",
1155 			    pmProgname, stsmeta);
1156 			abandon();
1157 		    }
1158 		    free(inarch.metarec);
1159 		    stsmeta = 0;
1160 		}
1161 	
1162 		if (first_datarec) {
1163 		    first_datarec = 0;
1164 		    /* any global time adjustment done after nextlog() above */
1165 		    outarch.logctl.l_label.ill_start.tv_sec = inarch.rp->timestamp.tv_sec;
1166 		    outarch.logctl.l_label.ill_start.tv_usec = inarch.rp->timestamp.tv_usec;
1167 		    /* need to fix start-time in label records */
1168 		    writelabel(1);
1169 		    needti = 1;
1170 		}
1171 	
1172 		tstamp.tv_sec = inarch.rp->timestamp.tv_sec;
1173 		tstamp.tv_usec = inarch.rp->timestamp.tv_usec;
1174 	
1175 		if (needti) {
1176 		    fflush(outarch.logctl.l_mdfp);
1177 		    fflush(outarch.logctl.l_mfp);
1178 		    new_meta_offset = ftell(outarch.logctl.l_mdfp);
1179 	            fseek(outarch.logctl.l_mdfp, (long)old_meta_offset, SEEK_SET);
1180 	            __pmLogPutIndex(&outarch.logctl, &tstamp);
1181 	            fseek(outarch.logctl.l_mdfp, (long)new_meta_offset, SEEK_SET);
1182 		    needti = 0;
1183 		    doneti = 1;
1184 	        }
1185 		else
1186 		    doneti = 0;
1187 	
1188 		old_log_offset = ftell(outarch.logctl.l_mfp);
1189 	
1190 		if (inarch.rp->numpmid == 0)
1191 		    /* mark record, need index entry @ next log record */
1192 		    needti = 1;
1193 	
1194 		do_result();
1195 	    }
1196 	
1197 	    if (!doneti) {
1198 		/* Final temporal index entry */
1199 		fflush(outarch.logctl.l_mfp);
1200 		fseek(outarch.logctl.l_mfp, (long)old_log_offset, SEEK_SET);
1201 		__pmLogPutIndex(&outarch.logctl, &tstamp);
1202 	    }
1203 	
1204 	    if (iflag) {
1205 		if (__pmLogRename(inarch.name, bak_base) < 0)
1206 		    abandon();
1207 		if (__pmLogRename(outarch.name, inarch.name) < 0)
1208 		    abandon();
1209 		__pmLogRemove(bak_base);
1210 	    }
1211 	
1212 	    exit(0);
1213 	}
1214 	
1215 	void
1216 	abandon(void)
1217 	{
1218 	    char    path[MAXNAMELEN+1];
1219 	    if (dflag == 0) {
1220 		if (Cflag == 0 && iflag == 0)
1221 		    fprintf(stderr, "Archive \"%s\" not created.\n", outarch.name);
1222 	
1223 		__pmLogRemove(outarch.name);
1224 		if (iflag)
1225 		    __pmLogRename(bak_base, inarch.name);
1226 		while (outarch.logctl.l_curvol >= 0) {
1227 		    snprintf(path, sizeof(path), "%s.%d", outarch.name, outarch.logctl.l_curvol);
1228 		    unlink(path);
1229 		    outarch.logctl.l_curvol--;
1230 		}
1231 		snprintf(path, sizeof(path), "%s.meta", outarch.name);
1232 		unlink(path);
1233 		snprintf(path, sizeof(path), "%s.index", outarch.name);
1234 		unlink(path);
1235 	    }
1236 	    else
1237 		fprintf(stderr, "Archive \"%s\" creation truncated.\n", outarch.name);
1238 	
1239 	    exit(1);
1240 	}