1    	/*
2    	 * Copyright (c) 1995-2002 Silicon Graphics, Inc.  All Rights Reserved.
3    	 * Copyright (c) 2007 Aconex.  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   	/*
17   	 * Parse uniform metric and host specification syntax
18   	 */
19   	
20   	#include <ctype.h>
21   	#include "pmapi.h"
22   	#include "impl.h"
23   	#ifdef HAVE_STRINGS_H
24   	#include <strings.h>
25   	#endif
26   	
27   	static void *
28   	parseAlloc(const char *func, size_t need)
29   	{
30   	    void    *tmp;
31   	
32   	    if ((tmp = malloc(need)) == NULL)
33   		__pmNoMem(func, need, PM_FATAL_ERR);
34   	    return tmp;
35   	}
36   	
37   	static void
38   	parseError(const char *func, const char *spec, const char *point, char *msg, char **rslt)
39   	{
40   	    int		need;
41   	    const char	*p;
42   	    char	*q;
43   	
44   	    if (rslt == NULL)
45   		return;
46   	
47   	    need = 2 * (int)strlen(spec) + 1 + 6 + (int)strlen(msg) + 2;
48   	    *rslt = q = (char *)parseAlloc(func, need);
49   	    for (p = spec; *p != '\0'; p++)
50   		*q++ = *p;
51   	    *q++ = '\n';
52   	    for (p = spec; p != point; p++)
53   		*q++ = isgraph((int)*p) ? ' ' : *p;
54   	    sprintf(q, "^ -- %s\n", msg);	/* safe */
55   	}
56   	
57   	static void *
58   	metricAlloc(size_t need)
59   	{
60   	    return parseAlloc("pmParseMetricSpec", need);
61   	}
62   	
63   	static void
64   	metricError(const char *spec, const char *point, char *msg, char **rslt)
65   	{
66   	    parseError("pmParseMetricSpec", spec, point, msg, rslt);
67   	}
68   	
69   	int		/* 0 -> ok, PM_ERR_GENERIC -> error */
70   	pmParseMetricSpec(
71   	    const char *spec,		/* parse this string */
72   	    int isarch,			/* default source: 0 -> host, 1 -> archive */
73   	    char *source,		/* name of default host or archive */
74   	    pmMetricSpec **rslt,	/* result allocated and returned here */
75   	    char **errmsg)		/* error message */
76   	{
77   	    pmMetricSpec   *msp = NULL;
78   	    const char	    *scan;
79   	    const char	    *mark;
80   	    const char	    *h_start = NULL;	/* host name */
81   	    const char	    *h_end = NULL;
82   	    const char	    *a_start = NULL;	/* archive name */
83   	    const char	    *a_end = NULL;
84   	    const char	    *m_start = NULL;	/* metric name */
85   	    const char	    *m_end = NULL;
86   	    const char	    *i_start = NULL;	/* instance names */
87   	    const char	    *i_end = NULL;
88   	    char	    *i_str = NULL;	/* temporary instance names */
89   	    char	    *i_scan;
90   	    int		    ninst;		/* number of instance names */
91   	    char	    *push;
92   	    const char	    *pull;
93   	    int		    length;
94   	    int		    i;
95   	    int		    inquote = 0;	/* true if within quotes */
96   	
97   	    scan = spec;
98   	    while (isspace((int)*scan))
99   		scan++;
100  	
101  	    /*
102  	     * Options here are ...
103  	     * [host:]metric[[instance list]]
104  	     *     special case for PM_CONTEXT_LOCAL [@:]metric[[instance list]]
105  	     * [archive/]metric[[instance list]]
106  	     *
107  	     * Find end of metric name first ([ or end of string) then scan
108  	     * backwards for first ':' or '/'
109  	     */
110  	    mark = index(scan, (int)'[');
111  	    if (mark == NULL) mark = &scan[strlen(scan)-1];
112  	    while (mark >= scan) {
113  		if (*mark == ':') {
114  		    h_start = scan;
115  		    h_end = mark-1;
116  		    while (h_end >= scan && isspace((int)*h_end)) h_end--;
117  		    if (h_end < h_start) {
118  			metricError(spec, h_start, "host name expected", errmsg);
119  			return PM_ERR_GENERIC;
120  		    }
121  		    h_end++;
122  		    scan = mark+1;
123  		    break;
124  		}
125  		else if (*mark == '/') {
126  		    a_start = scan;
127  		    a_end = mark-1;
128  		    while (a_end >= scan && isspace((int)*a_end)) a_end--;
129  		    if (a_end < a_start) {
130  			metricError(spec, a_start, "archive name expected", errmsg);
131  			return PM_ERR_GENERIC;
132  		    }
133  		    a_end++;
134  		    scan = mark+1;
135  		    break;
136  		}
137  		mark--;
138  	    }
139  	
140  	    while (isspace((int)*scan))
141  		scan++;
142  	    mark = scan;
143  	
144  	    /* delimit metric name */
145  	    m_start = scan;
146  	    while (! isspace((int)*scan) && *scan != '\0' && *scan != '[') {
147  		if (*scan == ']' || *scan == ',') {
148  		    metricError(spec, scan, "unexpected character in metric name", errmsg);
149  		    return PM_ERR_GENERIC;
150  		}
151  		if (*scan == '\\' && *(scan+1) != '\0')
152  		    scan++;
153  		scan++;
154  	    }
155  	    m_end = scan;
156  	    if (m_start == m_end) {
157  		metricError(spec, m_start, "performance metric name expected", errmsg);
158  		return PM_ERR_GENERIC;
159  	    }
160  	
161  	    while (isspace((int)*scan))
162  		scan++;
163  	
164  	    /* delimit instance names */
165  	    if (*scan == '[') {
166  		scan++;
167  		while (isspace((int)*scan))
168  		    scan++;
169  		i_start = scan;
170  		for ( ; ; ) {
171  		    if (*scan == '\0') {
172  			if (inquote)
173  			    metricError(spec, scan, "closing \" and ] expected", errmsg);
174  			else
175  			    metricError(spec, scan, "closing ] expected", errmsg);
176  			return PM_ERR_GENERIC;
177  		    }
178  		    if (*scan == '\\' && *(scan+1) != '\0')
179  			scan++;
180  		    else if (*scan == '"')
181  		         inquote = 1 - inquote;
182  		    else if (!inquote && *scan == ']')
183  			break;
184  		    scan++;
185  		}
186  		i_end = scan;
187  		scan++;
188  	    }
189  	
190  	    /* check for rubbish at end of string */
191  	    while (isspace((int)*scan))
192  		scan++;
193  	    if (*scan != '\0') {
194  		metricError(spec, scan, "unexpected extra characters", errmsg);
195  		return PM_ERR_GENERIC;
196  	    }
197  	
198  	    /* count instance names and make temporary copy */
199  	    ninst = 0;
200  	    if (i_start != NULL) {
201  		i_str = (char *) metricAlloc(i_end - i_start + 1);
202  	
203  		/* count and copy instance names */
204  		scan = i_start;
205  		i_scan = i_str;
206  		while (scan < i_end) {
207  	
208  		    /* copy single instance name */
209  		    ninst++;
210  		    if (*scan == '"') {
211  			scan++;
212  			for (;;) {
213  			    if (scan >= i_end) {
214  				metricError(spec, scan, "closing \" expected (pmParseMetricSpec botch?)", errmsg);
215  				if (msp)
216  				    pmFreeMetricSpec(msp);
217  				if (i_str)
218  				    free(i_str);
219  				return PM_ERR_GENERIC;
220  			    }
221  			    if (*scan == '\\')
222  				scan++;
223  			    else if (*scan == '"')
224  				break;
225  			    *i_scan++ = *scan++;
226  			}
227  			scan++;
228  		    }
229  		    else {
230  			while (! isspace((int)*scan) && *scan != ',' && scan < i_end) {
231  			    if (*scan == '\\')
232  				scan++;
233  			    *i_scan++ = *scan++;
234  			}
235  		    }
236  		    *i_scan++ = '\0';
237  	
238  		    /* skip delimiters */
239  		    while ((isspace((int)*scan) || *scan == ',') && scan < i_end)
240  			scan++;
241  		}
242  		i_start = i_str;
243  		i_end = i_scan;
244  	    }
245  	
246  	    /* single memory allocation for result structure */
247  	    length = (int)(sizeof(pmMetricSpec) +
248  	             ((ninst > 1) ? (ninst - 1) * sizeof(char *) : 0) +
249  		     ((h_start) ? h_end - h_start + 1 : 0) +
250  		     ((a_start) ? a_end - a_start + 1 : 0) +
251  		     ((m_start) ? m_end - m_start + 1 : 0) +
252  		     ((i_start) ? i_end - i_start + 1 : 0));
253  	    msp = (pmMetricSpec *)metricAlloc(length);
254  	
255  	    /* strings follow pmMetricSpec proper */
256  	    push = ((char *) msp) +
257  		   sizeof(pmMetricSpec) + 
258  		   ((ninst > 1) ? (ninst - 1) * sizeof(char *) : 0);
259  	
260  	    /* copy metric name */
261  	    msp->metric = push;
262  	    pull = m_start;
263  	    while (pull < m_end) {
264  		if (*pull == '\\' && (pull+1) < m_end)
265  		    pull++;
266  		*push++ = *pull++;
267  	    }
268  	    *push++ = '\0';
269  	
270  	    /* copy host name */
271  	    if (h_start != NULL) {
272  		if (h_end - h_start == 1 && *h_start == '@') {
273  		    /* PM_CONTEXT_LOCAL special case */
274  		    msp->isarch = 2;
275  		}
276  		else {
277  		    /* PM_CONTEXT_HOST */
278  		    msp->isarch = 0;
279  		}
280  		msp->source = push;
281  		pull = h_start;
282  		while (pull < h_end) {
283  		    if (*pull == '\\' && (pull+1) < h_end)
284  			pull++;
285  		    *push++ = *pull++;
286  		}
287  		*push++ = '\0';
288  	    }
289  	
290  	    /* copy archive name */
291  	    else if (a_start != NULL) {
292  		msp->isarch = 1;
293  		msp->source = push;
294  		pull = a_start;
295  		while (pull < a_end) {
296  		    if (*pull == '\\' && (pull+1) < a_end)
297  			pull++;
298  		    *push++ = *pull++;
299  		}
300  		*push++ = '\0';
301  	    }
302  	
303  	    /* take default host or archive */
304  	    else {
305  		msp->isarch = isarch;
306  		msp->source = source;
307  	    }
308  	
309  	    /* instance names */
310  	    msp->ninst = ninst;
311  	    pull = i_start;
312  	    for (i = 0; i < ninst; i++) {
313  		msp->inst[i] = push;
314  		do
315  		    *push++ = *pull;
316  		while (*pull++ != '\0');
317  	    }
318  	
319  	    if (i_str)
320  		free(i_str);
321  	    *rslt = msp;
322  	    return 0;
323  	}
324  	
325  	void
326  	pmFreeMetricSpec(pmMetricSpec *spec)
327  	{
328  	    free(spec);
329  	}
330  	
331  	
332  	static void
333  	hostError(const char *spec, const char *point, char *msg, char **rslt)
334  	{
335  	    parseError("pmParseHostSpec", spec, point, msg, rslt);
336  	}
337  	
338  	static char *
339  	hostStrndup(const char *name, int namelen)
340  	{
Event alloc_fn: Storage is returned from allocation function "malloc".
Event var_assign: Assigning: "s" = "malloc(namelen + 1)".
Also see events: [noescape][return_alloc]
341  	    char *s = malloc(namelen + 1);
Event noescape: Variable "s" is not freed or pointed-to in function "strncpy".
Also see events: [alloc_fn][var_assign][return_alloc]
342  	    strncpy(s, name, namelen);
343  	    s[namelen] = '\0';
Event return_alloc: Returning allocated memory "s".
Also see events: [alloc_fn][var_assign][noescape]
344  	    return s;
345  	}
346  	
347  	static pmHostSpec *
348  	hostAdd(pmHostSpec *specp, int *count, const char *name, int namelen)
349  	{
350  	    int n = *count;
351  	    char *host;
352  	
Event alloc_fn: Calling allocation function "hostStrndup". [details]
Event var_assign: Assigning: "host" = storage returned from "hostStrndup(name, namelen)".
Also see events: [leaked_storage]
353  	    host = hostStrndup(name, namelen);
At conditional (1): "!host": Taking false branch.
At conditional (2): "(specp = realloc(specp, sizeof (pmHostSpec) /*24*/ * (n + 1))) == NULL": Taking true branch.
354  	    if (!host || (specp = realloc(specp, sizeof(pmHostSpec) * (n+1))) == NULL) {
355  		*count = 0;
Event leaked_storage: Variable "host" going out of scope leaks the storage it points to.
Also see events: [alloc_fn][var_assign]
356  		return NULL;
357  	    }
358  	    specp[n].name = host;
359  	    specp[n].ports = NULL;
360  	    specp[n].nports = 0;
361  	
362  	    *count = n + 1;
363  	    return specp;
364  	}
365  	
366  	int
367  	__pmAddHostPorts(pmHostSpec *specp, int *ports, int nports)
368  	{
369  	    int *portlist;
370  	
371  	    if ((portlist = malloc(sizeof(int) * (specp->nports + nports))) == NULL)
372  		return -ENOMEM;
373  	    if (specp->nports > 0) {
374  		memcpy(portlist, specp->ports, sizeof(int) * specp->nports);
375  		free(specp->ports);
376  	    }
377  	    memcpy(&portlist[specp->nports], ports, sizeof(int) * nports);
378  	    specp->ports = portlist;
379  	    specp->nports = specp->nports + nports;
380  	    return 0;
381  	}
382  	
383  	void
384  	__pmDropHostPort(pmHostSpec *specp)
385  	{
386  	    specp->nports--;
387  	    memmove(&specp->ports[0], &specp->ports[1], specp->nports*sizeof(int));
388  	}
389  	
390  	/* 
391  	 * Parse a host specification, with optional ports and proxy host(s).
392  	 * Examples:
393  	 *	pcp -h app1.aconex.com:44321,4321@firewall.aconex.com:44322
394  	 *	pcp -h app1.aconex.com:44321@firewall.aconex.com:44322
395  	 *	pcp -h app1.aconex.com:44321@firewall.aconex.com
396  	 *	pcp -h app1.aconex.com@firewall.aconex.com
397  	 *	pcp -h app1.aconex.com:44321
398  	 *
399  	 * Basic algorithm:
400  	 *	look for first colon, @ or null; preceding text is hostname
401  	 *	 if colon, look for comma, @ or null, preceding text is port
402  	 *	  while comma, look for comma, @ or null, preceding text is next port
403  	 *	if @, start following host specification at the following character,
404  	 *	 by returning to the start and repeating the above for the next chunk.
405  	 * Note:
406  	 *	Currently only two hosts are useful, but ability to handle more than
407  	 *	one optional proxy host is there (i.e. proxy ->proxy ->... ->pmcd),
408  	 *	in case someone implements the pmproxy->pmproxy protocol extension.
409  	 */
410  	
411  	int             /* 0 -> ok, PM_ERR_GENERIC -> error */
412  	__pmParseHostSpec(
413  	    const char *spec,           /* parse this string */
414  	    pmHostSpec **rslt,          /* result allocated and returned here */
415  	    int *count,
416  	    char **errmsg)              /* error message */
417  	{
418  	    pmHostSpec *hsp = NULL;
419  	    const char *s, *start;
420  	    int nhosts = 0, sts = 0;
421  	
422  	    for (s = start = spec; s != NULL; s++) {
423  		if (*s == ':' || *s == '@' || *s == '\0') {
424  		    if (s == start)
425  			continue;
426  		    hsp = hostAdd(hsp, &nhosts, start, s - start);
427  		    if (hsp == NULL) {
428  			sts = -ENOMEM;
429  			goto fail;
430  		    }
431  		    if (*s == ':') {
432  			for (++s, start = s; s != NULL; s++) {
433  			    if (*s == ',' || *s == '@' || *s == '\0') {
434  				if (s - start < 1) {
435  				    hostError(spec, s, "missing port", errmsg);
436  				    sts = PM_ERR_GENERIC;
437  				    goto fail;
438  				}
439  				int port = atoi(start);
440  				sts = __pmAddHostPorts(&hsp[nhosts-1], &port, 1);
441  				if (sts < 0)
442  				    goto fail;
443  				start = s + 1;
444  				if (*s == '@' || *s == '\0')
445  				    break;
446  				continue;
447  			    }
448  			    if (isdigit(*s))
449  				continue;
450  			    hostError(spec, s, "non-numeric port", errmsg);
451  			    sts = PM_ERR_GENERIC;
452  			    goto fail;
453  			}
454  		    }
455  		    if (*s == '@') {
456  			start = s+1;
457  			continue;
458  		    }
459  		    break;
460  		}
461  	    }
462  	    *count = nhosts;
463  	    *rslt = hsp;
464  	    return 0;
465  	
466  	fail:
467  	    __pmFreeHostSpec(hsp, nhosts);
468  	    *rslt = NULL;
469  	    *count = 0;
470  	    return sts;
471  	}
472  	
473  	void
474  	__pmUnparseHostSpec(pmHostSpec *hostp, int count, char **specp, int specsz)
475  	{
476  	    int i, j, sz = 0;
477  	
478  	    for (i = 0; i < count; i++) {
479  		if (i > 0)
480  		    sz += snprintf(*specp, specsz, "@");
481  		sz += snprintf(*specp, specsz, "%s", hostp[0].name);
482  		for (j = 0; j < hostp[i].nports; j++)
483  		    sz += snprintf((*specp) + sz, specsz - sz,
484  				    "%c%u", (j == 0) ? ':' : ',', hostp[i].ports[j]);
485  	    }
486  	}
487  	
488  	void
489  	__pmFreeHostSpec(pmHostSpec *specp, int count)
490  	{
491  	    int i;
492  	
493  	    for (i = 0; i < count; i++) {
494  		free(specp[i].name);
495  		specp[i].name = NULL;
496  		if (specp[i].nports > 0)
497  		    free(specp[i].ports);
498  		specp[i].ports = NULL;
499  		specp[i].nports = 0;
500  	    }
501  	    if (specp && count)
502  		free(specp);
503  	}