Merge branch 'PolynomialDivision-feature/add_workflow'
[olsrd.git] / lib / sgwdynspeed / src / speedFile.c
1 /*
2  * The olsr.org Optimized Link-State Routing daemon (olsrd)
3  *
4  * (c) by the OLSR project
5  *
6  * See our Git repository to find out who worked on this file
7  * and thus is a copyright holder on it.
8  *
9  * All rights reserved.
10  *
11  * Redistribution and use in source and binary forms, with or without
12  * modification, are permitted provided that the following conditions
13  * are met:
14  *
15  * * Redistributions of source code must retain the above copyright
16  *   notice, this list of conditions and the following disclaimer.
17  * * Redistributions in binary form must reproduce the above copyright
18  *   notice, this list of conditions and the following disclaimer in
19  *   the documentation and/or other materials provided with the
20  *   distribution.
21  * * Neither the name of olsr.org, olsrd nor the names of its
22  *   contributors may be used to endorse or promote products derived
23  *   from this software without specific prior written permission.
24  *
25  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
26  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
27  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
28  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
29  * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
30  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
31  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
32  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
33  * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
34  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
35  * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36  * POSSIBILITY OF SUCH DAMAGE.
37  *
38  * Visit http://www.olsr.org for more information.
39  *
40  * If you find this software useful feel free to make a donation
41  * to the project. For more information see the website or contact
42  * the copyright holders.
43  *
44  */
45
46 #include "speedFile.h"
47
48 /* Plugin includes */
49 #include "sgwDynSpeed.h"
50
51 /* OLSRD includes */
52 #include "olsr_cfg.h"
53 #include "gateway.h"
54
55 /* System includes */
56 #include <stddef.h>
57 #include <unistd.h>
58 #include <fcntl.h>
59 #include <stdlib.h>
60 #include <regex.h>
61 #include <errno.h>
62 #include <sys/stat.h>
63 #include <stdio.h>
64 #include <assert.h>
65
66 #define SPEED_UPLINK_NAME   "upstream"
67 #define SPEED_DOWNLINK_NAME "downstream"
68
69 /** the maximal length of a line that is read from the file */
70 #define LINE_LENGTH 256
71
72 /** regular expression describing a comment */
73 static const char * regexCommentString = "^([[:space:]]*|[[:space:]#]+.*)$";
74
75 /** regular expression describing a key/value pair */
76 static const char * regexNameValueString =
77                 "^[[:space:]]*([^[:space:]]+)[[:space:]]*=[[:space:]]*(.*?)[[:space:]]*$";
78
79 /** the number of matches in regexNameValueString */
80 static const size_t regexNameValuematchCount = 3;
81
82 /** the compiled regular expression describing a comment */
83 static regex_t regexComment;
84
85 /** the compiled regular expression describing a key/value pair */
86 static regex_t regexNameValue;
87
88 /** true when the plugin has been started */
89 static bool started = false;
90
91 /** type to hold the cached stat result */
92 typedef struct _CachedStat {
93 #if defined(__linux__) && !defined(__ANDROID__)
94   struct timespec timeStamp; /* Time of last modification (full resolution) */
95 #else
96   time_t timeStamp; /* Time of last modification (second resolution) */
97 #endif
98 } CachedStat;
99
100 /** the cached stat result */
101 static CachedStat cachedStat;
102
103 /**
104  Read an unsigned long number from a value string
105
106  @param valueName
107  the name of the value
108  @param value
109  the string to convert to a number
110  @param valueNumber
111  a pointer to the location where to store the number upon successful conversion
112
113  @return
114  - true on success
115  - false otherwise
116  */
117 static bool readUL(const char * valueName, const char * value, unsigned long * valueNumber) {
118         char * endPtr = NULL;
119         unsigned long valueNew;
120
121         assert(valueName != NULL);
122         assert(value != NULL);
123         assert(valueNumber != NULL);
124
125         errno = 0;
126         valueNew = strtoul(value, &endPtr, 10);
127
128         if (!((endPtr != value) && (*value != '\0') && (*endPtr == '\0')) || (errno == ERANGE)) {
129                 /* invalid conversion */
130                 sgwDynSpeedError(false, "Value of parameter %s (%s) could not be converted to a number", valueName, value);
131                 return false;
132         }
133
134         *valueNumber = valueNew;
135
136         return true;
137 }
138
139 /**
140  * Strip EOL characters from a string
141  *
142  * @param str the string to strip
143  * @param endindex the index of the \0 string terminator (end-of-string/strlen)
144  */
145 static void stripEols(char * str, regoff_t endindex) {
146   regoff_t len = endindex;
147   while ((len > 0) && ((str[len - 1] == '\n') || (str[len - 1] == '\r'))) {
148     len--;
149   }
150   str[len] = '\0';
151 }
152
153 /**
154  * Initialises the speedFile reader.
155  * @return true upon success, false otherwise
156  */
157 bool startSpeedFile(void) {
158         if (started) {
159                 return true;
160         }
161
162         if (regcomp(&regexComment, regexCommentString, REG_EXTENDED | REG_ICASE)) {
163                 sgwDynSpeedError(false, "Could not compile regex \"%s\"", regexCommentString);
164                 return false;
165         }
166
167         if (regcomp(&regexNameValue, regexNameValueString, REG_EXTENDED | REG_ICASE)) {
168                 sgwDynSpeedError(false, "Could not compile regex \"%s\"", regexNameValueString);
169                 regfree(&regexComment);
170                 return false;
171         }
172
173         memset(&cachedStat, 0, sizeof(cachedStat));
174
175         started = true;
176         return true;
177 }
178
179 /**
180  * Cleans up the speedFile reader.
181  */
182 void stopSpeedFile(void) {
183         if (started) {
184                 regfree(&regexNameValue);
185                 regfree(&regexComment);
186                 started = false;
187         }
188 }
189
190 /**
191  * Performs a regex match
192  * @param regex the compiled regex to match against
193  * @param line the line to match
194  * @param nmatch the number of matches to produce
195  * @param pmatch the array with match information
196  * @return true upon success, false otherwise
197  */
198 static bool regexMatch(regex_t * regex, char * line, size_t nmatch, regmatch_t pmatch[]) {
199         int result = regexec(regex, line, nmatch, pmatch, 0);
200         if (!result) {
201                 return true;
202         }
203
204         if (result == REG_NOMATCH) {
205                 return false;
206         }
207
208         {
209                 char msgbuf[256];
210                 regerror(result, regex, msgbuf, sizeof(msgbuf));
211                 sgwDynSpeedError(false, "Regex match failed: %s", msgbuf);
212         }
213
214         return false;
215 }
216
217 /** the buffer in which to store a line read from the file */
218 static char line[LINE_LENGTH];
219
220 static bool reportedErrorsPrevious = false;
221
222 /**
223  * Read the speed file
224  * @param fileName the filename
225  */
226 void readSpeedFile(char * fileName) {
227         int fd;
228         struct stat statBuf;
229         FILE * fp = NULL;
230         void * mtim;
231         unsigned int lineNumber = 0;
232
233         char * name = NULL;
234         char * value = NULL;
235
236         unsigned long uplink = DEF_UPLINK_SPEED;
237         unsigned long downlink = DEF_DOWNLINK_SPEED;
238         bool uplinkSet = false;
239         bool downlinkSet = false;
240         bool reportedErrors = false;
241
242         fd = open(fileName, O_RDONLY);
243         if (fd < 0) {
244                 /* could not open the file */
245                 memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
246                 goto out;
247         }
248
249         if (fstat(fd, &statBuf)) {
250                 /* could not stat the file */
251                 memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
252                 goto out;
253         }
254
255 #if defined(__linux__) && !defined(__ANDROID__)
256         mtim = &statBuf.st_mtim;
257 #else
258         mtim = &statBuf.st_mtime;
259 #endif
260
261         if (!memcmp(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp))) {
262                 /* file did not change since last read */
263                 goto out;
264         }
265
266         fp = fdopen(fd, "r");
267         if (!fp) {
268                 /* could not open the file */
269                 goto out;
270         }
271
272         memcpy(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp));
273
274         while (fgets(line, LINE_LENGTH, fp)) {
275                 regmatch_t pmatch[regexNameValuematchCount];
276
277                 lineNumber++;
278
279                 if (regexMatch(&regexComment, line, 0, NULL)) {
280                         continue;
281                 }
282
283                 if (!regexMatch(&regexNameValue, line, regexNameValuematchCount, pmatch)) {
284                         sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d uses invalid syntax: ignored (%s)", fileName, lineNumber,
285                                         line);
286                         continue;
287                 }
288
289                 stripEols(line, pmatch[2].rm_eo);
290
291                 /* determine name/value */
292                 name = &line[pmatch[1].rm_so];
293                 line[pmatch[1].rm_eo] = '\0';
294                 value = &line[pmatch[2].rm_so];
295                 line[pmatch[2].rm_eo] = '\0';
296
297                 if (!strncasecmp(SPEED_UPLINK_NAME, name, sizeof(line))) {
298                         if (!readUL(SPEED_UPLINK_NAME, value, &uplink)) {
299                                 sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d: %s value \"%s\" is not a valid number: ignored",
300                                         fileName, lineNumber, SPEED_UPLINK_NAME, value);
301                                 reportedErrors = true;
302                         } else {
303                                 uplinkSet = true;
304                         }
305                 } else if (!strncasecmp(SPEED_DOWNLINK_NAME, name, sizeof(line))) {
306                         if (!readUL(SPEED_DOWNLINK_NAME, value, &downlink)) {
307                                 sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d: %s value \"%s\" is not a valid number: ignored",
308                                         fileName, lineNumber, SPEED_DOWNLINK_NAME, value);
309                                 reportedErrors = true;
310                         } else {
311                                 downlinkSet = true;
312                         }
313                 } else {
314                   if (!reportedErrorsPrevious) {
315                     sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d specifies an unknown option \"%s\": ignored",
316                         fileName, lineNumber, name);
317                     reportedErrors = true;
318                   }
319                 }
320         }
321
322         reportedErrorsPrevious = reportedErrors;
323
324         if (uplinkSet) {
325           smartgw_set_uplink(olsr_cnf, uplink);
326         }
327         if (downlinkSet) {
328           smartgw_set_downlink(olsr_cnf, downlink);
329         }
330         if (uplinkSet || downlinkSet) {
331           refresh_smartgw_netmask();
332         }
333
334         out: if (fp) {
335                 fclose(fp);
336         }
337         if (fd >= 0) {
338                 close(fd);
339         }
340 }