Fix "invalid file header"
[framework/connectivity/connman.git] / src / stats.c
1 /*
2  *
3  *  Connection Manager
4  *
5  *  Copyright (C) 2010  BMW Car IT GmbH. All rights reserved.
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 #define _GNU_SOURCE
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
35 #include "connman.h"
36
37 #define MAGIC 0xFA00B915
38
39 /*
40  * Statistics counters are stored into a ring buffer which is stored
41  * into a file
42  *
43  * File properties:
44  *   The ring buffer is mmap to a file
45  *   Initialy only the smallest possible amount of disk space is allocated
46  *   The files grow to the configured maximal size
47  *   The grows by _SC_PAGESIZE step size
48  *   For each service home and service roaming counter set a file is created
49  *   Each file has a header where the indexes are stored
50  *
51  * Entries properties:
52  *   The entries are fixed sized (stats_record)
53  *   Each entry has a timestamp
54  *
55  * Ring buffer properties:
56  *   There are to indexes 'begin' and 'end'
57  *   'begin' points to the oldest entry
58  *   'end' points to the newest/current entry
59  *   If 'begin' == 'end' then the buffer is empty
60  *   If 'end' + 1 == 'begin then it's full
61  *   The ring buffer is valid in the range (begin, end]
62  *   'first' points to the first entry in the ring buffer
63  *   'last' points to the last entry in the ring buffer
64  */
65
66 struct stats_file_header {
67         unsigned int magic;
68         unsigned int begin;
69         unsigned int end;
70 };
71
72 struct stats_record {
73         time_t ts;
74         struct connman_stats_data data;
75 };
76
77 struct stats_file {
78         int fd;
79         char *name;
80         char *addr;
81         size_t len;
82         size_t max_len;
83
84         /* cached values */
85         struct stats_record *first;
86         struct stats_record *last;
87 };
88
89 struct stats_handle {
90         struct stats_file home;
91         struct stats_file roaming;
92 };
93
94 GHashTable *stats_hash = NULL;
95
96 static struct stats_file_header *get_hdr(struct stats_file *file)
97 {
98         return (struct stats_file_header *)file->addr;
99 }
100
101 static struct stats_record *get_begin(struct stats_file *file)
102 {
103         unsigned int off = get_hdr(file)->begin;
104
105         return (struct stats_record *)(file->addr + off);
106 }
107
108 static struct stats_record *get_end(struct stats_file *file)
109 {
110         unsigned int off = get_hdr(file)->end;
111
112         return (struct stats_record *)(file->addr + off);
113 }
114
115 static void set_begin(struct stats_file *file, struct stats_record *begin)
116 {
117         struct stats_file_header *hdr;
118
119         hdr = get_hdr(file);
120         hdr->begin = (char *)begin - file->addr;
121 }
122
123 static void set_end(struct stats_file *file, struct stats_record *end)
124 {
125         struct stats_file_header *hdr;
126
127         hdr = get_hdr(file);
128         hdr->end = (char *)end - file->addr;
129 }
130
131 static struct stats_record *get_next(struct stats_file *file,
132                                         struct stats_record *cur)
133 {
134         cur++;
135
136         if (cur > file->last)
137                 cur = file->first;
138
139         return cur;
140 }
141
142 static struct stats_file *stats_file_get(struct stats_handle *handle,
143                                                 connman_bool_t roaming)
144 {
145         struct stats_file *file;
146
147         if (roaming == FALSE)
148                 file = &handle->home;
149         else
150                 file = &handle->roaming;
151
152         return file;
153 }
154
155 static void stats_close_file(struct stats_file *file)
156 {
157         msync(file->addr, file->len, MS_SYNC);
158
159         munmap(file->addr, file->len);
160         file->addr = NULL;
161
162         close(file->fd);
163         file->fd = -1;
164
165         g_free(file->name);
166 }
167
168 static void stats_free(gpointer user_data)
169 {
170         struct stats_handle *handle = user_data;
171
172         stats_close_file(&handle->home);
173         stats_close_file(&handle->roaming);
174
175         g_free(handle);
176 }
177
178 static int stats_create(struct connman_service *service)
179 {
180         struct stats_handle *handle;
181
182         handle = g_try_new0(struct stats_handle, 1);
183         if (handle == NULL)
184                 return -ENOMEM;
185
186         g_hash_table_insert(stats_hash, service, handle);
187
188         return 0;
189 }
190
191 static void update_first(struct stats_file *file)
192 {
193         file->first = (struct stats_record *)
194                         (file->addr + sizeof(struct stats_file_header));
195 }
196
197 static void update_last(struct stats_file *file)
198 {
199         unsigned int max_entries;
200
201         max_entries = (file->len - sizeof(struct stats_file_header)) /
202                         sizeof(struct stats_record);
203         file->last = file->first + max_entries - 1;
204 }
205
206 static void stats_file_update_cache(struct stats_file *file)
207 {
208         update_first(file);
209         update_last(file);
210 }
211
212 static int stats_file_remap(struct stats_file *file, size_t size)
213 {
214         size_t page_size, new_size;
215         void *addr;
216         int err;
217
218         page_size = sysconf(_SC_PAGESIZE);
219         new_size = (size + page_size - 1) & ~(page_size - 1);
220
221         err = ftruncate(file->fd, new_size);
222         if (err < 0) {
223                 connman_error("ftrunctate error %s for %s",
224                                 strerror(errno), file->name);
225                 return -errno;
226         }
227
228         if (file->addr == NULL) {
229                 addr = mmap(NULL, new_size, PROT_READ | PROT_WRITE,
230                                 MAP_SHARED, file->fd, 0);
231         } else {
232                 addr = mremap(file->addr, file->len, new_size, MREMAP_MAYMOVE);
233         }
234
235         if (addr == MAP_FAILED) {
236                 connman_error("mmap error %s for %s",
237                                 strerror(errno), file->name);
238                 return -errno;
239         }
240
241         file->addr = addr;
242         file->len = new_size;
243
244         stats_file_update_cache(file);
245
246         return 0;
247 }
248
249 static int stats_open_file(struct connman_service *service,
250                                 struct stats_file *file,
251                                 connman_bool_t roaming)
252 {
253         struct stat st;
254         char *name;
255         int err;
256         size_t size;
257         struct stats_file_header *hdr;
258         connman_bool_t new_file = FALSE;
259
260         if (roaming == FALSE) {
261                 name = g_strdup_printf("%s/stats/%s.data", STORAGEDIR,
262                                         __connman_service_get_ident(service));
263         } else {
264                 name = g_strdup_printf("%s/stats/%s-roaming.data", STORAGEDIR,
265                                         __connman_service_get_ident(service));
266         }
267
268         file->name = name;
269
270         err = stat(file->name, &st);
271         if (err < 0) {
272                 /* according documentation the only possible error is ENOENT */
273                 new_file = TRUE;
274         }
275
276         file->fd = open(name, O_RDWR | O_CREAT, 0644);
277
278         if (file->fd < 0) {
279                 connman_error("open error %s for %s",
280                                 strerror(errno), file->name);
281                 return -errno;
282         }
283
284         file->max_len = STATS_MAX_FILE_SIZE;
285
286         if (st.st_size < sysconf(_SC_PAGESIZE))
287                 size = sysconf(_SC_PAGESIZE);
288         else
289                 size = st.st_size;
290
291         err = stats_file_remap(file, size);
292         if (err < 0)
293                 return err;
294
295         hdr = get_hdr(file);
296
297         if (hdr->magic != MAGIC ||
298                         hdr->begin < sizeof(struct stats_file_header) ||
299                         hdr->end < sizeof(struct stats_file_header) ||
300                         hdr->begin > file->len ||
301                         hdr->end > file->len) {
302                 if (new_file == FALSE) {
303                         /*
304                          * A newly created file can't have a correct
305                          * header so we only warn if the file already
306                          * existed and doesn't have a proper
307                          * header.
308                          */
309                         connman_warn("invalid file header for %s", file->name);
310                 }
311
312                 hdr->magic = MAGIC;
313                 hdr->begin = sizeof(struct stats_file_header);
314                 hdr->end = sizeof(struct stats_file_header);
315         }
316
317         return 0;
318 }
319
320 static int stats_open(struct connman_service *service,
321                         struct stats_handle *handle)
322 {
323         int err;
324
325         err = stats_open_file(service, &handle->home, FALSE);
326         if (err < 0)
327                 return err;
328
329         err = stats_open_file(service, &handle->roaming, TRUE);
330         if (err < 0)
331                 return err;
332
333         return 0;
334 }
335
336 int __connman_stats_service_register(struct connman_service *service)
337 {
338         struct stats_handle *handle;
339         int err;
340
341         DBG("service %p", service);
342
343         handle = g_hash_table_lookup(stats_hash, service);
344         if (handle == NULL) {
345                 err = stats_create(service);
346
347                 if (err < 0)
348                         return err;
349
350                 handle = g_hash_table_lookup(stats_hash, service);
351         }
352
353         err = stats_open(service, handle);
354         if (err < 0)
355                 g_hash_table_remove(stats_hash, service);
356
357         return err;
358 }
359
360 void __connman_stats_service_unregister(struct connman_service *service)
361 {
362         DBG("service %p", service);
363
364         g_hash_table_remove(stats_hash, service);
365 }
366
367 int  __connman_stats_update(struct connman_service *service,
368                                 connman_bool_t roaming,
369                                 struct connman_stats_data *data)
370 {
371         struct stats_handle *handle;
372         struct stats_file *file;
373         struct stats_record *next;
374         int err;
375
376         handle = g_hash_table_lookup(stats_hash, service);
377         if (handle == NULL)
378                 return -EEXIST;
379
380         file = stats_file_get(handle, roaming);
381
382         if (file->len < file->max_len &&
383                         file->last == get_end(file)) {
384                 DBG("grow file %s", file->name);
385
386                 err = stats_file_remap(file, file->len + sysconf(_SC_PAGESIZE));
387                 if (err < 0)
388                         return err;
389         }
390
391         next = get_next(file, get_end(file));
392
393         if (next == get_begin(file))
394                 set_begin(file, get_next(file, next));
395
396         next->ts = time(NULL);
397         memcpy(&next->data, data, sizeof(struct connman_stats_data));
398
399         set_end(file, next);
400
401         return 0;
402 }
403
404 int __connman_stats_get(struct connman_service *service,
405                                 connman_bool_t roaming,
406                                 struct connman_stats_data *data)
407 {
408         struct stats_handle *handle;
409         struct stats_file *file;
410         struct stats_file_header *hdr;
411
412         handle = g_hash_table_lookup(stats_hash, service);
413         if (handle == NULL)
414                 return -EEXIST;
415
416         file = stats_file_get(handle, roaming);
417         hdr = get_hdr(file);
418
419         if (hdr->begin != hdr->end) {
420                 memcpy(data, &get_end(file)->data,
421                         sizeof(struct connman_stats_data));
422         }
423
424         return 0;
425 }
426
427 int __connman_stats_init(void)
428 {
429         DBG("");
430
431         stats_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
432                                                         NULL, stats_free);
433
434         return 0;
435 }
436
437 void __connman_stats_cleanup(void)
438 {
439         DBG("");
440
441         g_hash_table_destroy(stats_hash);
442         stats_hash = NULL;
443 }