1    	/*
2    	 * Copyright (c) 1995-2002,2004 Silicon Graphics, Inc.  All Rights Reserved.
3    	 * Copyright (c) 2010 Ken McDonell.  All Rights Reserved.
4    	 * 
5    	 * This library is free software; you can redistribute it and/or modify it
6    	 * under the terms of the GNU Lesser General Public License as published
7    	 * by the Free Software Foundation; either version 2.1 of the License, or
8    	 * (at your option) any later version.
9    	 * 
10   	 * This library is distributed in the hope that it will be useful, but
11   	 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
12   	 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public
13   	 * License for more details.
14   	 */
15   	
16   	#include "pmapi.h"
17   	#include "impl.h"
18   	#include "pmda.h"
19   	#include <ctype.h>
20   	#include <sys/stat.h>
21   	
22   	static __pmDSO *dsotab;
23   	static int	numdso = -1;
24   	
25   	static int
26   	build_dsotab(void)
27   	{
28   	    /*
29   	     * parse pmcd's config file extracting details from dso lines
30   	     *
31   	     * very little syntactic checking here ... pmcd(1) does that job
32   	     * nicely and even if we get confused, the worst thing that happens
33   	     * is we don't include one or more of the DSO PMDAs in dsotab[]
34   	     *
35   	     * lines for DSO PMDAs generally look like this ...
36   	     * Name	Domain	Type	Init Routine	Path
37   	     * mmv	70	dso	mmv_init	/var/lib/pcp/pmdas/mmv/pmda_mmv.so 
38   	     *
39   	     */
40   	    char	configFileName[MAXPATHLEN];
41   	    FILE	*configFile;
42   	    char	*config;
43   	    char	*p;
44   	    char	*q;
45   	    struct stat	sbuf;
46   	    int		lineno = 1;
47   	    int		domain;
48   	    char	*init;
49   	    char	*name;
50   	    char	peekc;
51   	
52   	    numdso = 0;
53   	    dsotab = NULL;
54   	
55   	    strcpy(configFileName, pmGetConfig("PCP_PMCDCONF_PATH"));
56   	#ifdef PCP_DEBUG
57   	    if (pmDebug & DBG_TRACE_CONTEXT) {
58   		fprintf(stderr, "build_dsotab: parsing %s\n", configFileName);
59   	    }
60   	#endif
61   	    if (stat(configFileName, &sbuf) < 0) {
62   		return -oserror();
63   	    }
64   	    configFile = fopen(configFileName, "r");
65   	    if (configFile == NULL) {
66   		return -oserror();
67   	    }
Event alloc_fn: Calling allocation function "malloc".
Event var_assign: Assigning: "config" = storage returned from "malloc(sbuf.st_size + 1L)".
Also see events: [noescape][leaked_storage]
At conditional (1): "(config = malloc(sbuf.st_size + 1L)) == NULL": Taking false branch.
68   	    if ((config = malloc(sbuf.st_size+1)) == NULL) {
69   		__pmNoMem("build_dsotbl:", sbuf.st_size+1, PM_RECOV_ERR);
70   		fclose(configFile);
71   		return -oserror();
72   	    }
Event noescape: Variable "config" is not freed or pointed-to in function "fread".
Also see events: [alloc_fn][var_assign][leaked_storage]
At conditional (2): "fread(config, 1UL, sbuf.st_size, configFile) != sbuf.st_size": Taking true branch.
73   	    if (fread(config, 1, sbuf.st_size, configFile) != sbuf.st_size) {
74   		fclose(configFile);
Event leaked_storage: Variable "config" going out of scope leaks the storage it points to.
Also see events: [alloc_fn][var_assign][noescape]
75   		return -oserror();
76   	    }
77   	    config[sbuf.st_size] = '\0';
78   	
79   	    p = config;
80   	    while (*p != '\0') {
81   		/* each time through here we're at the start of a new line */
82   		if (*p == '#')
83   		    goto eatline;
84   		if (strncmp(p, "pmcd", 4) == 0) {
85   		    /*
86   		     * the pmcd PMDA is an exception ... it makes reference to
87   		     * symbols in pmcd, and only makes sense when attached to the
88   		     * pmcd process, so we skip this one
89   		     */
90   		    goto eatline;
91   		}
92   		/* skip the PMDA's name */
93   		while (*p != '\0' && *p != '\n' && !isspace(*p))
94   		    p++;
95   		while (*p != '\0' && *p != '\n' && isspace(*p))
96   		    p++;
97   		/* extract domain number */
98   		domain = (int)strtol(p, &q, 10);
99   		p = q;
100  		while (*p != '\0' && *p != '\n' && isspace(*p))
101  		    p++;
102  		/* only interested if the type is "dso" */
103  		if (strncmp(p, "dso", 3) != 0)
104  		    goto eatline;
105  		p += 3;
106  		while (*p != '\0' && *p != '\n' && isspace(*p))
107  		    p++;
108  		/* up to the init routine name */
109  		init = p;
110  		while (*p != '\0' && *p != '\n' && !isspace(*p))
111  		    p++;
112  		*p = '\0';
113  		p++;
114  		while (*p != '\0' && *p != '\n' && isspace(*p))
115  		    p++;
116  		/* up to the dso pathname */
117  		name = p;
118  		while (*p != '\0' && *p != '\n' && !isspace(*p))
119  		    p++;
120  		peekc = *p;
121  		*p = '\0';
122  	#ifdef PCP_DEBUG
123  		if (pmDebug & DBG_TRACE_CONTEXT) {
124  		    fprintf(stderr, "[%d] domain=%d, name=%s, init=%s\n", lineno, domain, name, init);
125  		}
126  	#endif
127  		/*
128  		 * a little be recursive if we got here via __pmLocalPMDA(),
129  		 * but numdso has been set correctly, so this is OK
130  		 */
131  		__pmLocalPMDA(PM_LOCAL_ADD, domain, name, init);
132  		*p = peekc;
133  	
134  	eatline:
135  		while (*p != '\0' && *p != '\n')
136  		    p++;
137  		if (*p == '\n') {
138  		    lineno++;
139  		    p++;
140  		}
141  	    }
142  	
143  	    fclose(configFile);
144  	    free(config);
145  	    return 0;
146  	}
147  	
148  	#if defined(HAVE_DLFCN_H)
149  	#include <dlfcn.h>
150  	#endif
151  	
152  	/*
153  	 * As of PCP version 2.1, we're no longer searching for DSO's;
154  	 * pmcd's config file should have full paths to each of 'em.
155  	 */
156  	const char *
157  	__pmFindPMDA(const char *name)
158  	{
159  	    return (access(name, F_OK) == 0) ? name : NULL;
160  	}
161  	
162  	__pmDSO *
163  	__pmLookupDSO(int domain)
164  	{
165  	    int		i;
166  	    for (i = 0; i < numdso; i++) {
167  		if (dsotab[i].domain == domain && dsotab[i].handle != NULL)
168  		    return &dsotab[i];
169  	    }
170  	    return NULL;
171  	}
172  	
173  	#ifdef HAVE_ATEXIT
174  	static void
175  	EndLocalContext(void)
176  	{
177  	    int		i;
178  	    __pmDSO	*dp;
179  	    int		ctx = pmWhichContext();
180  	
181  	    for (i = 0; i < numdso; i++) {
182  		dp = &dsotab[i];
183  		if (dp->domain != -1 &&
184  		    dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5 &&
185  		    dp->dispatch.version.four.ext->e_endCallBack != NULL) {
186  	#ifdef PCP_DEBUG
187  		    if (pmDebug & DBG_TRACE_CONTEXT) {
188  			fprintf(stderr, "NotifyEndLocalContext: DSO PMDA %s (%d) notified of context %d close\n", 
189  			    dp->name, dp->domain, ctx);
190  		    }
191  	#endif
192  		    (*(dp->dispatch.version.four.ext->e_endCallBack))(ctx);
193  		}
194  	    }
195  	}
196  	#endif
197  	
198  	int
199  	__pmConnectLocal(void)
200  	{
201  	    int			i;
202  	    __pmDSO		*dp;
203  	    char		pathbuf[MAXPATHLEN];
204  	    const char		*path;
205  	#if defined(HAVE_DLOPEN)
206  	    unsigned int	challenge;
207  	    void		(*initp)(pmdaInterface *);
208  	#ifdef HAVE_ATEXIT
209  	    static int		atexit_installed = 0;
210  	#endif
211  	#endif
212  	
213  	    if (numdso == -1) {
214  		int	sts;
215  		sts = build_dsotab();
216  		if (sts < 0) return sts;
217  	    }
218  	
219  	    for (i = 0; i < numdso; i++) {
220  		dp = &dsotab[i];
221  		if (dp->domain == -1 || dp->handle != NULL)
222  		    continue;
223  		/*
224  		 * __pmLocalPMDA() means the path to the DSO may be something
225  		 * other than relative to $PCP_PMDAS_DIR ... need to try both
226  		 * options and also with and without DSO_SUFFIX (so, dll, etc)
227  		 */
228  		snprintf(pathbuf, sizeof(pathbuf), "%s%c%s",
229  			 pmGetConfig("PCP_PMDAS_DIR"), __pmPathSeparator(), dp->name);
230  		if ((path = __pmFindPMDA(pathbuf)) == NULL) {
231  		    snprintf(pathbuf, sizeof(pathbuf), "%s%c%s.%s",
232  			 pmGetConfig("PCP_PMDAS_DIR"), __pmPathSeparator(), dp->name, DSO_SUFFIX);
233  		    if ((path = __pmFindPMDA(pathbuf)) == NULL) {
234  			if ((path = __pmFindPMDA(dp->name)) == NULL) {
235  			    snprintf(pathbuf, sizeof(pathbuf), "%s.%s", dp->name, DSO_SUFFIX);
236  			    if ((path = __pmFindPMDA(pathbuf)) == NULL) {
237  				pmprintf("__pmConnectLocal: Warning: cannot find DSO at \"%s\" or \"%s\"\n", 
238  				     pathbuf, dp->name);
239  				pmflush();
240  				dp->domain = -1;
241  				dp->handle = NULL;
242  				continue;
243  			    }
244  			}
245  		    }
246  		}
247  	#if defined(HAVE_DLOPEN)
248  		dp->handle = dlopen(path, RTLD_NOW);
249  		if (dp->handle == NULL) {
250  		    pmprintf("__pmConnectLocal: Warning: error attaching DSO "
251  			     "\"%s\"\n%s\n\n", path, dlerror());
252  		    pmflush();
253  		    dp->domain = -1;
254  		}
255  	#else	/* ! HAVE_DLOPEN */
256  		dp->handle = NULL;
257  		pmprintf("__pmConnectLocal: Warning: error attaching DSO \"%s\"\n",
258  			 path);
259  		pmprintf("No dynamic DSO/DLL support on this platform\n\n");
260  		pmflush();
261  		dp->domain = -1;
262  	#endif
263  	
264  		if (dp->handle == NULL)
265  		    continue;
266  	
267  	#if defined(HAVE_DLOPEN)
268  		/*
269  		 * rest of this only makes sense if the dlopen() worked
270  		 */
271  		if (dp->init == NULL)
272  		    initp = NULL;
273  		else
274  		    initp = (void (*)(pmdaInterface *))dlsym(dp->handle, dp->init);
275  		if (initp == NULL) {
276  		    pmprintf("__pmConnectLocal: Warning: couldn't find init function "
277  			     "\"%s\" in DSO \"%s\"\n", dp->init, path);
278  		    pmflush();
279  		    dlclose(dp->handle);
280  		    dp->domain = -1;
281  		    continue;
282  		}
283  	
284  		/*
285  		 * Pass in the expected domain id.
286  		 * The PMDA initialization routine can (a) ignore it, (b) check it
287  		 * is the expected value, or (c) self-adapt.
288  		 */
289  		dp->dispatch.domain = dp->domain;
290  	
291  		/*
292  		 * the PMDA interface / PMAPI version discovery as a "challenge" ...
293  		 * for pmda_interface it is all the bits being set,
294  		 * for pmapi_version it is the complement of the one you are using now
295  		 */
296  		challenge = 0xff;
297  		dp->dispatch.comm.pmda_interface = challenge;
298  		dp->dispatch.comm.pmapi_version = ~PMAPI_VERSION;
299  	
300  		dp->dispatch.comm.flags = 0;
301  		dp->dispatch.status = 0;
302  	
303  		(*initp)(&dp->dispatch);
304  	
305  		if (dp->dispatch.status != 0) {
306  		    /* initialization failed for some reason */
307  		    pmprintf("__pmConnectLocal: Warning: initialization "
308  			     "routine \"%s\" failed in DSO \"%s\": %s\n", 
309  			     dp->init, path, pmErrStr(dp->dispatch.status));
310  		    pmflush();
311  		    dlclose(dp->handle);
312  		    dp->domain = -1;
313  		}
314  		else {
315  		    if (dp->dispatch.comm.pmda_interface == challenge) {
316  			/*
317  			 * DSO did not change pmda_interface, assume PMAPI version 1
318  			 * from PCP 1.x and PMDA_INTERFACE_1
319  			 */
320  			dp->dispatch.comm.pmda_interface = PMDA_INTERFACE_1;
321  			dp->dispatch.comm.pmapi_version = PMAPI_VERSION_1;
322  		    }
323  		    else {
324  			/*
325  			 * gets a bit tricky ...
326  			 * interface_version (8-bits) used to be version (4-bits),
327  			 * so it is possible that only the bottom 4 bits were
328  			 * changed and in this case the PMAPI version is 1 for
329  			 * PCP 1.x
330  			 */
331  			if ((dp->dispatch.comm.pmda_interface & 0xf0) == (challenge & 0xf0)) {
332  			    dp->dispatch.comm.pmda_interface &= 0x0f;
333  			    dp->dispatch.comm.pmapi_version = PMAPI_VERSION_1;
334  			}
335  		    }
336  	
337  		    if (dp->dispatch.comm.pmda_interface < PMDA_INTERFACE_1 ||
338  			dp->dispatch.comm.pmda_interface > PMDA_INTERFACE_LATEST) {
339  			pmprintf("__pmConnectLocal: Error: Unknown PMDA interface "
340  				 "version %d in \"%s\" DSO\n", 
341  				 dp->dispatch.comm.pmda_interface, path);
342  			pmflush();
343  			dlclose(dp->handle);
344  			dp->domain = -1;
345  		    }
346  	
347  		    if (dp->dispatch.comm.pmapi_version != PMAPI_VERSION_1 &&
348  			dp->dispatch.comm.pmapi_version != PMAPI_VERSION_2) {
349  			pmprintf("__pmConnectLocal: Error: Unknown PMAPI version %d "
350  				 "in \"%s\" DSO\n",
351  				 dp->dispatch.comm.pmapi_version, path);
352  			pmflush();
353  			dlclose(dp->handle);
354  			dp->domain = -1;
355  		    }
356  		}
357  	#ifdef HAVE_ATEXIT
358  		if (dp->dispatch.comm.pmda_interface >= PMDA_INTERFACE_5 &&
359  		    atexit_installed == 0) {
360  		    /* install end of local context handler */
361  		    atexit(EndLocalContext);
362  		    atexit_installed = 1;
363  		}
364  	#endif
365  	#endif	/* HAVE_DLOPEN */
366  	    }
367  	
368  	    return 0;
369  	}
370  	
371  	int
372  	__pmLocalPMDA(int op, int domain, const char *name, const char *init)
373  	{
374  	    int		sts = 0;
375  	    int		i;
376  	
377  	#ifdef PCP_DEBUG
378  	    if (pmDebug & DBG_TRACE_CONTEXT) {
379  		fprintf(stderr, "__pmLocalPMDA(op=");
380  		if (op == PM_LOCAL_ADD) fprintf(stderr, "ADD");
381  		else if (op == PM_LOCAL_DEL) fprintf(stderr, "DEL");
382  		else if (op == PM_LOCAL_CLEAR) fprintf(stderr, "CLEAR");
383  		else fprintf(stderr, "%d ???", op);
384  		fprintf(stderr, ", domain=%d, name=%s, init=%s)\n", domain, name, init);
385  	    }
386  	#endif
387  	
388  	    if (numdso == -1) {
389  		sts = build_dsotab();
390  		if (sts < 0) return sts;
391  	    }
392  	
393  	    switch (op) {
394  		case PM_LOCAL_ADD:
395  		    if ((dsotab = (__pmDSO *)realloc(dsotab, (numdso+1)*sizeof(__pmDSO))) == NULL) {
396  			__pmNoMem("__pmLocalPMDA realloc", (numdso+1)*sizeof(__pmDSO), PM_FATAL_ERR);
397  			/*NOTREACHED*/
398  		    }
399  		    dsotab[numdso].domain = domain;
400  		    if (name == NULL) {
401  			/* odd, will fail later at dlopen */
402  			dsotab[numdso].name = NULL;
403  		    }
404  		    else {
405  			if ((dsotab[numdso].name = strdup(name)) == NULL) {
406  			    sts = -oserror();
407  			    __pmNoMem("__pmLocalPMDA name", strlen(name)+1, PM_RECOV_ERR);
408  			    return sts;
409  			}
410  		    }
411  		    if (init == NULL) {
412  			/* odd, will fail later at initialization call */
413  			dsotab[numdso].init = NULL;
414  		    }
415  		    else {
416  			if ((dsotab[numdso].init = strdup(init)) == NULL) {
417  			    sts = -oserror();
418  			    __pmNoMem("__pmLocalPMDA init", strlen(init)+1, PM_RECOV_ERR);
419  			    return sts;
420  			}
421  		    }
422  		    dsotab[numdso].handle = NULL;
423  		    numdso++;
424  		    break;
425  	
426  		case PM_LOCAL_DEL:
427  		    sts = PM_ERR_INDOM;
428  		    for (i = 0; i < numdso; i++) {
429  			if ((domain != -1 && dsotab[i].domain == domain) ||
430  			    (name != NULL && strcmp(dsotab[i].name, name) == 0)) {
431  			    if (dsotab[i].handle) {
432  				dlclose(dsotab[i].handle);
433  				dsotab[i].handle = NULL;
434  			    }
435  			    dsotab[i].domain = -1;
436  			    sts = 0;
437  			}
438  		    }
439  		    break;
440  	
441  		case PM_LOCAL_CLEAR:
442  		    for (i = 0; i < numdso; i++) {
443  			free(dsotab[i].name);
444  		    	free(dsotab[i].init);
445  			if (dsotab[i].handle)
446  			    dlclose(dsotab[i].handle);
447  		    }
448  		    free(dsotab);
449  		    dsotab = NULL;
450  		    numdso = 0;
451  		    break;
452  	
453  		default:
454  		    sts = PM_ERR_CONV;
455  		    break;
456  	    }
457  	
458  	#ifdef PCP_DEBUG
459  	    if (pmDebug & DBG_TRACE_CONTEXT) {
460  		if (sts != 0)
461  		    fprintf(stderr, "__pmLocalPMDA -> %s\n", pmErrStr(sts));
462  		fprintf(stderr, "Local Context PMDA Table");
463  		if (numdso == 0)
464  		    fprintf(stderr, " ... empty");
465  		fputc('\n', stderr);
466  		for (i = 0; i < numdso; i++) {
467  		    fprintf(stderr, PRINTF_P_PFX "%p [%d] domain=%d name=%s init=%s handle=" PRINTF_P_PFX "%p\n",
468  			&dsotab[i], i, dsotab[i].domain, dsotab[i].name, dsotab[i].init, dsotab[i].handle);
469  		}
470  	    }
471  	#endif
472  	
473  	    return sts;
474  	}
475  	
476  	/*
477  	 * Parse a command line string that encodes arguments to __pmLocalPMDA(),
478  	 * then call __pmLocalPMDA().
479  	 *
480  	 * The syntax for the string is 1 to 4 fields separated by colons:
481  	 * 	- op ("add" for add, "del" for delete, "clear" for clear)
482  	 *	- domain (PMDA's PMD)
483  	 *	- path (path to DSO PMDA)
484  	 *	- init (name of DSO's initialization routine)
485  	 */
486  	char *
487  	__pmSpecLocalPMDA(const char *spec)
488  	{
489  	    int		op;
490  	    int		domain = -1;
491  	    char	*name = NULL;
492  	    char	*init = NULL;
493  	    int		sts;
494  	    char	*arg;
495  	    char	*sbuf;
496  	    char	*ap;
497  	
498  	    if ((arg = sbuf = strdup(spec)) == NULL) {
499  		sts = -oserror();
500  		__pmNoMem("__pmSpecLocalPMDA dup spec", strlen(spec)+1, PM_RECOV_ERR);
501  		return "strdup failed";
502  	    }
503  	    if (strncmp(arg, "add", 3) == 0) {
504  		op = PM_LOCAL_ADD;
505  		ap = &arg[3];
506  	    }
507  	    else if (strncmp(arg, "del", 3) == 0) {
508  		op = PM_LOCAL_DEL;
509  		ap = &arg[3];
510  	    }
511  	    else if (strncmp(arg, "clear", 5) == 0) {
512  		op = PM_LOCAL_CLEAR;
513  		ap = &arg[5];
514  		if (*ap == '\0')
515  		    goto doit;
516  		else {
517  		    free(sbuf);
518  		    return "unexpected text after clear op in spec";
519  		}
520  	    }
521  	    else {
522  		free(sbuf);
523  		return "bad op in spec";
524  	    }
525  	
526  	    if (*ap != ',') {
527  		free(sbuf);
528  		return "expected , after op in spec";
529  	    }
530  	    /* ap-> , after add or del */
531  	    arg = ++ap;
532  	    if (*ap == '\0') {
533  		free(sbuf);
534  		return "missing domain in spec";
535  	    }
536  	    else if (*ap != ',') {
537  		/* ap-> domain */
538  		domain = (int)strtol(arg, &ap, 10);
539  		if ((*ap != ',' && *ap != '\0') || domain < 0 || domain > 510) {
540  		    free(sbuf);
541  		    return "bad domain in spec";
542  		}
543  	    }
544  	    else {
545  		if (op != PM_LOCAL_DEL) {
546  		    /* found ,, where ,domain, expected */
547  		    free(sbuf);
548  		    return "missing domain in spec";
549  		}
550  	    }
551  	    ap++;
552  	    /* ap -> char after , following domain */
553  	    if (*ap == ',') {
554  		/* no path, could have init (not useful but possible!) */
555  		ap++;
556  		if (*ap != '\0')
557  		    init = ap;
558  	    }
559  	    else if (*ap != '\0') {
560  		/* have path and possibly init */
561  		name = ap;
562  		while (*ap != ',' && *ap != '\0')
563  		    ap++;
564  		if (*ap == ',') {
565  		    *ap++ = '\0';
566  		    if (*ap != '\0')
567  			init = ap;
568  		    else {
569  			if (op != PM_LOCAL_DEL) {
570  			    /* found end of string where init-routine expected */
571  			    free(sbuf);
572  			    return "missing init-routine in spec";
573  			}
574  		    }
575  		}
576  		else {
577  		    if (op != PM_LOCAL_DEL) {
578  			/* found end of string where init-routine expected */
579  			free(sbuf);
580  			return "missing init-routine in spec";
581  		    }
582  		}
583  	    }
584  	    else {
585  		if (op != PM_LOCAL_DEL) {
586  		    /* found end of string where path expected */
587  		    free(sbuf);
588  		    return "missing dso-path in spec";
589  		}
590  	    }
591  	
592  	    if (domain == -1 && name == NULL) {
593  		free(sbuf);
594  		return "missing domain and dso-path in spec";
595  	    }
596  	
597  	doit:
598  	    sts = __pmLocalPMDA(op, domain, name, init);
599  	    if (sts < 0) {
600  		static char buffer[256];
601  		snprintf(buffer, sizeof(buffer), "__pmLocalPMDA: %s", pmErrStr(sts));
602  		free(sbuf);
603  		return buffer;
604  	    }
605  	
606  	    free(sbuf);
607  	    return NULL;
608  	}