sgwdynspeed: properly close the speed file
[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 #if defined(__linux__) && !defined(__ANDROID__)
49   struct timespec timeStamp; /* Time of last modification (full resolution) */
50 #else
51   time_t timeStamp; /* Time of last modification (second resolution) */
52 #endif
53 } CachedStat;
54
55 /** the cached stat result */
56 static CachedStat cachedStat;
57
58 /**
59  Read an unsigned long number from a value string
60
61  @param valueName
62  the name of the value
63  @param value
64  the string to convert to a number
65  @param valueNumber
66  a pointer to the location where to store the number upon successful conversion
67
68  @return
69  - true on success
70  - false otherwise
71  */
72 static bool readUL(const char * valueName, const char * value, unsigned long * valueNumber) {
73         char * endPtr = NULL;
74         unsigned long valueNew;
75
76         assert(valueName != NULL);
77         assert(value != NULL);
78         assert(valueNumber != NULL);
79
80         errno = 0;
81         valueNew = strtoul(value, &endPtr, 10);
82
83         if (!((endPtr != value) && (*value != '\0') && (*endPtr == '\0')) || (errno == ERANGE)) {
84                 /* invalid conversion */
85                 sgwDynSpeedError(false, "Value of parameter %s (%s) could not be converted to a number", valueName, value);
86                 return false;
87         }
88
89         *valueNumber = valueNew;
90
91         return true;
92 }
93
94 /**
95  * Strip EOL characters from a string
96  *
97  * @param str the string to strip
98  * @param endindex the index of the \0 string terminator (end-of-string/strlen)
99  */
100 static void stripEols(char * str, regoff_t endindex) {
101   regoff_t len = endindex;
102   while ((len > 0) && ((str[len - 1] == '\n') || (str[len - 1] == '\r'))) {
103     len--;
104   }
105   str[len] = '\0';
106 }
107
108 /**
109  * Initialises the speedFile reader.
110  * @return true upon success, false otherwise
111  */
112 bool startSpeedFile(void) {
113         if (started) {
114                 return true;
115         }
116
117         if (regcomp(&regexComment, regexCommentString, REG_EXTENDED | REG_ICASE)) {
118                 sgwDynSpeedError(false, "Could not compile regex \"%s\"", regexCommentString);
119                 return false;
120         }
121
122         if (regcomp(&regexNameValue, regexNameValueString, REG_EXTENDED | REG_ICASE)) {
123                 sgwDynSpeedError(false, "Could not compile regex \"%s\"", regexNameValueString);
124                 regfree(&regexComment);
125                 return false;
126         }
127
128         memset(&cachedStat, 0, sizeof(cachedStat));
129
130         started = true;
131         return true;
132 }
133
134 /**
135  * Cleans up the speedFile reader.
136  */
137 void stopSpeedFile(void) {
138         if (started) {
139                 regfree(&regexNameValue);
140                 regfree(&regexComment);
141                 started = false;
142         }
143 }
144
145 /**
146  * Performs a regex match
147  * @param regex the compiled regex to match against
148  * @param line the line to match
149  * @param nmatch the number of matches to produce
150  * @param pmatch the array with match information
151  * @return true upon success, false otherwise
152  */
153 static bool regexMatch(regex_t * regex, char * line, size_t nmatch, regmatch_t pmatch[]) {
154         int result = regexec(regex, line, nmatch, pmatch, 0);
155         if (!result) {
156                 return true;
157         }
158
159         if (result == REG_NOMATCH) {
160                 return false;
161         }
162
163         {
164                 char msgbuf[256];
165                 regerror(result, regex, msgbuf, sizeof(msgbuf));
166                 sgwDynSpeedError(false, "Regex match failed: %s", msgbuf);
167         }
168
169         return false;
170 }
171
172 /** the buffer in which to store a line read from the file */
173 static char line[LINE_LENGTH];
174
175 static bool reportedErrorsPrevious = false;
176
177 /**
178  * Read the speed file
179  * @param fileName the filename
180  */
181 void readSpeedFile(char * fileName) {
182         int fd;
183         struct stat statBuf;
184         FILE * fp = NULL;
185         void * mtim;
186         unsigned int lineNumber = 0;
187
188         char * name = NULL;
189         char * value = NULL;
190
191         unsigned long uplink = DEF_UPLINK_SPEED;
192         unsigned long downlink = DEF_DOWNLINK_SPEED;
193         bool uplinkSet = false;
194         bool downlinkSet = false;
195         bool reportedErrors = false;
196
197         fd = open(fileName, O_RDONLY);
198         if (fd < 0) {
199                 /* could not open the file */
200                 memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
201                 goto out;
202         }
203
204         if (fstat(fd, &statBuf)) {
205                 /* could not stat the file */
206                 memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
207                 goto out;
208         }
209
210 #if defined(__linux__) && !defined(__ANDROID__)
211         mtim = &statBuf.st_mtim;
212 #else
213         mtim = &statBuf.st_mtime;
214 #endif
215
216         if (!memcmp(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp))) {
217                 /* file did not change since last read */
218                 goto out;
219         }
220
221         fp = fdopen(fd, "r");
222         if (!fp) {
223                 /* could not open the file */
224                 goto out;
225         }
226
227         memcpy(&cachedStat.timeStamp, &statBuf.st_mtime, sizeof(cachedStat.timeStamp));
228
229         while (fgets(line, LINE_LENGTH, fp)) {
230                 regmatch_t pmatch[regexNameValuematchCount];
231
232                 lineNumber++;
233
234                 if (regexMatch(&regexComment, line, 0, NULL)) {
235                         continue;
236                 }
237
238                 if (!regexMatch(&regexNameValue, line, regexNameValuematchCount, pmatch)) {
239                         sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d uses invalid syntax: ignored (%s)", fileName, lineNumber,
240                                         line);
241                         continue;
242                 }
243
244                 stripEols(line, pmatch[2].rm_eo);
245
246                 /* determine name/value */
247                 name = &line[pmatch[1].rm_so];
248                 line[pmatch[1].rm_eo] = '\0';
249                 value = &line[pmatch[2].rm_so];
250                 line[pmatch[2].rm_eo] = '\0';
251
252                 if (!strncasecmp(SPEED_UPLINK_NAME, name, sizeof(line))) {
253                         if (!readUL(SPEED_UPLINK_NAME, value, &uplink)) {
254                                 sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d: %s value \"%s\" is not a valid number: ignored",
255                                         fileName, lineNumber, SPEED_UPLINK_NAME, value);
256                                 reportedErrors = true;
257                         } else {
258                                 uplinkSet = true;
259                         }
260                 } else if (!strncasecmp(SPEED_DOWNLINK_NAME, name, sizeof(line))) {
261                         if (!readUL(SPEED_DOWNLINK_NAME, value, &downlink)) {
262                                 sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d: %s value \"%s\" is not a valid number: ignored",
263                                         fileName, lineNumber, SPEED_DOWNLINK_NAME, value);
264                                 reportedErrors = true;
265                         } else {
266                                 downlinkSet = true;
267                         }
268                 } else {
269                   if (!reportedErrorsPrevious) {
270                     sgwDynSpeedError(false, "Gateway speed file \"%s\", line %d specifies an unknown option \"%s\": ignored",
271                         fileName, lineNumber, name);
272                     reportedErrors = true;
273                   }
274                 }
275         }
276
277         reportedErrorsPrevious = reportedErrors;
278
279         fclose(fp);
280         fp = NULL;
281
282         if (uplinkSet) {
283           smartgw_set_uplink(olsr_cnf, uplink);
284         }
285         if (downlinkSet) {
286           smartgw_set_downlink(olsr_cnf, downlink);
287         }
288         if (uplinkSet || downlinkSet) {
289           refresh_smartgw_netmask();
290         }
291
292         out: if (fp) {
293                 fclose(fp);
294         }
295         if (fd >= 0) {
296                 close(fd);
297         }
298 }