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