1    	/*
2    	 * Copyright (c) 1995-2001,2003 Silicon Graphics, Inc.  All Rights Reserved.
3    	 * 
4    	 * This program is free software; you can redistribute it and/or modify it
5    	 * under the terms of the GNU General Public License as published by the
6    	 * Free Software Foundation; either version 2 of the License, or (at your
7    	 * option) any later version.
8    	 * 
9    	 * This program is distributed in the hope that it will be useful, but
10   	 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11   	 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
12   	 * for more details.
13   	 */
14   	
15   	#include <ctype.h>
16   	#include <limits.h>
17   	#include <sys/stat.h>
18   	#include "logger.h"
19   	
20   	char		*configfile;
21   	__pmLogCtl	logctl;
22   	int		exit_samples = -1;       /* number of samples 'til exit */
23   	__int64_t	exit_bytes = -1;         /* number of bytes 'til exit */
24   	__int64_t	vol_bytes;		 /* total in earlier volumes */
25   	struct timeval  exit_time;               /* time interval 'til exit */
26   	int		vol_switch_samples = -1; /* number of samples 'til vol switch */
27   	__int64_t	vol_switch_bytes = -1;   /* number of bytes 'til vol switch */
28   	struct timeval	vol_switch_time;         /* time interval 'til vol switch */
29   	int		vol_samples_counter;     /* Counts samples - reset for new vol*/
30   	int		vol_switch_afid = -1;    /* afid of event for vol switch */
31   	int		parse_done;
32   	int		primary;		/* Non-zero for primary pmlogger */
33   	char	    	*archBase;		/* base name for log files */
34   	char		*pmcd_host;
35   	struct timeval	epoch;
36   	int		archive_version = PM_LOG_VERS02; /* Type of archive to create */
37   	int		linger;			/* linger with no tasks/events */
38   	int		rflag;			/* report sizes */
39   	struct timeval	delta = { 60, 0 };	/* default logging interval */
40   	int		unbuffered;		/* is -u specified? */
41   	int		qa_case;		/* QA error injection state */
42   	
43   	static int 	    pmcdfd;		/* comms to pmcd */
44   	static int	    ctx;		/* handle correspondong to ctxp below */
45   	static __pmContext  *ctxp;		/* pmlogger has just this one context */
46   	static fd_set	    fds;		/* file descriptors mask for select */
47   	static int	    numfds;		/* number of file descriptors in mask */
48   	
49   	static int	rsc_fd = -1;	/* recording session control see -x */
50   	static int	rsc_replay;
51   	static time_t	rsc_start;
52   	static char	*rsc_prog = "<unknown>";
53   	static char	*folio_name = "<unknown>";
54   	static char	*dialog_title = "PCP Archive Recording Session";
55   	
56   	/*
57   	 * flush stdio buffers
58   	 */
59   	int
60   	do_flush(void)
61   	{
62   	    int		sts;
63   	
64   	    sts = 0;
65   	    if (fflush(logctl.l_mdfp) != 0)
66   		sts = oserror();
67   	    if (fflush(logctl.l_mfp) != 0 && sts == 0)
68   		sts = oserror();
69   	    if (fflush(logctl.l_tifp) != 0 && sts == 0)
70   		sts = oserror();
71   	
72   	    return sts;
73   	}
74   	
75   	void
76   	run_done(int sts, char *msg)
77   	{
78   	#ifdef PCP_DEBUG
79   	    if (msg != NULL)
80   	    	fprintf(stderr, "pmlogger: %s, exiting\n", msg);
81   	    else
82   	    	fprintf(stderr, "pmlogger: End of run time, exiting\n");
83   	#endif
84   	
85   	    /*
86   	     * write the last last temportal index entry with the time stamp
87   	     * of the last pmResult and the seek pointer set to the offset
88   	     * _before_ the last log record
89   	     */
90   	    if (last_stamp.tv_sec != 0) {
91   		__pmTimeval	tmp;
92   		tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
93   		tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
94   		fseek(logctl.l_mfp, last_log_offset, SEEK_SET);
95   		__pmLogPutIndex(&logctl, &tmp);
96   	    }
97   	
98   	    exit(sts);
99   	}
100  	
101  	static void
102  	run_done_callback(int i, void *j)
103  	{
104  	  run_done(0, NULL);
105  	}
106  	
107  	static void
108  	vol_switch_callback(int i, void *j)
109  	{
110  	  newvolume(VOL_SW_TIME);
111  	}
112  	
113  	static int
114  	maxfd(void)
115  	{
116  	    int	max = ctlfd;
117  	    if (clientfd > max)
118  		max = clientfd;
119  	    if (pmcdfd > max)
120  		max = pmcdfd;
121  	    if (rsc_fd > max)
122  		max = rsc_fd;
123  	    return max;
124  	}
125  	
126  	/*
127  	 * tolower_str - convert a string to all lowercase
128  	 */
129  	static void 
130  	tolower_str(char *str)
131  	{
132  	  char *s = str;
133  	  while(*s){
134  	    *s = tolower(*s);
135  	    s++;
136  	  }
137  	}
138  	
139  	/*
140  	 * ParseSize - parse a size argument given in a command option
141  	 *
142  	 * The size can be in one of the following forms:
143  	 *   "40"    = sample counter of 40
144  	 *   "40b"   = byte size of 40
145  	 *   "40Kb"  = byte size of 40*1024 bytes = 40 kilobytes
146  	 *   "40Mb"  = byte size of 40*1024*1024 bytes = 40 megabytes
147  	 *   time-format = time delta in seconds
148  	 *
149  	 */
150  	static int
151  	ParseSize(char *size_arg, int *sample_counter, __int64_t *byte_size, 
152  	          struct timeval *time_delta)
153  	{
154  	  long x = 0; /* the size number */
155  	  char *ptr = NULL;
156  	
157  	  *sample_counter = -1;
158  	  *byte_size = -1;
159  	  time_delta->tv_sec = -1;
160  	  time_delta->tv_usec = -1;
161  	  
162  	  x = strtol(size_arg, &ptr, 10);
163  	
164  	  /* must be positive */
165  	  if (x <= 0) {
166  	    return -1;
167  	  }
168  	
169  	  if (*ptr == '\0') {
170  	    /* we have consumed entire string as a long */
171  	    /* => we have a sample counter */
172  	    *sample_counter = x;
173  	    return 1;
174  	  }
175  	
176  	  if (ptr != size_arg) {
177  	    /* we have a number followed by something else */
178  	
179  	    tolower_str(ptr);
180  	
181  	    /* chomp off plurals */
182  	    {
183  	      int len = strlen(ptr);
184  	      if (ptr[len-1] == 's')
185  	        ptr[len-1] = '\0';
186  	    }
187  	
188  	    /* if bytes */
189  	    if (strcmp(ptr, "b") == 0 ||
190  	        strcmp(ptr, "byte") == 0) {
191  	      *byte_size = x;
192  	      return 1;
193  	    }  
194  	
195  	    /* if kilobytes */
196  	    if (strcmp(ptr, "k") == 0 ||
197  	        strcmp(ptr, "kb") == 0 ||
198  	        strcmp(ptr, "kbyte") == 0 ||
199  	        strcmp(ptr, "kilobyte") == 0) {
200  	      *byte_size = x*1024;
201  	      return 1;
202  	    }
203  	
204  	    /* if megabytes */
205  	    if (strcmp(ptr, "m") == 0 ||
206  	        strcmp(ptr, "mb") == 0 ||
207  	        strcmp(ptr, "mbyte") == 0 ||
208  	        strcmp(ptr, "megabyte") == 0) {
209  	      *byte_size = x*1024*1024;
210  	      return 1;
211  	    }
212  	
213  	    /* if gigabytes */
214  	    if (strcmp(ptr, "g") == 0 ||
215  	        strcmp(ptr, "gb") == 0 ||
216  	        strcmp(ptr, "gbyte") == 0 ||
217  	        strcmp(ptr, "gigabyte") == 0) {
218  	      *byte_size = ((__int64_t)x)*1024*1024*1024;
219  	      return 1;
220  	    }
221  	
222  	  }
223  	  
224  	  /* Doesn't fit pattern above, try a time interval */
225  	  {
226  	    char *interval_err;
227  	
Event alloc_arg: Calling allocation function "pmParseInterval" on "interval_err". [details]
Also see events: [leaked_storage]
At conditional (1): "pmParseInterval(size_arg, time_delta, &interval_err) >= 0": Taking false branch.
228  	    if (pmParseInterval(size_arg, time_delta, &interval_err) >= 0) {
229  	      return 1;
230  	    }
Event leaked_storage: Variable "interval_err" going out of scope leaks the storage it points to.
Also see events: [alloc_arg]
231  	  }
232  	  
233  	  /* Doesn't match anything, return an error */
234  	  return -1;  
235  	}/*ParseSize*/
236  	
237  	/* time manipulation */
238  	static void
239  	tsub(struct timeval *a, struct timeval *b)
240  	{
241  	    a->tv_usec -= b->tv_usec;
242  	    if (a->tv_usec < 0) {
243  	        a->tv_usec += 1000000;
244  	        a->tv_sec--;
245  	    }
246  	    a->tv_sec -= b->tv_sec;
247  	    if (a->tv_sec < 0) {
248  	        /* clip negative values at zero */
249  	        a->tv_sec = 0;
250  	        a->tv_usec = 0;
251  	    }
252  	}
253  	
254  	static char *
255  	do_size(double d)
256  	{
257  	    static char nbuf[100];
258  	
259  	    if (d < 10 * 1024)
260  		sprintf(nbuf, "%ld bytes", (long)d);
261  	    else if (d < 10.0 * 1024 * 1024)
262  		sprintf(nbuf, "%.1f Kbytes", d/1024);
263  	    else if (d < 10.0 * 1024 * 1024 * 1024)
264  		sprintf(nbuf, "%.1f Mbytes", d/(1024 * 1024));
265  	    else
266  		sprintf(nbuf, "%ld Mbytes", (long)d/(1024 * 1024));
267  	    
268  	    return nbuf;
269  	}
270  	
271  	/*
272  	 * add text identified by p to the malloc buffer at bp[0] ... bp[nchar -1]
273  	 * return the length of the result or -1 for an error
274  	 */
275  	static int
276  	add_msg(char **bp, int nchar, char *p)
277  	{
278  	    int		add_len;
279  	
280  	    if (nchar < 0 || p == NULL)
281  		return nchar;
282  	
283  	    add_len = (int)strlen(p);
284  	    if (nchar == 0)
285  		add_len++;
286  	    if ((*bp = realloc(*bp, nchar+add_len)) == NULL)
287  		return -1;
288  	    if (nchar == 0)
289  		strcpy(*bp, p);
290  	    else
291  		strcat(&(*bp)[nchar-1], p);
292  	
293  	    return nchar+add_len;
294  	}
295  	
296  	
297  	/*
298  	 * generate dialog/message when launching application wishes to break
299  	 * its association with pmlogger
300  	 *
301  	 * cmd is one of the following:
302  	 *	D	detach pmlogger and let it run forever
303  	 *	Q	terminate pmlogger
304  	 *	?	display status
305  	 *	X	fatal error or application exit ... user must decide
306  	 *		to detach or quit
307  	 */
308  	void
309  	do_dialog(char cmd)
310  	{
311  	    FILE	*msgf = NULL;
312  	    time_t	now;
313  	    static char	lbuf[100+MAXPATHLEN];
314  	    double	archsize;
315  	    char	*q;
316  	    char	*p = NULL;
317  	    int		nchar;
318  	    char	*msg;
319  	#if HAVE_MKSTEMP
320  	    char	tmp[MAXPATHLEN];
321  	#endif
322  	
323  	    /*
324  	     * flush archive buffers so size is accurate
325  	     */
326  	    do_flush();
327  	
328  	    time(&now);
329  	    now -= rsc_start;
330  	    if (now == 0)
331  		/* hack is close enough! */
332  		now = 1;
333  	
334  	    archsize = vol_bytes + ftell(logctl.l_mfp);
335  	
336  	    nchar = add_msg(&p, 0, "");
337  	    p[0] = '\0';
338  	
339  	    sprintf(lbuf, "PCP recording for the archive folio \"%s\" and the host", folio_name);
340  	    nchar = add_msg(&p, nchar, lbuf);
341  	    sprintf(lbuf, " \"%s\" has been in progress for %ld %s",
342  		pmcd_host,
343  		now < 240 ? now : now/60, now < 240 ? "seconds" : "minutes");
344  	    nchar = add_msg(&p, nchar, lbuf);
345  	    nchar = add_msg(&p, nchar, " and in that time the pmlogger process has created an");
346  	    nchar = add_msg(&p, nchar, " archive of ");
347  	    q = do_size(archsize);
348  	    nchar = add_msg(&p, nchar, q);
349  	    nchar = add_msg(&p, nchar, ".");
350  	    if (rsc_replay) {
351  		nchar = add_msg(&p, nchar, "\n\nThis archive may be replayed with the following command:\n");
352  		sprintf(lbuf, "  $ pmafm %s replay", folio_name);
353  		nchar = add_msg(&p, nchar, lbuf);
354  	    }
355  	
356  	    if (cmd == 'D') {
357  		nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has asked pmlogger");
358  		nchar = add_msg(&p, nchar, " to continue independently and the PCP archive will grow at");
359  		nchar = add_msg(&p, nchar, " the rate of ");
360  		q = do_size((archsize * 3600) / now);
361  		nchar = add_msg(&p, nchar, q);
362  		nchar = add_msg(&p, nchar, " per hour or ");
363  		q = do_size((archsize * 3600 * 24) / now);
364  		nchar = add_msg(&p, nchar, q);
365  		nchar = add_msg(&p, nchar, " per day.");
366  	    }
367  	
368  	    if (cmd == 'X') {
369  		nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has exited and you");
370  		nchar = add_msg(&p, nchar, " must decide if the PCP recording session should be terminated");
371  		nchar = add_msg(&p, nchar, " or continued.  If recording is continued the PCP archive will");
372  		nchar = add_msg(&p, nchar, " grow at the rate of ");
373  		q = do_size((archsize * 3600) / now);
374  		nchar = add_msg(&p, nchar, q);
375  		nchar = add_msg(&p, nchar, " per hour or ");
376  		q = do_size((archsize * 3600 * 24) / now);
377  		nchar = add_msg(&p, nchar, q);
378  		nchar = add_msg(&p, nchar, " per day.");
379  	    }
380  	
381  	    if (cmd == 'Q') {
382  		nchar = add_msg(&p, nchar, "\n\nThe application that launched pmlogger has terminated");
383  		nchar = add_msg(&p, nchar, " this PCP recording session.\n");
384  	    }
385  	
386  	    if (cmd != 'Q') {
387  		nchar = add_msg(&p, nchar, "\n\nAt any time this pmlogger process may be terminated with the");
388  		nchar = add_msg(&p, nchar, " following command:\n");
389  		sprintf(lbuf, "  $ pmsignal -s TERM %" FMT_PID "\n", getpid());
390  		nchar = add_msg(&p, nchar, lbuf);
391  	    }
392  	
393  	    if (cmd == 'X')
394  		nchar = add_msg(&p, nchar, "\n\nTerminate this PCP recording session now?");
395  	
396  	    if (nchar > 0) {
397  		char * xconfirm = __pmNativePath(pmGetConfig("PCP_XCONFIRM_PROG"));
398  		int fd = -1;
399  	
400  	#if HAVE_MKSTEMP
401  		sprintf(tmp, "%s%cmsgXXXXXX", pmGetConfig("PCP_TMP_DIR"), __pmPathSeparator());
402  		msg = tmp;
403  		fd = mkstemp(tmp);
404  	#else
405  		if ((msg = tmpnam(NULL)) != NULL)
406  		    fd = open(msg, O_WRONLY|O_CREAT|O_EXCL, 0600);
407  	#endif
408  		if (fd >= 0)
409  		    msgf = fdopen(fd, "w");
410  		if (msgf == NULL) {
411  		    fprintf(stderr, "\nError: failed create temporary message file for recording session dialog\n");
412  		    fprintf(stderr, "Reason? %s\n", osstrerror());
413  		    if (fd != -1)
414  			close(fd);
415  		    goto failed;
416  		}
417  		fputs(p, msgf);
418  		fclose(msgf);
419  		msgf = NULL;
420  	
421  		if (cmd == 'X')
422  		    sprintf(lbuf, "%s -c -header \"%s - %s\" -file %s -icon question "
423  				  "-B Yes -b No 2>/dev/null",
424  			    xconfirm, dialog_title, rsc_prog, msg);
425  		else
426  		    sprintf(lbuf, "%s -c -header \"%s - %s\" -file %s -icon info "
427  				  "-b Close 2>/dev/null",
428  			    xconfirm, dialog_title, rsc_prog, msg);
429  	
430  		if ((msgf = popen(lbuf, "r")) == NULL) {
431  		    fprintf(stderr, "\nError: failed to start command for recording session dialog\n");
432  		    fprintf(stderr, "Command: \"%s\"\n", lbuf);
433  		    goto failed;
434  		}
435  	
436  		if (fgets(lbuf, sizeof(lbuf), msgf) == NULL) {
437  		    fprintf(stderr, "\n%s: pmconfirm(1) failed for recording session dialog\n",
438  			    cmd == '?' ? "Warning" : "Error");
439  	failed:
440  		    fprintf(stderr, "Dialog:\n");
441  		    fputs(p, stderr);
442  		    strcpy(lbuf, "Yes");
443  		}
444  		else {
445  		    /* strip at first newline */
446  		    for (q = lbuf; *q && *q != '\n'; q++)
447  			;
448  		    *q = '\0';
449  		}
450  	
451  		if (msgf != NULL)
452  		    pclose(msgf);
453  		unlink(msg);
454  	    }
455  	    else {
456  		fprintf(stderr, "Error: failed to create recording session dialog message!\n");
457  		fprintf(stderr, "Reason? %s\n", osstrerror());
458  		strcpy(lbuf, "Yes");
459  	    }
460  	
461  	    free(p);
462  	
463  	    if (cmd == 'Q' || (cmd == 'X' && strcmp(lbuf, "Yes") == 0)) {
464  		run_done(0, "Recording session terminated");
465  	    }
466  	
467  	    if (cmd != '?') {
468  		/* detach, silently go off to the races ... */
469  		close(rsc_fd);
470  		FD_CLR(rsc_fd, &fds);
471  		rsc_fd = -1;
472  	    }
473  	}
474  	
475  	
476  	int
477  	main(int argc, char **argv)
478  	{
479  	    int			c;
480  	    int			sts;
481  	    int			sep = __pmPathSeparator();
482  	    int			errflag = 0;
483  	    char		local[MAXHOSTNAMELEN];
484  	    char		*pmnsfile = PM_NS_DEFAULT;
485  	    char		*logfile = "pmlogger.log";
486  					    /* default log (not archive) file name */
487  	    char		*endnum;
488  	    int			i;
489  	    task_t		*tp;
490  	    optcost_t		ocp;
491  	    fd_set		readyfds;
492  	    char		*p;
493  	    char		*runtime = NULL;
494  	
495  	    __pmSetProgname(argv[0]);
496  	
497  	    /*
498  	     * Warning:
499  	     *		If any of the pmlogger options change, make sure the
500  	     *		corresponding changes are made to pmnewlog when pmlogger
501  	     *		options are passed through from the control file
502  	     */
503  	    while ((c = getopt(argc, argv, "c:D:h:l:Ln:Prs:T:t:uv:V:x:?")) != EOF) {
504  		switch (c) {
505  	
506  		case 'c':		/* config file */
507  		    if (access(optarg, F_OK) == 0)
508  			configfile = optarg;
509  		    else {
510  			/* does not exist as given, try the standard place */
511  			char *vardir = pmGetConfig("PCP_VAR_DIR");
512  			int sz = strlen(vardir)+strlen("/config/pmlogger/")+strlen(optarg)+1;
513  			if ( (configfile = (char *)malloc(sz)) == NULL ) {
514  			    __pmNoMem("config file name", sz, PM_FATAL_ERR);
515  			}
516  			sprintf(configfile,
517  				"%s%c" "config" "%c" "pmlogger" "%c%s",
518  				vardir, sep, sep, sep, optarg);
519  			if (access(configfile, F_OK) != 0) {
520  			    /* still no good, error handling happens below */
521  			    free(configfile);
522  			    configfile = optarg;
523  			}
524  		    }
525  		    break;
526  	
527  		case 'D':	/* debug flag */
528  		    sts = __pmParseDebug(optarg);
529  		    if (sts < 0) {
530  			fprintf(stderr, "%s: unrecognized debug flag specification (%s)\n",
531  			    pmProgname, optarg);
532  			errflag++;
533  		    }
534  		    else
535  			pmDebug |= sts;
536  		    break;
537  	
538  		case 'h':		/* hostname for PMCD to contact */
539  		    pmcd_host = optarg;
540  		    break;
541  	
542  		case 'l':		/* log file name */
543  		    logfile = optarg;
544  		    break;
545  	
546  		case 'L':		/* linger if not primary logger */
547  		    linger = 1;
548  		    break;
549  	
550  		case 'n':		/* alternative name space file */
551  		    pmnsfile = optarg;
552  		    break;
553  	
554  		case 'P':		/* this is the primary pmlogger */
555  		    primary = 1;
556  		    break;
557  	
558  		case 'r':		/* report sizes of pmResult records */
559  		    rflag = 1;
560  		    break;
561  	
562  		case 's':		/* exit size */
563  		    if (ParseSize(optarg, &exit_samples, &exit_bytes, 
564  			&exit_time) < 0) {
565  		      fprintf(stderr, "%s: illegal size argument '%s' for -s\n", 
566  	                      pmProgname, optarg);
567  		      errflag++;
568  	            }
569  		    if (exit_time.tv_sec > 0) {
570  		      __pmAFregister(&exit_time, NULL, run_done_callback);
571  	            }
572  		    break;
573  	
574  		case 'T':		/* end time */
575  		    runtime = optarg;
576  	            break;
577  	
578  		case 't':		/* change default logging interval */
579  		    if (pmParseInterval(optarg, &delta, &p) < 0) {
580  			fprintf(stderr, "%s: illegal -t argument\n", pmProgname);
581  			fputs(p, stderr);
582  			free(p);
583  			errflag++;
584  		    }
585  		    break;
586  	
587  		case 'u':		/* flush output buffers after each fetch */
588  		    unbuffered = 1;
589  		    break;
590  	
591  		case 'v':		/* volume switch after given size */
592  		    if (ParseSize(optarg, &vol_switch_samples, &vol_switch_bytes,
593  	                &vol_switch_time) < 0) {
594  		      fprintf(stderr, "%s: illegal size argument '%s' for -v\n", 
595  	                      pmProgname, optarg);
596  		      errflag++;
597  	            }
598  		    if (vol_switch_time.tv_sec > 0) {
599  		      vol_switch_afid = __pmAFregister(&vol_switch_time, NULL, 
600  	                                           vol_switch_callback);
601  	            }
602  		    break;
603  	
604  	        case 'V': 
605  		    archive_version = (int)strtol(optarg, &endnum, 10);
606  	            if (*endnum != '\0' ||
607  			(archive_version != PM_LOG_VERS01 &&
608  	                 archive_version != PM_LOG_VERS02)) {
609  	                fprintf(stderr, "%s: -V requires a version number of "
610  	                        "%d or %d\n", pmProgname, 
611  	                        PM_LOG_VERS01, PM_LOG_VERS02); 
612  			errflag++;
613  	            }
614  		    break;
615  	
616  		case 'x':		/* recording session control fd */
617  		    rsc_fd = (int)strtol(optarg, &endnum, 10);
618  		    if (*endnum != '\0' || rsc_fd < 0) {
619  			fprintf(stderr, "%s: -x requires a non-negative numeric argument\n", pmProgname);
620  			errflag++;
621  		    }
622  		    time(&rsc_start);
623  		    break;
624  	
625  		case '?':
626  		default:
627  		    errflag++;
628  		    break;
629  		}
630  	    }
631  	
632  	    if (errflag || optind != argc-1) {
633  		fprintf(stderr,
634  	"Usage: %s [options] archive\n\
635  	\n\
636  	Options:\n\
637  	  -c configfile file to load configuration from\n\
638  	  -h host	metrics source is PMCD on host\n\
639  	  -l logfile	redirect diagnostics and trace output\n\
640  	  -L		linger, even if not primary logger instance and nothing to log\n\
641  	  -n pmnsfile   use an alternative PMNS\n\
642  	  -P		execute as primary logger instance\n\
643  	  -r		report record sizes and archive growth rate\n\
644  	  -s endsize	terminate after endsize has been accumulated\n\
645  	  -t interval   default logging interval [default 60.0 seconds]\n\
646  	  -T endtime	terminate at given time\n\
647  	  -u		output is unbuffered\n\
648  	  -v volsize	switch log volumes after volsize has been accumulated\n\
649  	  -V version    generate version 1 or 2 archives (default is 2)\n\
650  	  -x fd		control file descriptor for application launching pmlogger\n\
651  			via pmRecordControl(3)\n",
652  				pmProgname);
653  		exit(1);
654  	    }
655  	
656  	    if (primary && pmcd_host != NULL) {
657  		fprintf(stderr, "%s: -P and -h are mutually exclusive ... use -P only when running\n%s on the same (local) host as the PMCD to which it connects.\n", pmProgname, pmProgname);
658  		exit(1);
659  	    }
660  	
661  	    __pmOpenLog("pmlogger", logfile, stderr, &sts);
662  	
663  	    /* base name for archive is here ... */
664  	    archBase = argv[optind];
665  	
666  	    if (pmcd_host == NULL || strcmp(pmcd_host, "localhost") == 0) {
667  		(void)gethostname(local, MAXHOSTNAMELEN);
668  		local[MAXHOSTNAMELEN-1] = '\0';
669  		pmcd_host = local;
670  	    }
671  	
672  	    /* initialise access control */
673  	    if (__pmAccAddOp(PM_OP_LOG_ADV) < 0 ||
674  		__pmAccAddOp(PM_OP_LOG_MAND) < 0 ||
675  		__pmAccAddOp(PM_OP_LOG_ENQ) < 0) {
676  		fprintf(stderr, "%s: access control initialisation failed\n", pmProgname);
677  		exit(1);
678  	    }
679  	
680  	    if (pmnsfile != PM_NS_DEFAULT) {
681  		if ((sts = pmLoadNameSpace(pmnsfile)) < 0) {
682  		    fprintf(stderr, "%s: Cannot load namespace from \"%s\": %s\n", pmProgname, pmnsfile, pmErrStr(sts));
683  		    exit(1);
684  		}
685  	    }
686  	
687  	    if ((ctx = pmNewContext(PM_CONTEXT_HOST, pmcd_host)) < 0) {
688  		fprintf(stderr, "%s: Cannot connect to PMCD on host \"%s\": %s\n", pmProgname, pmcd_host, pmErrStr(ctx));
689  		exit(1);
690  	    }
691  	
692  	    if (rsc_fd == -1) {
693  		/* no -x, so register client id with pmcd */
694  		__pmSetClientIdArgv(argc, argv);
695  	    }
696  	
697  	    /*
698  	     * discover fd for comms channel to PMCD ... 
699  	     */
700  	    ctxp = __pmHandleToPtr(ctx);
701  	    pmcdfd = ctxp->c_pmcd->pc_fd;
702  	    pmcd_host = ctxp->c_pmcd->pc_hosts[0].name;
703  	
704  	    if (configfile != NULL) {
705  		if ((yyin = fopen(configfile, "r")) == NULL) {
706  		    fprintf(stderr, "%s: Cannot open config file \"%s\": %s\n",
707  			pmProgname, configfile, osstrerror());
708  		    exit(1);
709  		}
710  	    }
711  	    else {
712  		/* **ANY** Lex would read from stdin automagically */
713  		configfile = "<stdin>";
714  	    }
715  	
716  	    __pmOptFetchGetParams(&ocp);
717  	    ocp.c_scope = 1;
718  	    __pmOptFetchPutParams(&ocp);
719  	
720  	    /* prevent early timer events ... */
721  	    __pmAFblock();
722  	
723  	    if (yyparse() != 0)
724  		exit(1);
725  	
726  	    if ( configfile != NULL ) {
727  		fclose(yyin);
728  	    }
729  	
730  	#ifdef PCP_DEBUG
731  	    fprintf(stderr, "Config parsed\n");
732  	#endif
733  	
734  	    fprintf(stderr, "Starting %slogger for host \"%s\"\n",
735  		primary ? "primary " : "", pmcd_host);
736  	
737  	#ifdef PCP_DEBUG
738  	    if (pmDebug & DBG_TRACE_LOG) {
739  		fprintf(stderr, "optFetch Cost Parameters: pmid=%d indom=%d fetch=%d scope=%d\n",
740  			ocp.c_pmid, ocp.c_indom, ocp.c_fetch, ocp.c_scope);
741  	
742  		fprintf(stderr, "\nAfter loading config ...\n");
743  		for (tp = tasklist; tp != NULL; tp = tp->t_next) {
744  		    fprintf(stderr, " state: %sin log, %savail, %s, %s",
745  			PMLC_GET_INLOG(tp->t_state) ? "" : "not ",
746  			PMLC_GET_AVAIL(tp->t_state) ? "" : "un",
747  			PMLC_GET_MAND(tp->t_state) ? "mand" : "adv",
748  			PMLC_GET_ON(tp->t_state) ? "on" : "off");
749  		    fprintf(stderr, " delta: %ld usec", 
750  				(long)1000 * tp->t_delta.tv_sec + tp->t_delta.tv_usec);
751  		    fprintf(stderr, " numpmid: %d\n", tp->t_numpmid);
752  		    for (i = 0; i < tp->t_numpmid; i++) {
753  			fprintf(stderr, "  %s (%s):\n", pmIDStr(tp->t_pmidlist[i]), tp->t_namelist[i]);
754  		    }
755  		    __pmOptFetchDump(stderr, tp->t_fetch);
756  		}
757  	    }
758  	#endif
759  	
760  	    if (!primary && tasklist == NULL && !linger) {
761  		fprintf(stderr, "Nothing to log, and not the primary logger instance ... good-bye\n");
762  		exit(1);
763  	    }
764  	
765  	    if ((sts = __pmLogCreate(pmcd_host, archBase, archive_version, &logctl)) < 0) {
766  		fprintf(stderr, "__pmLogCreate: %s\n", pmErrStr(sts));
767  		exit(1);
768  	    }
769  	    else {
770  		/*
771  		 * try and establish $TZ from the remote PMCD ...
772  		 * Note the label record has been set up, but not written yet
773  		 */
774  		char		*name = "pmcd.timezone";
775  		pmID		pmid;
776  		pmResult	*resp;
777  	
778  		__pmtimevalNow(&epoch);
779  		sts = pmUseContext(ctx);
780  		if (sts >= 0)
781  		    sts = pmLookupName(1, &name, &pmid);
782  		if (sts >= 0) {
783  		    sts = pmFetch(1, &pmid, &resp);
784  		}
785  		if (sts >= 0 && resp->vset[0]->numval > 0) {
786  		    strcpy(logctl.l_label.ill_tz, resp->vset[0]->vlist[0].value.pval->vbuf);
787  		    /* prefer to use remote time to avoid clock drift problems */
788  		    epoch = resp->timestamp;		/* struct assignment */
789  		    pmFreeResult(resp);
790  		    pmNewZone(logctl.l_label.ill_tz);
791  	        }
792  	#ifdef PCP_DEBUG
793  	    	else if (pmDebug & DBG_TRACE_LOG) {
794  			fprintf(stderr, 
795  				"main: Could not get timezone from host %s\n",
796  				pmcd_host);
797  		}
798  	#endif
799  	    }
800  	
801  	    /* do ParseTimeWindow stuff for -T */
802  	    if (runtime) {
803  	        struct timeval res_end;    /* time window end */
804  	        struct timeval start;
805  	        struct timeval end;
806  	        struct timeval last_delta;
807  	        char *err_msg;             /* parsing error message */
808  	        time_t now;
809  	        struct timeval now_tv;
810  	
811  	        time(&now);
812  	        now_tv.tv_sec = now;
813  	        now_tv.tv_usec = 0; 
814  	
815  	        start = now_tv;
816  	        end.tv_sec = INT_MAX;
817  	        end.tv_usec = INT_MAX;
818  	        sts = __pmParseTime(runtime, &start, &end, &res_end, &err_msg);
819  	        if (sts < 0) {
820  		    fprintf(stderr, "%s: illegal -T argument\n%s", pmProgname, err_msg);
821  	            exit(1);
822  	        }
823  	
824  	        last_delta = res_end;
825  	        tsub(&last_delta, &now_tv);
826  		__pmAFregister(&last_delta, NULL, run_done_callback);
827  	
828  	        last_stamp = res_end;
829  	    }
830  	
831  	    fprintf(stderr, "Archive basename: %s\n", archBase);
832  	
833  	#ifndef IS_MINGW
834  	    /* detach yourself from the launching process */
835  	    setpgid(getpid(), 0);
836  	#endif
837  	
838  	    /* set up control port */
839  	    init_ports();
840  	    FD_ZERO(&fds);
841  	    FD_SET(ctlfd, &fds);
842  	#ifndef IS_MINGW
843  	    FD_SET(pmcdfd, &fds);
844  	#endif
845  	    if (rsc_fd != -1)
846  		FD_SET(rsc_fd, &fds);
847  	    numfds = maxfd() + 1;
848  	
849  	    if ((sts = do_preamble()) < 0)
850  		fprintf(stderr, "Warning: problem writing archive preamble: %s\n",
851  		    pmErrStr(sts));
852  	
853  	    sts = 0;		/* default exit status */
854  	
855  	    parse_done = 1;	/* enable callback processing */
856  	    __pmAFunblock();
857  	
858  	    for ( ; ; ) {
859  		int		nready;
860  	
861  		memcpy(&readyfds, &fds, sizeof(readyfds));
862  		nready = select(numfds, &readyfds, NULL, NULL, NULL);
863  	
864  		if (wantflush) {
865  		    /*
866  		     * flush request via SIGUSR1
867  		     */
868  		    do_flush();
869  		    wantflush = 0;
870  		}
871  	
872  		if (nready > 0) {
873  		    /* block signals to simplify IO handling */
874  		    __pmAFblock();
875  	
876  		    /* handle request on control port */
877  		    if (FD_ISSET(ctlfd, &readyfds)) {
878  			if (control_req()) {
879  			    /* new client has connected */
880  			    FD_SET(clientfd, &fds);
881  			    if (clientfd >= numfds)
882  				numfds = clientfd + 1;
883  			}
884  		    }
885  		    if (clientfd >= 0 && FD_ISSET(clientfd, &readyfds)) {
886  			/* process request from client, save clientfd in case client
887  			 * closes connection, resetting clientfd to -1
888  			 */
889  			int	fd = clientfd;
890  	
891  			if (client_req()) {
892  			    /* client closed connection */
893  			    FD_CLR(fd, &fds);
894  			    numfds = maxfd() + 1;
895  			    qa_case = 0;
896  			}
897  		    }
898  	#ifndef IS_MINGW
899  		    if (pmcdfd >= 0 && FD_ISSET(pmcdfd, &readyfds)) {
900  			/*
901  			 * do not expect this, given synchronous commumication with the
902  			 * pmcd ... either pmcd has terminated, or bogus PDU ... or its
903  			 * Win32 and we are operating under the different conditions of
904  			 * our AF.c implementation there, which has to deal with a lack
905  			 * of signal support on Windows - race condition exists between
906  			 * this check and the async event timer callback.
907  			 */
908  			__pmPDU		*pb;
909  			__pmPDUHdr	*php;
910  			sts = __pmGetPDU(pmcdfd, ANY_SIZE, TIMEOUT_NEVER, &pb);
911  			if (sts <= 0) {
912  			    if (sts < 0)
913  				fprintf(stderr, "Error: __pmGetPDU: %s\n", pmErrStr(sts));
914  			    disconnect(sts);
915  			}
916  			else {
917  			    php = (__pmPDUHdr *)pb;
918  			    fprintf(stderr, "Error: Unsolicited %s PDU from PMCD\n",
919  				__pmPDUTypeStr(php->type));
920  			    disconnect(PM_ERR_IPC);
921  			}
922  		    }
923  	#endif
924  		    if (rsc_fd >= 0 && FD_ISSET(rsc_fd, &readyfds)) {
925  			/*
926  			 * some action on the recording session control fd
927  			 * end-of-file means launcher has quit, otherwise we
928  			 * expect one of these commands
929  			 *	V<number>\n	- version
930  			 *	F<folio>\n	- folio name
931  			 *	P<name>\n	- launcher's name
932  			 *	R\n		- launcher can replay
933  			 *	D\n		- detach from launcher
934  			 *	Q\n		- quit pmlogger
935  			 */
936  			char	rsc_buf[MAXPATHLEN];
937  			char	*rp = rsc_buf;
938  			char	myc;
939  			int	fake_x = 0;
940  	
941  			for (rp = rsc_buf; ; rp++) {
942  			    if (read(rsc_fd, &myc, 1) <= 0) {
943  	#ifdef PCP_DEBUG
944  				if (pmDebug & DBG_TRACE_APPL2)
945  				    fprintf(stderr, "recording session control: eof\n");
946  	#endif
947  				if (rp != rsc_buf) {
948  				    *rp = '\0';
949  				    fprintf(stderr, "Error: incomplete recording session control message: \"%s\"\n", rsc_buf);
950  				}
951  				fake_x = 1;
952  				break;
953  			    }
954  			    if (rp >= &rsc_buf[MAXPATHLEN]) {
955  				fprintf(stderr, "Error: absurd recording session control message: \"%100.100s ...\"\n", rsc_buf);
956  				fake_x = 1;
957  				break;
958  			    }
959  			    if (myc == '\n') {
960  				*rp = '\0';
961  				break;
962  			    }
963  			    *rp = myc;
964  			}
965  	
966  	#ifdef PCP_DEBUG
967  			if (pmDebug & DBG_TRACE_APPL2) {
968  			    if (fake_x == 0)
969  				fprintf(stderr, "recording session control: \"%s\"\n", rsc_buf);
970  			}
971  	#endif
972  	
973  			if (fake_x)
974  			    do_dialog('X');
975  			else if (strcmp(rsc_buf, "Q") == 0 ||
976  			         strcmp(rsc_buf, "D") == 0 ||
977  				 strcmp(rsc_buf, "?") == 0)
978  			    do_dialog(rsc_buf[0]);
979  			else if (rsc_buf[0] == 'F')
980  			    folio_name = strdup(&rsc_buf[1]);
981  			else if (rsc_buf[0] == 'P')
982  			    rsc_prog = strdup(&rsc_buf[1]);
983  			else if (strcmp(rsc_buf, "R") == 0)
984  			    rsc_replay = 1;
985  			else if (rsc_buf[0] == 'V' && rsc_buf[1] == '0') {
986  			    /*
987  			     * version 0 of the recording session control ...
988  			     * this is all we grok at the moment
989  			     */
990  			    ;
991  			}
992  			else {
993  			    fprintf(stderr, "Error: illegal recording session control message: \"%s\"\n", rsc_buf);
994  			    do_dialog('X');
995  			}
996  		    }
997  	
998  		    __pmAFunblock();
999  		}
1000 		else if (nready < 0 && neterror() != EINTR)
1001 		    fprintf(stderr, "Error: select: %s\n", netstrerror());
1002 	    }
1003 	
1004 	}
1005 	
1006 	
1007 	int
1008 	newvolume(int vol_switch_type)
1009 	{
1010 	    FILE	*newfp;
1011 	    int		nextvol = logctl.l_curvol + 1;
1012 	    time_t	now;
1013 	    static char *vol_sw_strs[] = {
1014 	       "SIGHUP", "pmlc request", "sample counter",
1015 	       "sample byte size", "sample time", "max data volume size"
1016 	    };
1017 	
1018 	    vol_samples_counter = 0;
1019 	    vol_bytes += ftell(logctl.l_mfp);
1020 	    if (exit_bytes != -1) {
1021 	        if (vol_bytes >= exit_bytes) 
1022 		    run_done(0, "Byte limit reached");
1023 	    }
1024 	
1025 	    /* 
1026 	     * If we are not part of a callback but instead a 
1027 	     * volume switch from "pmlc" or a "SIGHUP" then
1028 	     * get rid of pending volume switch in event queue
1029 	     * as it will now be wrong, and schedule a new
1030 	     * volume switch event.
1031 	     */
1032 	    if (vol_switch_afid >= 0 && vol_switch_type != VOL_SW_TIME) {
1033 	      __pmAFunregister(vol_switch_afid);
1034 	      vol_switch_afid = __pmAFregister(&vol_switch_time, NULL,
1035 	                                   vol_switch_callback);
1036 	    }
1037 	
1038 	    if ((newfp = __pmLogNewFile(archBase, nextvol)) != NULL) {
1039 		if (logctl.l_state == PM_LOG_STATE_NEW) {
1040 		    /*
1041 		     * nothing has been logged as yet, force out the label records
1042 		     */
1043 		    __pmtimevalNow(&last_stamp);
1044 		    logctl.l_label.ill_start.tv_sec = (__int32_t)last_stamp.tv_sec;
1045 		    logctl.l_label.ill_start.tv_usec = (__int32_t)last_stamp.tv_usec;
1046 		    logctl.l_label.ill_vol = PM_LOG_VOL_TI;
1047 		    __pmLogWriteLabel(logctl.l_tifp, &logctl.l_label);
1048 		    logctl.l_label.ill_vol = PM_LOG_VOL_META;
1049 		    __pmLogWriteLabel(logctl.l_mdfp, &logctl.l_label);
1050 		    logctl.l_label.ill_vol = 0;
1051 		    __pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
1052 		    logctl.l_state = PM_LOG_STATE_INIT;
1053 		}
1054 	#if 0
1055 		if (last_stamp.tv_sec != 0) {
1056 		    __pmTimeval	tmp;
1057 		    tmp.tv_sec = (__int32_t)last_stamp.tv_sec;
1058 		    tmp.tv_usec = (__int32_t)last_stamp.tv_usec;
1059 		    __pmLogPutIndex(&logctl, &tmp);
1060 		}
1061 	#endif
1062 		fclose(logctl.l_mfp);
1063 		logctl.l_mfp = newfp;
1064 		logctl.l_label.ill_vol = logctl.l_curvol = nextvol;
1065 		__pmLogWriteLabel(logctl.l_mfp, &logctl.l_label);
1066 		fflush(logctl.l_mfp);
1067 		time(&now);
1068 		fprintf(stderr, "New log volume %d, via %s at %s",
1069 			nextvol, vol_sw_strs[vol_switch_type], ctime(&now));
1070 		return nextvol;
1071 	    }
1072 	    else
1073 		return -oserror();
1074 	}
1075 	
1076 	
1077 	void
1078 	disconnect(int sts)
1079 	{
1080 	    time_t  now;
1081 	
1082 	    time(&now);
1083 	    if (sts != 0)
1084 		fprintf(stderr, "%s: Error: %s\n", pmProgname, pmErrStr(sts));
1085 	    fprintf(stderr, "%s: Lost connection to PMCD on \"%s\" at %s",
1086 		    pmProgname, ctxp->c_pmcd->pc_hosts[0].name, ctime(&now));
1087 	#if CAN_RECONNECT
1088 	    if (primary) {
1089 		fprintf(stderr, "This is fatal for the primary logger.");
1090 		exit(1);
1091 	    }
1092 	    close(pmcdfd);
1093 	    FD_CLR(pmcdfd, &fds);
1094 	    pmcdfd = -1;
1095 	    numfds = maxfd() + 1;
1096 	    ctxp->c_pmcd->pc_fd = -1;
1097 	#else
1098 	    exit(1);
1099 	#endif
1100 	}
1101 	
1102 	#if CAN_RECONNECT
1103 	int
1104 	reconnect(void)
1105 	{
1106 	    int	    sts;
1107 	
1108 	    sts = pmReconnectContext(ctx);
1109 	    if (sts >= 0) {
1110 		time_t      now;
1111 		time(&now);
1112 		fprintf(stderr, "%s: re-established connection to PMCD on \"%s\" at %s\n",
1113 			pmProgname, ctxp->c_pmcd->pc_name, ctime(&now));
1114 		pmcdfd = ctxp->c_pmcd->pc_fd;
1115 		FD_SET(pmcdfd, &fds);
1116 		numfds = maxfd() + 1;
1117 	    }
1118 	    return sts;
1119 	}
1120 	#endif
1121 	
1122