0937da2100ed2fc1355f058878bbeb10f5d9dc4c
[olsrd.git] / lib / sgwdynspeed / src / speedFile.c
1 #include "speedFile.h"
2
3 /* Plugin includes */
4 #include "sgwDynSpeed.h"
5
6 /* OLSRD includes */
7 #include "olsr_cfg.h"
8 #include "gateway.h"
9
10 /* System includes */
11 #include <stddef.h>
12 #include <unistd.h>
13 #include <fcntl.h>
14 #include <stdlib.h>
15 #include <regex.h>
16 #include <errno.h>
17 #include <sys/stat.h>
18 #include <stdio.h>
19 #include <assert.h>
20
21 #define SPEED_UPLINK_NAME   "upstream"
22 #define SPEED_DOWNLINK_NAME "downstream"
23
24 /** the maximal length of a line that is read from the file */
25 #define LINE_LENGTH 256
26
27 /** regular expression describing a comment */
28 static const char * regexCommentString = "^([[:space:]]*|[[:space:]#]+.*)$";
29
30 /** regular expression describing a key/value pair */
31 static const char * regexNameValueString =
32                 "^[[:space:]]*([^[:space:]]+)[[:space:]]*=[[:space:]]*(.*?)[[:space:]]*$";
33
34 /** the number of matches in regexNameValueString */
35 static const size_t regexNameValuematchCount = 3;
36
37 /** the compiled regular expression describing a comment */
38 static regex_t regexComment;
39
40 /** the compiled regular expression describing a key/value pair */
41 static regex_t regexNameValue;
42
43 /** true when the plugin has been started */
44 static bool started = false;
45
46 /** type to hold the cached stat result */
47 typedef struct _CachedStat {
48         time_t timeStamp; /* Time of last modification (second resolution) */
49 } CachedStat;
50
51 /** the cached stat result */
52 static CachedStat cachedStat;
53
54 /**
55  Read an unsigned long number from a value string
56
57  @param valueName
58  the name of the value
59  @param value
60  the string to convert to a number
61  @param valueNumber
62  a pointer to the location where to store the number upon successful conversion
63
64  @return
65  - true on success
66  - false otherwise
67  */
68 static bool readUL(const char * valueName, const char * value, unsigned long * valueNumber) {
69         char * endPtr = NULL;
70         unsigned long valueNew;
71
72         assert(valueName != NULL);
73         assert(value != NULL);
74         assert(valueNumber != NULL);
75
76         errno = 0;
77         valueNew = strtoul(value, &endPtr, 10);
78
79         if (!((endPtr != value) && (*value != '\0') && (*endPtr == '\0')) || (errno == ERANGE)) {
80                 /* invalid conversion */
81                 sgwDynSpeedError(false, "Value of parameter %s (%s) could not be converted to a number", valueName, value);
82                 return false;
83         }
84
85         *valueNumber = valueNew;
86
87         return true;
88 }
89
90 /**
91  * Strip EOL characters from a string
92  *
93  * @param str the string to strip
94  * @param endindex the index of the \0 string terminator (end-of-string/strlen)
95  */
96 static void stripEols(char * str, regoff_t endindex) {
97   regoff_t len = endindex;
98   while ((len > 0) && ((str[len - 1] == '\n') || (str[len - 1] == '\r'))) {
99     len--;
100   }
101   str[len] = '\0';
102 }
103
104 /**
105  * Initialises the speedFile reader.
106  * @return true upon success, false otherwise
107  */
108 bool startSpeedFile(void) {
109         if (started) {
110                 return true;
111         }
112
113         if (regcomp(&regexComment, regexCommentString, REG_EXTENDED | REG_ICASE)) {
114                 sgwDynSpeedError(false, "Could not compile regex \"%s\"", regexCommentString);
115                 return false;
116         }
117
118         if (regcomp(&regexNameValue, regexNameValueString, REG_EXTENDED | REG_ICASE)) {
119                 sgwDynSpeedError(false, "Could not compile regex \"%s\"", regexNameValueString);
120                 regfree(&regexComment);
121                 return false;
122         }
123
124         cachedStat.timeStamp = -1;
125
126         started = true;
127         return true;
128 }
129
130 /**
131  * Cleans up the speedFile reader.
132  */
133 void stopSpeedFile(void) {
134         if (started) {
135                 regfree(&regexNameValue);
136                 regfree(&regexComment);
137                 started = false;
138         }
139 }
140
141 /**
142  * Performs a regex match
143  * @param regex the compiled regex to match against
144  * @param line the line to match
145  * @param nmatch the number of matches to produce
146  * @param pmatch the array with match information
147  * @return true upon success, false otherwise
148  */
149 static bool regexMatch(regex_t * regex, char * line, size_t nmatch, regmatch_t pmatch[]) {
150         int result = regexec(regex, line, nmatch, pmatch, 0);
151         if (!result) {
152                 return true;
153         }
154
155         if (result == REG_NOMATCH) {
156                 return false;
157         }
158
159         {
160                 char msgbuf[256];
161                 regerror(result, regex, msgbuf, sizeof(msgbuf));
162                 sgwDynSpeedError(false, "Regex match failed: %s", msgbuf);
163         }
164
165         return false;
166 }
167
168 /** the buffer in which to store a line read from the file */
169 static char line[LINE_LENGTH];
170
171 static bool reportedErrorsPrevious = false;
172
173 /**
174  * Read the speed file
175  * @param fileName the filename
176  */
177 void readSpeedFile(char * fileName) {
178         int fd;
179         struct stat statBuf;
180         FILE * fp = NULL;
181         unsigned int lineNumber = 0;
182         char * name = NULL;
183         char * value = NULL;
184         unsigned long uplink = DEF_UPLINK_SPEED;
185         unsigned long downlink = DEF_DOWNLINK_SPEED;
186         bool uplinkSet = false;
187         bool downlinkSet = false;
188         bool reportedErrors = false;
189
190         fd = open(fileName, O_RDONLY);
191         if (fd < 0) {
192                 /* could not access the file */
193                 goto out;
194         }
195
196         if (fstat(fd, &statBuf)) {
197                 /* could not access the file */
198                 goto out;
199         }
200
201         if (!memcmp(&cachedStat.timeStamp, &statBuf.st_mtime, sizeof(cachedStat.timeStamp))) {
202                 /* file did not change since last read */
203                 goto out;
204         }
205
206         fp = fdopen(fd, "r");
207         if (!fp) {
208                 goto out;
209         }
210
211         memcpy(&cachedStat.timeStamp, &statBuf.st_mtime, sizeof(cachedStat.timeStamp));
212
213         while (fgets(line, LINE_LENGTH, fp)) {
214                 regmatch_t pmatch[regexNameValuematchCount];
215
216                 lineNumber++;
217
218                 if (regexMatch(&regexComment, line, 0, NULL)) {
219                         continue;
220                 }
221
222                 if (!regexMatch(&regexNameValue, line, regexNameValuematchCount, pmatch)) {
223                         sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d uses invalid syntax: ignored (%s)", fileName, lineNumber,
224                                         line);
225                         continue;
226                 }
227
228                 stripEols(line, pmatch[2].rm_eo);
229
230                 /* determine name/value */
231                 name = &line[pmatch[1].rm_so];
232                 line[pmatch[1].rm_eo] = '\0';
233                 value = &line[pmatch[2].rm_so];
234                 line[pmatch[2].rm_eo] = '\0';
235
236                 if (!strncasecmp(SPEED_UPLINK_NAME, name, sizeof(line))) {
237                         if (!readUL(SPEED_UPLINK_NAME, value, &uplink)) {
238                                 sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d: %s value \"%s\" is not a valid number: ignored",
239                                         fileName, lineNumber, SPEED_UPLINK_NAME, value);
240                                 reportedErrors = true;
241                         } else {
242                                 uplinkSet = true;
243                         }
244                 } else if (!strncasecmp(SPEED_DOWNLINK_NAME, name, sizeof(line))) {
245                         if (!readUL(SPEED_DOWNLINK_NAME, value, &downlink)) {
246                                 sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d: %s value \"%s\" is not a valid number: ignored",
247                                         fileName, lineNumber, SPEED_DOWNLINK_NAME, value);
248                                 reportedErrors = true;
249                         } else {
250                                 downlinkSet = true;
251                         }
252                 } else {
253                   if (!reportedErrorsPrevious) {
254                     sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d specifies an unknown option \"%s\": ignored",
255                         fileName, lineNumber, name);
256                     reportedErrors = true;
257                   }
258                 }
259         }
260
261         reportedErrorsPrevious = reportedErrors;
262
263         fclose(fp);
264         fp = NULL;
265
266         if (uplinkSet) {
267           smartgw_set_uplink(olsr_cnf, uplink);
268         }
269         if (downlinkSet) {
270           smartgw_set_downlink(olsr_cnf, downlink);
271         }
272         if (uplinkSet || downlinkSet) {
273           refresh_smartgw_netmask();
274         }
275
276         out: if (fd >= 0) {
277                 close(fd);
278         }
279 }