On device reboot enable wpa-supplicant for EAPoL.
[platform/upstream/connman.git] / src / stats.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2010-2014  BMW Car IT GmbH.
6  *
7  *  This program is free software; you can redistribute it and/or modify
8  *  it under the terms of the GNU General Public License version 2 as
9  *  published by the Free Software Foundation.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19  *
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #include <config.h>
24 #endif
25
26 #include <errno.h>
27 #include <sys/mman.h>
28 #include <sys/types.h>
29 #include <sys/stat.h>
30 #include <sys/time.h>
31 #include <fcntl.h>
32 #include <unistd.h>
33 #include <string.h>
34 #include <limits.h>
35 #include <sys/stat.h>
36
37 #include "connman.h"
38
39 #define MODE            (S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | \
40                         S_IXGRP | S_IROTH | S_IXOTH)
41
42 #ifdef TEMP_FAILURE_RETRY
43 #define TFR TEMP_FAILURE_RETRY
44 #else
45 #define TFR
46 #endif
47
48 #define MAGIC 0xFA00B916
49
50 /*
51  * Statistics counters are stored into a ring buffer which is stored
52  * into a file
53  *
54  * File properties:
55  *   The ring buffer is mmap to a file
56  *   Initially only the smallest possible amount of disk space is allocated
57  *   The files grow to the configured maximal size
58  *   The grows by _SC_PAGESIZE step size
59  *   For each service a file is created
60  *   Each file has a header where the indexes are stored
61  *
62  * Entries properties:
63  *   Each entry has a timestamp
64  *   A flag to mark if the entry is either home (0) or roaming (1) entry
65  *   The entries are fixed sized (stats_record)
66  *
67  * Ring buffer properties:
68  *   There are to indexes 'begin', 'end', 'home' and 'roaming'
69  *   'begin' points to the oldest entry
70  *   'end' points to the newest/current entry
71  *   'home' points to the current home entry
72  *   'roaming' points to the current roaming entry
73  *   If 'begin' == 'end' then the buffer is empty
74  *   If 'end' + 1 == 'begin then it's full
75  *   The ring buffer is valid in the range (begin, end]
76  *   If 'home' has the value UINT_MAX', 'home' is invalid
77  *   if 'roaming' has the value UINT_MAX', 'roaming' is invalid
78  *   'first' points to the first entry in the ring buffer
79  *   'last' points to the last entry in the ring buffer
80  *
81  * History file:
82  *   Same format as the ring buffer file
83  *   For a period of at least 2 months daily records are kept
84  *   If older, then only a monthly record is kept
85  */
86
87
88 struct stats_file_header {
89         unsigned int magic;
90         unsigned int begin;
91         unsigned int end;
92         unsigned int home;
93         unsigned int roaming;
94 };
95
96 struct stats_record {
97         time_t ts;
98         unsigned int roaming;
99         struct connman_stats_data data;
100 };
101
102 struct stats_file {
103         int fd;
104         char *name;
105         char *addr;
106         size_t len;
107         size_t max_len;
108
109         /* cached values */
110         struct stats_record *first;
111         struct stats_record *last;
112         struct stats_record *home;
113         struct stats_record *roaming;
114
115         /* history */
116         char *history_name;
117         int account_period_offset;
118 };
119
120 struct stats_iter {
121         struct stats_file *file;
122         struct stats_record *begin;
123         struct stats_record *end;
124         struct stats_record *it;
125 };
126
127 static GHashTable *stats_hash = NULL;
128
129 static struct stats_file_header *get_hdr(struct stats_file *file)
130 {
131         return (struct stats_file_header *)file->addr;
132 }
133
134 static struct stats_record *get_begin(struct stats_file *file)
135 {
136         unsigned int off = get_hdr(file)->begin;
137
138         return (struct stats_record *)(file->addr + off);
139 }
140
141 static struct stats_record *get_end(struct stats_file *file)
142 {
143         unsigned int off = get_hdr(file)->end;
144
145         return (struct stats_record *)(file->addr + off);
146 }
147
148 static struct stats_record *get_home(struct stats_file *file)
149 {
150         struct stats_file_header *hdr;
151
152         hdr = get_hdr(file);
153
154         if (hdr->home == UINT_MAX)
155                 return NULL;
156
157         return (struct stats_record *)(file->addr + hdr->home);
158 }
159
160 static struct stats_record *get_roaming(struct stats_file *file)
161 {
162         struct stats_file_header *hdr;
163
164         hdr = get_hdr(file);
165
166         if (hdr->roaming == UINT_MAX)
167                 return NULL;
168
169         return (struct stats_record *)(file->addr + hdr->roaming);
170 }
171
172 static void set_end(struct stats_file *file, struct stats_record *end)
173 {
174         struct stats_file_header *hdr;
175
176         hdr = get_hdr(file);
177         hdr->end = (char *)end - file->addr;
178 }
179
180 static void set_home(struct stats_file *file, struct stats_record *home)
181 {
182         struct stats_file_header *hdr;
183
184         hdr = get_hdr(file);
185         hdr->home = (char *)home - file->addr;
186 }
187
188 static void set_roaming(struct stats_file *file, struct stats_record *roaming)
189 {
190         struct stats_file_header *hdr;
191
192         hdr = get_hdr(file);
193         hdr->roaming = (char *)roaming - file->addr;
194 }
195
196 static struct stats_record *get_next(struct stats_file *file,
197                                         struct stats_record *cur)
198 {
199         cur++;
200
201         if (cur > file->last)
202                 cur = file->first;
203
204         return cur;
205 }
206
207 static struct stats_record *get_iterator_begin(struct stats_file *file)
208 {
209         return get_next(file, get_begin(file));
210 }
211
212 static struct stats_record *get_iterator_end(struct stats_file *file)
213 {
214         return get_next(file, get_end(file));
215 }
216
217 static void stats_free(gpointer user_data)
218 {
219         struct stats_file *file = user_data;
220
221         if (!file)
222                 return;
223
224         msync(file->addr, file->len, MS_SYNC);
225
226         munmap(file->addr, file->len);
227         file->addr = NULL;
228
229         close(file->fd);
230         file->fd = -1;
231
232         g_free(file->history_name);
233         file->history_name = NULL;
234
235         g_free(file->name);
236         file->name = NULL;
237
238         g_free(file);
239 }
240
241 static void update_first(struct stats_file *file)
242 {
243         file->first = (struct stats_record *)
244                         (file->addr + sizeof(struct stats_file_header));
245 }
246
247 static void update_last(struct stats_file *file)
248 {
249         unsigned int max_entries;
250
251         max_entries = (file->len - sizeof(struct stats_file_header)) /
252                         sizeof(struct stats_record);
253         file->last = file->first + max_entries - 1;
254 }
255
256 static void update_home(struct stats_file *file)
257 {
258         file->home = get_home(file);
259 }
260
261 static void update_roaming(struct stats_file *file)
262 {
263         file->roaming = get_roaming(file);
264 }
265
266 static void stats_file_update_cache(struct stats_file *file)
267 {
268         update_first(file);
269         update_last(file);
270         update_home(file);
271         update_roaming(file);
272 }
273
274 static int stats_file_remap(struct stats_file *file, size_t size)
275 {
276         size_t page_size, new_size;
277         void *addr;
278         int err;
279
280         DBG("file %p size %zu addr %p len %zu", file, size, file->addr,
281                 file->len);
282
283         page_size = sysconf(_SC_PAGESIZE);
284         new_size = (size + page_size - 1) & ~(page_size - 1);
285
286         err = ftruncate(file->fd, new_size);
287         if (err < 0) {
288                 connman_error("ftrunctate error %s for %s",
289                                 strerror(errno), file->name);
290                 return -errno;
291         }
292
293         if (!file->addr) {
294                 /*
295                  * Though the buffer is not shared between processes, we still
296                  * have to take MAP_SHARED because MAP_PRIVATE does not
297                  * guarantee that writes will hit the file without an explicit
298                  * call to munmap or msync. For more details please read the
299                  * mmap man pages.
300                  */
301                 addr = mmap(NULL, new_size, PROT_READ | PROT_WRITE,
302                                 MAP_SHARED, file->fd, 0);
303         } else {
304                 addr = mremap(file->addr, file->len, new_size, MREMAP_MAYMOVE);
305         }
306
307         if (addr == MAP_FAILED) {
308                 connman_error("mmap error %s for %s",
309                                 strerror(errno), file->name);
310                 if (errno == EINVAL) {
311                         connman_error("%s might be on a file system, such as "
312                                         "JFFS2, that does not allow shared "
313                                         "writable mappings.", file->name);
314                 }
315                 return -errno;
316         }
317
318         file->addr = addr;
319         file->len = new_size;
320
321         stats_file_update_cache(file);
322
323         return 0;
324 }
325
326 static int stats_open(struct stats_file *file,
327                         const char *name)
328 {
329         DBG("file %p name %s", file, name);
330
331         file->name = g_strdup(name);
332
333         file->fd = TFR(open(file->name, O_RDWR | O_CREAT | O_CLOEXEC, 0644));
334         if (file->fd < 0) {
335                 connman_error("open error %s for %s",
336                                 strerror(errno), file->name);
337                 g_free(file->name);
338                 file->name = NULL;
339                 return -errno;
340         }
341
342         return 0;
343 }
344
345 static int stats_open_temp(struct stats_file *file)
346 {
347         file->name = g_strdup_printf("%s/stats.XXXXXX.tmp",
348                                         STORAGEDIR);
349         file->fd = g_mkstemp_full(file->name, O_RDWR | O_CREAT, 0644);
350         if (file->fd < 0) {
351                 connman_error("create temporary file error %s for %s",
352                                 strerror(errno), file->name);
353                 g_free(file->name);
354                 file->name = NULL;
355                 return -errno;
356         }
357
358         return 0;
359 }
360
361 static int stats_file_setup(struct stats_file *file)
362 {
363         struct stats_file_header *hdr;
364         struct stat st;
365         size_t size = 0;
366         int err;
367
368         DBG("file %p fd %d name %s", file, file->fd, file->name);
369
370         err = fstat(file->fd, &st);
371         if (err < 0) {
372                 connman_error("fstat error %s for %s\n",
373                         strerror(errno), file->name);
374
375                 close(file->fd);
376                 file->fd = -1;
377                 g_free(file->name);
378                 file->name = NULL;
379
380                 return -errno;
381         }
382
383         size = (size_t)st.st_size;
384         file->max_len = STATS_MAX_FILE_SIZE;
385
386         if (size < (size_t)sysconf(_SC_PAGESIZE))
387                 size = sysconf(_SC_PAGESIZE);
388
389         err = stats_file_remap(file, size);
390         if (err < 0) {
391                 close(file->fd);
392                 file->fd = -1;
393                 g_free(file->name);
394                 file->name = NULL;
395
396                 return err;
397         }
398
399         hdr = get_hdr(file);
400
401         if (hdr->magic != MAGIC ||
402                         hdr->begin < sizeof(struct stats_file_header) ||
403                         hdr->end < sizeof(struct stats_file_header) ||
404                         hdr->home < sizeof(struct stats_file_header) ||
405                         hdr->roaming < sizeof(struct stats_file_header) ||
406                         hdr->begin > file->len ||
407                         hdr->end > file->len) {
408                 hdr->magic = MAGIC;
409                 hdr->begin = sizeof(struct stats_file_header);
410                 hdr->end = sizeof(struct stats_file_header);
411                 hdr->home = UINT_MAX;
412                 hdr->roaming = UINT_MAX;
413
414                 stats_file_update_cache(file);
415         }
416
417         return 0;
418 }
419
420
421 static struct stats_record *get_next_record(struct stats_iter *iter)
422 {
423         if (iter->it != iter->end) {
424                 struct stats_record *tmp;
425
426                 tmp = iter->it;
427                 iter->it = get_next(iter->file, iter->it);
428
429                 return tmp;
430         }
431
432         return NULL;
433 }
434
435 static int append_record(struct stats_file *file,
436                                 struct stats_record *rec)
437 {
438         struct stats_record *cur, *next;
439         int err;
440
441         if (file->last == get_end(file)) {
442                 err = stats_file_remap(file, file->len +
443                                         sysconf(_SC_PAGESIZE));
444                 if (err < 0)
445                         return err;
446
447                 stats_file_update_cache(file);
448         }
449
450         cur = get_end(file);
451         next = get_next(file, cur);
452
453         memcpy(next, rec, sizeof(struct stats_record));
454
455         set_end(file, next);
456
457         return 0;
458 }
459
460 static struct stats_record *process_file(struct stats_iter *iter,
461                                         struct stats_file *temp_file,
462                                         struct stats_record *cur,
463                                         GDate *date_change_step_size,
464                                         int account_period_offset)
465 {
466         struct stats_record *home, *roaming;
467         struct stats_record *next;
468
469         home = NULL;
470         roaming = NULL;
471
472         if (!cur)
473                 cur = get_next_record(iter);
474         next = get_next_record(iter);
475
476         while (next) {
477                 GDate date_cur;
478                 GDate date_next;
479                 bool append;
480
481                 append = false;
482
483                 if (cur->roaming)
484                         roaming = cur;
485                 else
486                         home = cur;
487
488                 g_date_set_time_t(&date_cur, cur->ts);
489                 g_date_set_time_t(&date_next, next->ts);
490
491                 if (g_date_compare(&date_cur, date_change_step_size) < 0) {
492                         /* month period size */
493                         GDateDay day_cur, day_next;
494                         GDateMonth month_cur, month_next;
495
496                         month_cur = g_date_get_month(&date_cur);
497                         month_next = g_date_get_month(&date_next);
498
499                         day_cur = g_date_get_day(&date_cur);
500                         day_next = g_date_get_day(&date_next);
501
502                         if (day_cur == day_next && month_cur != month_next) {
503                                 append = true;
504                         } else if (day_cur < account_period_offset &&
505                                         day_next >= account_period_offset) {
506                                 append = true;
507                         }
508                 } else {
509                         /* day period size */
510                         if (g_date_days_between(&date_cur, &date_next) > 0)
511                                 append = true;
512                 }
513
514                 if (append) {
515                         if (home) {
516                                 append_record(temp_file, home);
517                                 home = NULL;
518                         }
519
520                         if (roaming) {
521                                 append_record(temp_file, roaming);
522                                 roaming = NULL;
523                         }
524                 }
525
526                 cur = next;
527                 next = get_next_record(iter);
528         }
529
530         return cur;
531 }
532
533 static int summarize(struct stats_file *data_file,
534                         struct stats_file *history_file,
535                         struct stats_file *temp_file)
536 {
537         struct stats_iter data_iter;
538         struct stats_iter history_iter;
539         struct stats_record *cur, *next;
540
541         GDate today, date_change_step_size;
542
543         /*
544          * First calculate the date when switch from monthly
545          * accounting period size to daily size
546          */
547         g_date_set_time_t(&today, time(NULL));
548
549         date_change_step_size = today;
550         if (g_date_get_day(&today) - data_file->account_period_offset >= 0)
551                 g_date_subtract_months(&date_change_step_size, 2);
552         else
553                 g_date_subtract_months(&date_change_step_size, 3);
554
555         g_date_set_day(&date_change_step_size,
556                         data_file->account_period_offset);
557
558
559         /* Now process history file */
560         cur = NULL;
561
562         if (history_file) {
563                 history_iter.file = history_file;
564                 history_iter.begin = get_iterator_begin(history_iter.file);
565                 history_iter.end = get_iterator_end(history_iter.file);
566                 history_iter.it = history_iter.begin;
567
568                 cur = process_file(&history_iter, temp_file, NULL,
569                                         &date_change_step_size,
570                                         data_file->account_period_offset);
571         }
572
573         data_iter.file = data_file;
574         data_iter.begin = get_iterator_begin(data_iter.file);
575         data_iter.end = get_iterator_end(data_iter.file);
576         data_iter.it = data_iter.begin;
577
578         /*
579          * Ensure date_file records are newer than the history_file
580          * record
581          */
582         if (cur) {
583                 next = get_next_record(&data_iter);
584                 while (next && cur->ts > next->ts)
585                         next = get_next_record(&data_iter);
586         }
587
588         /* And finally process the new data records */
589         cur = process_file(&data_iter, temp_file, cur,
590                                 &date_change_step_size,
591                                 data_file->account_period_offset);
592
593         if (cur)
594                 append_record(temp_file, cur);
595
596         return 0;
597 }
598
599 static void stats_file_unmap(struct stats_file *file)
600 {
601         msync(file->addr, file->len, MS_SYNC);
602         munmap(file->addr, file->len);
603         file->addr = NULL;
604 }
605
606 static void stats_file_cleanup(struct stats_file *file)
607 {
608         file->fd = -1;
609         g_free(file->name);
610         file->name = NULL;
611 }
612
613 static int stats_file_close_swap(struct stats_file *history_file,
614                                         struct stats_file *temp_file)
615 {
616         int err;
617
618         stats_file_unmap(history_file);
619         stats_file_unmap(temp_file);
620
621         close(temp_file->fd);
622
623         unlink(history_file->name);
624
625         err = link(temp_file->name, history_file->name);
626
627         unlink(temp_file->name);
628
629         close(history_file->fd);
630
631         stats_file_cleanup(history_file);
632         stats_file_cleanup(temp_file);
633
634         return err;
635 }
636
637 static int stats_file_history_update(struct stats_file *data_file)
638 {
639         struct stats_file _history_file, *history_file;
640         struct stats_file _temp_file, *temp_file;
641         int err;
642
643         history_file = &_history_file;
644         temp_file = &_temp_file;
645
646         bzero(history_file, sizeof(struct stats_file));
647         bzero(temp_file, sizeof(struct stats_file));
648
649         history_file->fd = -1;
650         temp_file->fd = -1;
651
652         err = stats_open(history_file, data_file->history_name);
653         if (err < 0)
654                 return err;
655         stats_file_setup(history_file);
656
657         err = stats_open_temp(temp_file);
658         if (err < 0) {
659                 stats_free(history_file);
660                 return err;
661         }
662         stats_file_setup(temp_file);
663
664         summarize(data_file, history_file, temp_file);
665
666         err = stats_file_close_swap(history_file, temp_file);
667
668         return err;
669 }
670
671 int __connman_stats_service_register(struct connman_service *service)
672 {
673         struct stats_file *file;
674         char *name, *dir;
675         int err;
676
677         DBG("service %p", service);
678
679         dir = g_strdup_printf("%s/%s", STORAGEDIR,
680                                 connman_service_get_identifier(service));
681
682         /* If the dir doesn't exist, create it */
683         if (!g_file_test(dir, G_FILE_TEST_IS_DIR)) {
684                 if (mkdir(dir, MODE) < 0) {
685                         if (errno != EEXIST) {
686                                 g_free(dir);
687
688                                 err = -errno;
689                                 goto err;
690                         }
691                 }
692         }
693
694         g_free(dir);
695         file = g_hash_table_lookup(stats_hash, service);
696         if (!file) {
697                 file = g_try_new0(struct stats_file, 1);
698                 if (!file)
699                         return -ENOMEM;
700
701                 file->fd = -1;
702
703                 g_hash_table_insert(stats_hash, service, file);
704         } else {
705                 return -EALREADY;
706         }
707
708         name = g_strdup_printf("%s/%s/data", STORAGEDIR,
709                                 connman_service_get_identifier(service));
710         file->history_name = g_strdup_printf("%s/%s/history", STORAGEDIR,
711                                 connman_service_get_identifier(service));
712
713         /* TODO: Use a global config file instead of hard coded value. */
714         file->account_period_offset = 1;
715
716         err = stats_open(file, name);
717         g_free(name);
718         if (err < 0)
719                 goto err;
720
721         err = stats_file_setup(file);
722         if (err < 0)
723                 goto err;
724
725         return 0;
726
727 err:
728         g_hash_table_remove(stats_hash, service);
729
730         return err;
731 }
732
733 void __connman_stats_service_unregister(struct connman_service *service)
734 {
735         DBG("service %p", service);
736
737         g_hash_table_remove(stats_hash, service);
738 }
739
740 int  __connman_stats_update(struct connman_service *service,
741                                 bool roaming,
742                                 struct connman_stats_data *data)
743 {
744         struct stats_file *file;
745         struct stats_record *next;
746         int err;
747
748         file = g_hash_table_lookup(stats_hash, service);
749         if (!file)
750                 return -EEXIST;
751
752         if (file->len < file->max_len &&
753                         file->last == get_end(file)) {
754                 DBG("grow file %s", file->name);
755
756                 err = stats_file_remap(file, file->len + sysconf(_SC_PAGESIZE));
757                 if (err < 0)
758                         return err;
759         }
760
761         next = get_next(file, get_end(file));
762
763         if (next == get_begin(file)) {
764                 DBG("ring buffer is full, update history file");
765
766                 if (stats_file_history_update(file) < 0) {
767                         connman_warn("history file update failed %s",
768                                         file->history_name);
769                 }
770         }
771
772         next->ts = time(NULL);
773         next->roaming = roaming;
774         memcpy(&next->data, data, sizeof(struct connman_stats_data));
775
776         if (!roaming)
777                 set_home(file, next);
778         else
779                 set_roaming(file, next);
780
781         set_end(file, next);
782
783         return 0;
784 }
785
786 int __connman_stats_get(struct connman_service *service,
787                                 bool roaming,
788                                 struct connman_stats_data *data)
789 {
790         struct stats_file *file;
791         struct stats_record *rec;
792
793         file = g_hash_table_lookup(stats_hash, service);
794         if (!file)
795                 return -EEXIST;
796
797         if (!roaming)
798                 rec = file->home;
799         else
800                 rec = file->roaming;
801
802         if (rec) {
803                 memcpy(data, &rec->data,
804                         sizeof(struct connman_stats_data));
805         }
806
807         return 0;
808 }
809
810 int __connman_stats_init(void)
811 {
812         DBG("");
813
814         stats_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
815                                                         NULL, stats_free);
816
817         return 0;
818 }
819
820 void __connman_stats_cleanup(void)
821 {
822         DBG("");
823
824         g_hash_table_destroy(stats_hash);
825         stats_hash = NULL;
826 }