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