1/*********************************************************************
2 *   Copyright 2014, UCAR/Unidata
3 *   See netcdf/COPYRIGHT file for copying and redistribution conditions.
4 *********************************************************************/
5
6#include "config.h"
7#include <stdlib.h>
8#include <stdio.h>
9#include <string.h>
10
11/* General rule
12try to avoid any obscure string functions.
13We currently use
14- strcasecmp
15- strchr
16- strndup
17- strlen
18*/
19
20#undef DEBUG
21
22/* Define the legal leading key characters */
23#define KEYCHARS1 "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789$_."
24
25/*forward*/
26static const char* ncsettings_text;
27
28static char** lines;
29static int nlines;
30static char* dup;
31static char** map = NULL;
32
33/*forward*/
34static void parse();
35static int parseline(const char* line, int keypos);
36static int iskeyline(const char* line);
37static void preprocess();
38
39const char*
40nc_settings(const char* key)
41{
42    char** mapp;
43    if(map == NULL)
44	parse();
45    for(mapp=map;*mapp != NULL;mapp+=2) {
46	/* Note this assumes that no key is a prefix of another */
47	if(strcasecmp(*mapp,key)==0) {
48	    return mapp[1];
49	}
50    }
51    return NULL;
52}
53
54const char**
55nc_settings_all()
56{
57    if(map == NULL)
58	parse();
59    return (const char**)map;
60}
61
62const char*
63nc_settings_text()
64{
65    return ncsettings_text;
66}
67
68static void
69parse()
70{
71    int i,keypos;
72    int nkeys;
73    const char** line;
74
75    preprocess();
76
77    nkeys = 0;
78    /* Count # of key lines */
79    for(i=0;i<nlines;i++) {
80	const char* line = lines[i];
81#ifdef DEBUG
82	printf("testing: %s\n",line);
83#endif
84	if(iskeyline(line)) {
85#ifdef DEBUG
86	    printf("keyline: %s\n",line);
87#endif
88	    nkeys++;
89	}
90    }
91#ifdef DEBUG
92    fflush(stdout);
93#endif
94    /* Create the map of proper size */
95    map = (char**)malloc(((2*nkeys)+1) * sizeof(char*));/*+1 for terminating null*/
96    if(map == NULL) {
97	fprintf(stderr,"ncsettings: out of memory\n");
98	return;
99    }
100    map[2*nkeys] = NULL; /* pre-insert terminating null */
101    /* parse the keylines only */
102    keypos = 0;
103    for(i=0;i<nlines;i++) {
104	const char* line = lines[i];
105	if(!iskeyline(line)) continue;
106	if(!parseline(line,keypos))
107	    return;
108	keypos+=2;
109    }
110}
111
112/*
113We assume that each key starts with an alphanumeric character.
114*/
115static int
116parseline(const char* line, int keypos)
117{
118    const char* p;
119    const char* r;
120    char* q;
121    ptrdiff_t delta;
122    char* key;
123    char* value;
124    /* find colon ending of the key */
125    r = strchr(line,':');
126    if(r == NULL) { /* malformed */
127	fprintf(stderr,"malformed libnetcdf.settings file: %s\n,line");
128	return 0;
129    }
130    /* back up from the colon to the first non-blank */
131    for(p=r;p != line;p--) {
132	if(*p != ' ') break;
133    }
134    if(p == line) {/* empty key */
135	fprintf(stderr,"malformed libnetcdf.settings file: %s\n,line");
136	return 0;
137    }
138    delta = p - line;
139    key = strndup(line,delta);
140    /* skip post ':' blanks */
141    for(p=r+1;;p++) {
142	if(*p != ' ') break;
143    }
144    if(*p == '\0') /* empty value */
145	value = strdup("");
146    else { /* assert value is not empty */
147	value = strdup(p);
148	size_t len = strlen(value);
149	q = value + (len - 1); /* point to last char before trailing nul */
150        /* back up to the first non-blank */
151	for(;(*q == ' ');q--); /* will always terminate at value at least */
152	q[1] = '\0'; /* artificial end */
153    }
154    /* Append to the map */
155    map[keypos] = key;
156    map[keypos+1] = value;
157    return 1;
158}
159
160/* We assume that each key starts with an alphanumeric character. */
161static int
162iskeyline(const char* line)
163{
164   if(line == NULL || strlen(line) == 0)
165	return 0;
166   if(line[0] == '#' || line[0] == ' ')
167	return 0;
168   if(strchr(KEYCHARS1,line[0]) ==  NULL)
169	return 0;
170   return 1;
171}
172
173/* We need to process the text as follows:
1741. convert tabs and \r to blanks
1752. convert \n to EOL (\0)
1763. remove leading and trailing blanks from each line
177*/
178static void
179preprocess()
180{
181    int c,i;
182    const char* p;
183    char* q;
184    char* r;
185
186#ifdef DEBUG
187    printf("input: %s\n",ncsettings_text);
188    fflush(stdout);
189#endif
190    dup = (char*)malloc(strlen(ncsettings_text)+1);
191    nlines = 0;
192    /* steps 1 and 2 */
193    for(p=ncsettings_text,q=dup;(c=*p);p++) {
194	switch (c) {
195	case '\r': case '\t': *q++ = ' '; break;
196	case '\n': nlines++; *q++ = '\0'; break;
197	default: *q++ = c; break;
198	}
199    }
200    /* step 3 */
201    lines = (char**)malloc(nlines*sizeof(char*));
202    r = dup;
203    for(i=0;i<nlines;i++) {
204	int suppress;
205	lines[i] = r;
206	r += strlen(r);
207	r++; /* skip terminating nul */
208    }
209    for(i=0;i<nlines;i++) {
210	char* line = lines[i];
211	p = line;
212	for(;(c=*p);p++) {
213	    if(c != ' ')
214		break;
215	}
216	strcpy(line,p); /* remove leading blanks */
217	q = line+strlen(line); /* terminating nul */
218	while((c=*(--q))) {
219	    if(c != ' ')
220		break;
221	}
222	/* terminate */
223	q++;
224	*q = '\0';
225#ifdef DEBUG
226        printf("processed: %s\n",line);
227#endif
228    }
229#ifdef DEBUG
230    fflush(stdout);
231#endif
232}
233
234void
235nc_settings_reclaim()
236{
237    if(lines != NULL) free(lines);
238    if(dup != NULL) free(dup);
239    if(map != NULL) free(map);
240    lines = NULL;
241    dup = NULL;
242    map = NULL;
243}
244
245static const char* ncsettings_text =
246