1 #include "config.h"
2 
3 #include <errno.h>
4 #include <stdlib.h>
5 #include <stdio.h>
6 #include <strings.h>
7 #include <string.h>
8 #include <ctype.h>
9 #include <stdbool.h>
10 
11 #include <memcached/config_parser.h>
12 #include <memcached/util.h>
13 #include <platform/cb_malloc.h>
14 
15 static int read_config_file(const char *fname, struct config_item items[],
16                             FILE *error);
17 
18 /**
19  * Copy a string and trim of leading and trailing white space characters.
20  * Allow the user to escape out the stop character by putting a backslash before
21  * the character.
22  * @param dest where to store the result
23  * @param size size of the result buffer
24  * @param src where to copy data from
25  * @param end the last character parsed is returned here
26  * @param stop the character to stop copying.
27  * @return 0 if success, -1 otherwise
28  */
trim_copy(char *dest, size_t size, const char *src, const char **end, char stop)29 static int trim_copy(char *dest, size_t size, const char *src,
30                      const char **end, char stop) {
31    size_t n = 0;
32    bool escape = false;
33    int ret = 0;
34    const char *lastchar;
35 
36    while (isspace(*src)) {
37       ++src;
38    }
39 
40    /* Find the last non-escaped non-space character */
41    lastchar = src + strlen(src) - 1;
42    while (lastchar > src && isspace(*lastchar)) {
43        lastchar--;
44    }
45    if (lastchar < src || *lastchar == '\\') {
46        lastchar++;
47    }
48    cb_assert(lastchar >= src);
49 
50    do {
51       if ((*dest = *src) == '\\') {
52          escape = true;
53       } else {
54          escape = false;
55          ++dest;
56       }
57       ++n;
58       ++src;
59 
60    } while (!(n == size || src > lastchar || ((*src == stop) && !escape) || *src == '\0'));
61    *end = src;
62 
63    if (n == size) {
64        --dest;
65        ret = -1;
66    }
67    *dest = '\0';
68 
69    return ret;
70 }
71 
72 
parse_config(const char *str, struct config_item *items, FILE *error)73 int parse_config(const char *str, struct config_item *items, FILE *error) {
74    const char *end;
75    char key[80];
76       char value[1024];
77    int ret = 0;
78    const char *ptr = str;
79    int ii;
80 
81    while (*ptr != '\0') {
82       while (isspace(*ptr)) {
83          ++ptr;
84       }
85       if (*ptr == '\0') {
86          /* end of parameters */
87          return 0;
88       }
89 
90       if (trim_copy(key, sizeof(key), ptr, &end, '=') == -1) {
91          if (error != NULL) {
92             fprintf(error, "ERROR: Invalid key, starting at: <%s>\n", ptr);
93          }
94          return -1;
95       }
96 
97       ptr = end + 1;
98       if (trim_copy(value, sizeof(value), ptr, &end, ';') == -1) {
99          if (error != NULL) {
100             fprintf(error, "ERROR: Invalid value, starting at: <%s>\n", ptr);
101          }
102          return -1;
103       }
104       if (*end == ';') {
105          ptr = end + 1;
106       } else {
107          ptr = end;
108       }
109 
110       ii = 0;
111       while (items[ii].key != NULL) {
112          if (strcmp(key, items[ii].key) == 0) {
113             if (items[ii].found) {
114                if (error != NULL) {
115                   fprintf(error, "WARNING: Found duplicate entry for \"%s\"\n",
116                           items[ii].key);
117                }
118             }
119 
120             switch (items[ii].datatype) {
121             case DT_SIZE:
122             case DT_SSIZE:
123                {
124                   const char *sfx = "kmgt";
125                   size_t multiplier = 1;
126                   size_t m = 1;
127                   const char *p;
128 
129                   for (p = sfx; *p != '\0'; ++p) {
130                      char *ptr = strchr(value, *p);
131                      m *= 1024;
132                      if (ptr == NULL) {
133                         ptr = strchr(value, toupper(*p));
134                      }
135                      if (ptr != NULL) {
136                         multiplier = m;
137                         *ptr = '\0';
138                         break;
139                      }
140                   }
141 
142                   if (items[ii].datatype == DT_SIZE) {
143                      uint64_t val;
144                      if (safe_strtoull(value, val)) {
145                         *items[ii].value.dt_size = (size_t)(val * multiplier);
146                         items[ii].found = true;
147                      } else {
148                         ret = -1;
149                      }
150                   } else {
151                      int64_t val;
152                      if (safe_strtoll(value, val)) {
153                         *items[ii].value.dt_size = (size_t)(val * multiplier);
154                         items[ii].found = true;
155                      } else {
156                         ret = -1;
157                      }
158                   }
159                }
160                break;
161             case DT_FLOAT:
162                {
163                   float val;
164                   if (safe_strtof(value, val)) {
165                      *items[ii].value.dt_float = val;
166                      items[ii].found = true;
167                   } else {
168                      ret = -1;
169                   }
170                }
171                break;
172             case DT_STRING:
173                // MB-20598: free the dt_string as in case of a duplicate config entry we
174                // would leak when overwriting with the next strdup.
175                if (items[ii].found) {
176                    cb_free(*items[ii].value.dt_string);
177                }
178                *items[ii].value.dt_string = cb_strdup(value);
179                items[ii].found = true;
180                break;
181             case DT_BOOL:
182                if (strcasecmp(value, "true") == 0 || strcasecmp(value, "on") == 0) {
183                   *items[ii].value.dt_bool = true;
184                   items[ii].found = true;
185                } else if (strcasecmp(value, "false") == 0 || strcasecmp(value, "off") == 0) {
186                   *items[ii].value.dt_bool = false;
187                   items[ii].found = true;
188                } else {
189                   ret = -1;
190                }
191                break;
192             case DT_CONFIGFILE:
193                {
194                   int r = read_config_file(value, items, error);
195                   if (r != 0) {
196                      ret = r;
197                   }
198                }
199                break;
200             default:
201                /* You need to fix your code!!! */
202                fprintf(error, "ERROR: Invalid datatype %d for Key: <%s>\n",
203                        items[ii].datatype, key);
204                ret = -1;
205             }
206 
207             if (ret == -1) {
208                if (error != NULL) {
209                   fprintf(error, "Invalid entry, Key: <%s> Value: <%s>\n",
210                           key, value);
211                }
212                return ret;
213             }
214             break;
215          }
216          ++ii;
217       }
218 
219       if (items[ii].key == NULL) {
220          if (error != NULL) {
221             fprintf(error, "Unsupported key: <%s>\n", key);
222          }
223          ret = 1;
224       }
225    }
226    return ret;
227 }
228 
read_config_file(const char *fname, struct config_item items[], FILE *error)229 static int read_config_file(const char *fname, struct config_item items[],
230                             FILE *error) {
231    char line[1024];
232    int ret = 0;
233    FILE *fp = fopen(fname, "r");
234    if (fp == NULL) {
235       if (error != NULL) {
236          fprintf(error, "Failed to open file: %s\n", fname);
237       }
238       return -1;
239    }
240 
241    while (fgets(line, sizeof(line), fp) != NULL && ret != -1) {
242       int r;
243       if (line[0] == '#') {
244          /* Ignore comment line */
245          continue;
246       }
247 
248       r = parse_config(line, items, error);
249       if (r != 0) {
250          ret = r;
251       }
252    }
253 
254    (void)fclose(fp);
255 
256    return ret;
257 }
258