98dc56a48374a3fd7d92d98efcc8754fa12ddaf1
[olsrd.git] / src / egressFile.c
1 #ifdef __linux__
2
3 #include "egressFile.h"
4
5 /* Plugin includes */
6
7 /* OLSRD includes */
8 #include "olsr_cfg.h"
9 #include "gateway_costs.h"
10 #include "gateway.h"
11 #include "scheduler.h"
12 #include "ipcalc.h"
13 #include "log.h"
14
15 /* System includes */
16 #include <unistd.h>
17 #include <fcntl.h>
18 #include <regex.h>
19 #include <sys/stat.h>
20 #include <assert.h>
21 #include <net/if.h>
22
23 /** the maximum length of a line that is read from the file */
24 #define LINE_LENGTH 256
25
26 /** the weights for the cost calculation */
27 static struct costs_weights gw_costs_weights_storage;
28 static struct costs_weights * gw_costs_weights = NULL;
29
30 /** regular expression describing a comment */
31 static const char * regexComment = "^([[:space:]]*|[[:space:]#]+.*)$";
32
33 /**
34  * regular expression describing an egress line.
35  *
36  * # interface=uplink (Kbps),downlink (Kbps),path cost,gateway
37  */
38 static const char * regexEgress = "^[[:space:]]*" //
39         "([^[:space:]=]+)"                     /* 01: interface, mandatory, can NOT be empty */
40         "[[:space:]]*=[[:space:]]*"            /* --: field separator */
41         "([[:digit:]]*)"                       /* 02: uplink, mandatory, can be empty */
42         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
43         "([[:digit:]]*)"                       /* 03: downlink, mandatory, can be empty */
44         "("                                    /* 04: (the rest is optional) */
45         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
46         "([[:digit:]]*)"                       /* 05: path cost, optional, can be empty */
47         "("                                    /* 06: (the rest is optional) */
48         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
49         "(|([[:digit:]\\.:]+)/([[:digit:]]+))" /* 07: network, optional, can be empty, 07=ip/x 08=ip 09=x */
50         "("                                    /* 10: (the rest is optional) */
51         "[[:space:]]*,[[:space:]]*"            /* --: field separator */
52         "([[:digit:]\\.:]*)"                   /* 11: gateway, optional, can be empty */
53         ")?"                                   /* 10 */
54         ")?"                                   /* 06 */
55         ")?"                                   /* 04 */
56         "[[:space:]]*$";
57
58 /** the number of matches in regexEgress */
59 #define REGEX_EGRESS_LINE_MATCH_COUNT (1 /* 00 */ + 11)
60
61 /** the compiled regular expression describing a comment */
62 static regex_t compiledRegexComment;
63
64 /** the compiled regular expression describing an egress line */
65 static regex_t compiledRegexEgress;
66
67 /** true when the file reader has been started */
68 static bool started = false;
69
70 /** type to hold the cached stat result */
71 typedef struct _CachedStat {
72 #if defined(__linux__) && !defined(__ANDROID__)
73   struct timespec timeStamp; /* Time of last modification (full resolution) */
74 #else
75   time_t timeStamp; /* Time of last modification (second resolution) */
76 #endif
77 } CachedStat;
78
79 /** the cached stat result */
80 static CachedStat cachedStat;
81 static CachedStat cachedStatClear;
82
83 /** the malloc-ed buffer in which to store a line read from the file */
84 static char * line = NULL;
85
86 /* forward declaration */
87 static bool readEgressFile(const char * fileName);
88
89 /*
90  * Error Reporting
91  */
92
93 /** the maximum length of an error report */
94 #define ERROR_LENGTH 1024
95
96 /** true when errors have been reported, used to reduce error reports */
97 static bool reportedErrors = false;
98
99 /**
100  * Report an error.
101  *
102  * @param useErrno
103  * when true then errno is used in the error message; the error reason is also
104  * reported.
105  * @param lineNo
106  * the line number of the caller
107  * @param format
108  * a pointer to the format string
109  * @param ...
110  * arguments to the format string
111  */
112 __attribute__ ((format(printf, 3, 4)))
113 static void egressFileError(bool useErrno, int lineNo, const char *format, ...) {
114   char str[ERROR_LENGTH];
115   char *strErr = NULL;
116
117   if (reportedErrors) {
118     return;
119   }
120
121   if (useErrno) {
122     strErr = strerror(errno);
123   }
124
125   if ((format == NULL ) || (*format == '\0')) {
126     olsr_syslog(OLSR_LOG_ERR, "%s@%d: %s\n", __FILE__, lineNo, useErrno ? strErr : "Unknown error");
127   } else {
128     va_list arglist;
129
130     va_start(arglist, format);
131     vsnprintf(str, sizeof(str), format, arglist);
132     va_end(arglist);
133
134     str[sizeof(str) - 1] = '\0'; /* Ensures null termination */
135
136     if (useErrno) {
137       olsr_syslog(OLSR_LOG_ERR, "%s@%d: %s: %s\n", __FILE__, lineNo, str, strErr);
138     } else {
139       olsr_syslog(OLSR_LOG_ERR, "%s@%d: %s\n", __FILE__, lineNo, str);
140     }
141   }
142 }
143
144 /*
145  * Helpers
146  */
147
148 #ifdef __ANDROID__
149 static ssize_t getline(char **lineptr, size_t *n, FILE *stream)
150 {
151     char *ptr;
152     size_t len;
153
154     ptr = fgetln(stream, n);
155
156     if (ptr == NULL) {
157         return -1;
158     }
159
160     /* Free the original ptr */
161     if (*lineptr != NULL) free(*lineptr);
162
163     /* Add one more space for '\0' */
164     len = n[0] + 1;
165
166     /* Update the length */
167     n[0] = len;
168
169     /* Allocate a new buffer */
170     *lineptr = malloc(len);
171
172     /* Copy over the string */
173     memcpy(*lineptr, ptr, len-1);
174
175     /* Write the NULL character */
176     (*lineptr)[len-1] = '\0';
177
178     /* Return the length of the new buffer */
179     return len;
180 }
181 #endif
182
183 /**
184  * Read an (olsr_ip_addr) IP address from a string:
185  * First tries to parse the value as an IPv4 address, and if not successful
186  * tries to parse it as an IPv6 address.
187  *
188  * @param str
189  * The string to convert to an (olsr_ip_addr) IP address
190  * @param dst
191  * A pointer to the location where to store the (olsr_ip_addr) IP address upon
192  * successful conversion. Not touched when errors are reported.
193  * @param dstSet
194  * A pointer to the location where to store the flag that signals whether the
195  * IP address is set. Not touched when errors are reported.
196  * @param dstIpVersion
197  * A pointer to the location where to store the IP version of the IP address.
198  * Not touched when errors are reported.
199  *
200  * @return
201  * - true on success
202  * - false otherwise
203  */
204 static bool readIPAddress(const char * str, union olsr_ip_addr * dst, bool * dstSet, int * dstIpVersion) {
205   int conversion;
206   union olsr_ip_addr ip;
207   int ip_version;
208
209   assert(str);
210   assert(dst);
211   assert(dstSet);
212   assert(dstIpVersion);
213
214   /* try IPv4 first */
215   ip_version = AF_INET;
216   memset(&ip, 0, sizeof(ip));
217   conversion = inet_pton(ip_version, str, &ip.v4);
218
219   if (conversion != 1) {
220     /* now try IPv6: IPv4 conversion was not successful */
221     ip_version = AF_INET6;
222     memset(&ip, 0, sizeof(ip));
223     conversion = inet_pton(ip_version, str, &ip.v6);
224   }
225
226   if (conversion != 1) {
227     return false;
228   }
229
230   *dst = ip;
231   *dstSet = true;
232   *dstIpVersion = ip_version;
233   return true;
234 }
235
236 /**
237  * Read an unsigned long long number from a value string.
238  * An empty string results in a value of zero.
239  *
240  * @param value
241  * The string to convert to a number
242  * @param valueNumber
243  * A pointer to the location where to store the number upon successful conversion.
244  * Not touched when errors are reported.
245  *
246  * @return
247  * - true on success
248  * - false otherwise
249  */
250 static bool readULL(const char * value, unsigned long long * valueNumber) {
251   char * endPtr = NULL;
252   unsigned long valueNew;
253
254   assert(value);
255   assert(valueNumber);
256
257   if (!value || !strlen(value)) {
258     *valueNumber = 0;
259     return true;
260   }
261
262   errno = 0;
263   valueNew = strtoull(value, &endPtr, 10);
264
265   if (!((endPtr != value) && (*value != '\0') && (*endPtr == '\0')) || (errno == ERANGE)) {
266     /* invalid conversion */
267     return false;
268   }
269
270   *valueNumber = valueNew;
271   return true;
272 }
273
274 /**
275  * Strip EOL characters from the end of a string
276  *
277  * @param str the string to strip
278  * @param length the length of the string
279  */
280 static void stripEols(char * str, ssize_t length) {
281   ssize_t len = length;
282   while ((len > 0) && ((str[len - 1] == '\n') || (str[len - 1] == '\r'))) {
283     len--;
284   }
285   str[len] = '\0';
286 }
287
288 /**
289  * Find an egress interface in the configuration
290  *
291  * @param name the name of the egress interface
292  * @return the pointer to the egress interface, NULL when not found
293  */
294 struct sgw_egress_if * findEgressInterface(char * name) {
295   if (name && (name[0] != '\0')) {
296     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
297     while (egress_if) {
298       if (!strcmp(egress_if->name, name)) {
299         return egress_if;
300       }
301       egress_if = egress_if->next;
302     }
303   }
304
305   return NULL ;
306 }
307
308 /**
309  * Find an egress interface in the configuration by if_index
310  *
311  * @param if_index the index of the egress interface
312  * @return the pointer to the egress interface, NULL when not found
313  */
314 struct sgw_egress_if * findEgressInterfaceByIndex(int if_index) {
315   if (if_index > 0) {
316     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
317     while (egress_if) {
318       if (egress_if->if_index == if_index) {
319         return egress_if;
320       }
321       egress_if = egress_if->next;
322     }
323   }
324
325   return NULL ;
326 }
327
328 /**
329  * Calculate the costs from the bandwidth parameters
330  *
331  * @param bw the bandwidth parameters
332  * @param up true when the interface is up
333  * @return true when the costs changed
334  */
335 bool egressBwCalculateCosts(struct egress_if_bw * bw, bool up) {
336   if (!gw_costs_weights) {
337     gw_costs_weights_storage.WexitU = olsr_cnf->smart_gw_weight_exitlink_up;
338     gw_costs_weights_storage.WexitD = olsr_cnf->smart_gw_weight_exitlink_down;
339     gw_costs_weights_storage.Wetx = olsr_cnf->smart_gw_weight_etx;
340     gw_costs_weights_storage.Detx = olsr_cnf->smart_gw_divider_etx;
341     gw_costs_weights = &gw_costs_weights_storage;
342   }
343
344   {
345     int64_t costsPrevious = bw->costs;
346     bw->costs = gw_costs_weigh(up, gw_costs_weights_storage, bw->path_cost, olsr_cnf->smart_gw_path_max_cost_etx_max, bw->egressUk, bw->egressDk);
347     return (costsPrevious != bw->costs);
348   }
349 }
350
351 /**
352  * Clear the bandwidth parameters
353  * @param bw the bandwidth parameters
354  * @param up true when the interface is up
355  */
356 void egressBwClear(struct egress_if_bw * bw, bool up) {
357   bw->egressUk = 0;
358   bw->egressDk = 0;
359   bw->path_cost = 0;
360   memset(&bw->network, 0, sizeof(bw->network));
361   memset(&bw->gateway, 0, sizeof(bw->gateway));
362
363   bw->networkSet = false;
364   bw->gatewaySet = false;
365
366   egressBwCalculateCosts(bw, up);
367 }
368
369 /*
370  * Timer
371  */
372
373 /** the timer for polling the egress file for changes */
374 static struct timer_entry *egress_file_timer;
375
376 /**
377  * Timer callback to read the egress file
378  *
379  * @param unused unused
380  */
381 static void egress_file_timer_callback(void *unused __attribute__ ((unused))) {
382   if (readEgressFile(olsr_cnf->smart_gw_egress_file)) {
383     doRoutesMultiGw(true, false, GW_MULTI_CHANGE_PHASE_RUNTIME);
384   }
385 }
386
387 /*
388  * Life Cycle
389  */
390
391 /**
392  * Initialises the egress file reader
393  *
394  * @return
395  * - true upon success
396  * - false otherwise
397  */
398 bool startEgressFile(void) {
399   int r;
400
401   if (started) {
402     return true;
403   }
404
405   line = malloc(LINE_LENGTH);
406   if (!line) {
407     egressFileError(false, __LINE__, "Could not allocate a line buffer");
408     return false;
409   }
410   *line = '\0';
411
412   r = regcomp(&compiledRegexComment, regexComment, REG_EXTENDED);
413   if (r) {
414     regerror(r, &compiledRegexComment, line, LINE_LENGTH);
415     egressFileError(false, __LINE__, "Could not compile regex \"%s\" (%d = %s)", regexComment, r, line);
416
417     free(line);
418     line = NULL;
419     return false;
420   }
421
422   r = regcomp(&compiledRegexEgress, regexEgress, REG_EXTENDED);
423   if (r) {
424     regerror(r, &compiledRegexEgress, line, LINE_LENGTH);
425     egressFileError(false, __LINE__, "Could not compile regex \"%s\" (%d = %s)", regexEgress, r, line);
426
427     regfree(&compiledRegexComment);
428     free(line);
429     line = NULL;
430     return false;
431   }
432
433   memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
434   memset(&cachedStatClear.timeStamp, 0, sizeof(cachedStatClear.timeStamp));
435
436   readEgressFile(olsr_cnf->smart_gw_egress_file);
437
438   olsr_set_timer(&egress_file_timer, olsr_cnf->smart_gw_egress_file_period, 0, true, &egress_file_timer_callback, NULL, NULL);
439
440   started = true;
441   return true;
442 }
443
444 /**
445  * Cleans up the egress file reader.
446  */
447 void stopEgressFile(void) {
448   if (started) {
449     olsr_stop_timer(egress_file_timer);
450     egress_file_timer = NULL;
451
452     regfree(&compiledRegexEgress);
453     regfree(&compiledRegexComment);
454     free(line);
455
456     started = false;
457   }
458 }
459
460 /*
461  * File Reader
462  */
463
464 /** the buffer with regex matches */
465 static regmatch_t pmatch[REGEX_EGRESS_LINE_MATCH_COUNT];
466
467 static void readEgressFileClear(void) {
468   struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
469   while (egress_if) {
470     egress_if->bwPrevious = egress_if->bwCurrent;
471     egress_if->bwCostsChanged = false;
472     egress_if->bwNetworkChanged = false;
473     egress_if->bwGatewayChanged = false;
474     egress_if->bwChanged = false;
475
476     egress_if->inEgressFile = false;
477
478     egress_if = egress_if->next;
479   }
480 }
481
482 /**
483  * Read the egress file
484  *
485  * @param fileName the filename
486  * @return true to indicate changes (any egress_if->bwChanged is true)
487  */
488 static bool readEgressFile(const char * fileName) {
489   bool changed = false;
490
491   FILE * fp = NULL;
492   struct stat statBuf;
493   unsigned int lineNumber = 0;
494   ssize_t length = -1;
495   bool reportedErrorsLocal = false;
496   const char * filepath = !fileName ? DEF_GW_EGRESS_FILE : fileName;
497   void * mtim;
498   size_t line_length = LINE_LENGTH;
499
500   if (memcmp(&cachedStat.timeStamp, &cachedStatClear.timeStamp, sizeof(cachedStat.timeStamp))) {
501     /* read the file before */
502
503     if (stat(filepath, &statBuf)) {
504       /* could not stat the file */
505       memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
506       readEgressFileClear();
507       goto outerror;
508     }
509
510 #if defined(__linux__) && !defined(__ANDROID__)
511     mtim = &statBuf.st_mtim;
512 #else
513     mtim = &statBuf.st_mtime;
514 #endif
515     if (!memcmp(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp))) {
516       /* file did not change since last read */
517       return false;
518     }
519   }
520
521   fp = fopen(filepath, "r");
522   if (!fp) {
523     /* could not open the file */
524     memset(&cachedStat.timeStamp, 0, sizeof(cachedStat.timeStamp));
525     readEgressFileClear();
526     goto outerror;
527   }
528
529   /* copy 'current' egress interfaces into 'previous' field */
530   readEgressFileClear();
531
532   while ((length = getline(&line, &line_length, fp)) != -1) {
533     struct sgw_egress_if * egress_if = NULL;
534     unsigned long long uplink = DEF_EGRESS_UPLINK_KBPS;
535     unsigned long long downlink = DEF_EGRESS_DOWNLINK_KBPS;
536     unsigned long long pathCosts = DEF_EGRESS_PATH_COSTS;
537     struct olsr_ip_prefix network;
538     union olsr_ip_addr gateway;
539     bool networkSet = false;
540     bool gatewaySet = false;
541     int networkIpVersion = AF_INET;
542     int gatewayIpVersion = AF_INET;
543
544     lineNumber++;
545
546     if (!regexec(&compiledRegexComment, line, 0, NULL, 0)) {
547       /* the line is a comment */
548       continue;
549     }
550
551     memset(&network, 0, sizeof(network));
552     memset(&gateway, 0, sizeof(gateway));
553
554     stripEols(line, length);
555
556     memset(pmatch, 0, sizeof(pmatch));
557     if (regexec(&compiledRegexEgress, line, REGEX_EGRESS_LINE_MATCH_COUNT, pmatch, 0)) {
558       egressFileError(false, __LINE__, "Egress speed file line %d uses invalid syntax: line is ignored (%s)", lineNumber, line);
559       reportedErrorsLocal = true;
560       continue;
561     }
562
563     /* iface: mandatory presence, guaranteed through regex match */
564     {
565       regoff_t len = pmatch[1].rm_eo - pmatch[1].rm_so;
566       char * ifaceString = &line[pmatch[1].rm_so];
567       line[pmatch[1].rm_eo] = '\0';
568
569       if (len > IFNAMSIZ) {
570         /* interface name is too long */
571         egressFileError(false, __LINE__, "Egress speed file line %d: interface \"%s\" is too long: line is ignored", lineNumber, ifaceString);
572         reportedErrorsLocal = true;
573         continue;
574       }
575
576       egress_if = findEgressInterface(ifaceString);
577       if (!egress_if) {
578         /* not a known egress interface */
579         egressFileError(false, __LINE__, "Egress speed file line %d: interface \"%s\" is not a configured egress interface: line is ignored", lineNumber,
580             ifaceString);
581         reportedErrorsLocal = true;
582         continue;
583       }
584     }
585     assert(egress_if);
586
587     /* uplink: mandatory presence, guaranteed through regex match */
588     {
589       regoff_t len = pmatch[2].rm_eo - pmatch[2].rm_so;
590       char * uplinkString = &line[pmatch[2].rm_so];
591       line[pmatch[2].rm_eo] = '\0';
592
593       if ((len > 0) && !readULL(uplinkString, &uplink)) {
594         egressFileError(false, __LINE__, "Egress speed file line %d: uplink bandwidth \"%s\" is not a valid number: line is ignored", lineNumber, uplinkString);
595         reportedErrorsLocal = true;
596         continue;
597       }
598     }
599     uplink = MIN(uplink, MAX_SMARTGW_SPEED);
600
601     /* downlink: mandatory presence, guaranteed through regex match */
602     {
603       regoff_t len = pmatch[3].rm_eo - pmatch[3].rm_so;
604       char * downlinkString = &line[pmatch[3].rm_so];
605       line[pmatch[3].rm_eo] = '\0';
606
607       if ((len > 0) && !readULL(downlinkString, &downlink)) {
608         egressFileError(false, __LINE__, "Egress speed file line %d: downlink bandwidth \"%s\" is not a valid number: line is ignored", lineNumber,
609             downlinkString);
610         reportedErrorsLocal = true;
611         continue;
612       }
613     }
614     downlink = MIN(downlink, MAX_SMARTGW_SPEED);
615
616     /* path costs: optional presence */
617     if (pmatch[5].rm_so != -1) {
618       regoff_t len = pmatch[5].rm_eo - pmatch[5].rm_so;
619       char * pathCostsString = &line[pmatch[5].rm_so];
620       line[pmatch[5].rm_eo] = '\0';
621
622       if ((len > 0) && !readULL(pathCostsString, &pathCosts)) {
623         egressFileError(false, __LINE__, "Egress speed file line %d: path costs \"%s\" is not a valid number: line is ignored", lineNumber, pathCostsString);
624         reportedErrorsLocal = true;
625         continue;
626       }
627     }
628     pathCosts = MIN(pathCosts, UINT32_MAX);
629
630     /* network: optional presence */
631     if ((pmatch[7].rm_so != -1) && ((pmatch[7].rm_eo - pmatch[7].rm_so) > 0)) {
632       /* network is present: guarantees IP and prefix presence */
633       unsigned long long prefix_len;
634       char * networkString = &line[pmatch[8].rm_so];
635       char * prefixlenString = &line[pmatch[9].rm_so];
636       line[pmatch[8].rm_eo] = '\0';
637       line[pmatch[9].rm_eo] = '\0';
638
639       if (!readIPAddress(networkString, &network.prefix, &networkSet, &networkIpVersion)) {
640         egressFileError(false, __LINE__, "Egress speed file line %d: network IP address \"%s\" is not a valid IP address: line is ignored", lineNumber,
641             networkString);
642         reportedErrorsLocal = true;
643         continue;
644       }
645
646       if (!readULL(prefixlenString, &prefix_len)) {
647         egressFileError(false, __LINE__, "Egress speed file line %d: network prefix \"%s\" is not a valid number: line is ignored", lineNumber,
648             prefixlenString);
649         reportedErrorsLocal = true;
650         continue;
651       }
652
653       if (prefix_len > ((networkIpVersion == AF_INET) ? 32 : 128)) {
654         egressFileError(false, __LINE__, "Egress speed file line %d: network prefix \"%s\" is not in the range [0, %d]: line is ignored", lineNumber,
655             prefixlenString, ((networkIpVersion == AF_INET) ? 32 : 128));
656         reportedErrorsLocal = true;
657         continue;
658       }
659
660       network.prefix_len = prefix_len;
661     }
662
663     /* gateway: optional presence */
664     if (pmatch[11].rm_so != -1) {
665       regoff_t len = pmatch[11].rm_eo - pmatch[11].rm_so;
666       char * gatewayString = &line[pmatch[11].rm_so];
667       line[pmatch[11].rm_eo] = '\0';
668
669       if ((len > 0) && !readIPAddress(gatewayString, &gateway, &gatewaySet, &gatewayIpVersion)) {
670         egressFileError(false, __LINE__, "Egress speed file line %d: gateway IP address \"%s\" is not a valid IP address: line is ignored", lineNumber,
671             gatewayString);
672         reportedErrorsLocal = true;
673         continue;
674       }
675     }
676
677     /* check all IP versions are the same */
678     if ((networkSet && gatewaySet) && (networkIpVersion != gatewayIpVersion)) {
679       egressFileError(false, __LINE__, "Egress speed file line %d: network and gateway IP addresses must be of the same IP version: line is ignored",
680           lineNumber);
681       reportedErrorsLocal = true;
682       continue;
683     }
684
685     /* check no IPv6 */
686     if ((networkSet && networkIpVersion == AF_INET6) || //
687         (gatewaySet && gatewayIpVersion == AF_INET6)) {
688       egressFileError(false, __LINE__, "Egress speed file line %d: network and gateway IP addresses must not be IPv6 addresses: line is ignored", lineNumber);
689       reportedErrorsLocal = true;
690       continue;
691     }
692
693     /* ensure network is masked by netmask */
694     if (networkSet) {
695       /* assumes IPv4 */
696       in_addr_t mask = (network.prefix_len == 0) ? 0 : (~0U << (32 - network.prefix_len));
697       uint32_t masked = ntohl(network.prefix.v4.s_addr) & mask;
698       network.prefix.v4.s_addr = htonl(masked);
699     }
700
701     if (!uplink || !downlink) {
702       egressBwClear(&egress_if->bwCurrent, egress_if->upCurrent);
703     } else {
704       egress_if->bwCurrent.egressUk = uplink;
705       egress_if->bwCurrent.egressDk = downlink;
706       egress_if->bwCurrent.path_cost = pathCosts;
707       egress_if->bwCurrent.network = network;
708       egress_if->bwCurrent.gateway = gateway;
709
710       egress_if->bwCurrent.networkSet = networkSet;
711       egress_if->bwCurrent.gatewaySet = gatewaySet;
712
713       egressBwCalculateCosts(&egress_if->bwCurrent, egress_if->upCurrent);
714     }
715
716     egress_if->inEgressFile = true;
717   }
718
719   fclose(fp);
720   fp = NULL;
721
722 #if defined(__linux__) && !defined(__ANDROID__)
723     mtim = &statBuf.st_mtim;
724 #else
725     mtim = &statBuf.st_mtime;
726 #endif
727   memcpy(&cachedStat.timeStamp, mtim, sizeof(cachedStat.timeStamp));
728
729   reportedErrors = reportedErrorsLocal;
730
731   outerror:
732
733   /* clear absent egress interfaces and setup 'changed' status */
734   {
735     struct sgw_egress_if * egress_if = olsr_cnf->smart_gw_egress_interfaces;
736     while (egress_if) {
737       if (!egress_if->inEgressFile) {
738         egressBwClear(&egress_if->bwCurrent, egress_if->upCurrent);
739       }
740
741       egress_if->bwCostsChanged = egressBwCostsChanged(egress_if);
742       egress_if->bwNetworkChanged = egressBwNetworkChanged(egress_if);
743       egress_if->bwGatewayChanged = egressBwGatewayChanged(egress_if);
744       egress_if->bwChanged = egressBwChanged(egress_if);
745       if (egress_if->bwChanged) {
746         changed = true;
747       }
748
749       egress_if = egress_if->next;
750     }
751   }
752
753   return changed;
754 }
755
756 #endif /* __linux__ */