Put .data files into a separate directory
[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 stat;
254         char *name;
255         int err;
256         size_t size;
257         struct stats_file_header *hdr;
258
259         if (roaming == FALSE) {
260                 name = g_strdup_printf("%s/stats/%s.data", STORAGEDIR,
261                                         __connman_service_get_ident(service));
262         } else {
263                 name = g_strdup_printf("%s/stats/%s-roaming.data", STORAGEDIR,
264                                         __connman_service_get_ident(service));
265         }
266
267         file->name = name;
268         file->fd = open(name, O_RDWR | O_CREAT, 0644);
269
270         if (file->fd == -1) {
271                 connman_error("open error %s for %s",
272                                 strerror(errno), file->name);
273                 return -errno;
274         }
275
276         file->max_len = STATS_MAX_FILE_SIZE;
277
278         err = fstat(file->fd, &stat);
279         if (err < 0)
280                 return -errno;
281
282         if (stat.st_size < sysconf(_SC_PAGESIZE))
283                 size = sysconf(_SC_PAGESIZE);
284         else
285                 size = (size_t)stat.st_size;
286
287         err = stats_file_remap(file, size);
288         if (err < 0)
289                 return err;
290
291         hdr = get_hdr(file);
292
293         if (hdr->magic != MAGIC ||
294                         hdr->begin < sizeof(struct stats_file_header) ||
295                         hdr->end < sizeof(struct stats_file_header) ||
296                         hdr->begin > file->len ||
297                         hdr->end > file->len) {
298                 connman_warn("invalid file header for %s", file->name);
299
300                 hdr->magic = MAGIC;
301                 hdr->begin = sizeof(struct stats_file_header);
302                 hdr->end = sizeof(struct stats_file_header);
303         }
304
305         return 0;
306 }
307
308 static int stats_open(struct connman_service *service,
309                         struct stats_handle *handle)
310 {
311         int err;
312
313         err = stats_open_file(service, &handle->home, FALSE);
314         if (err < 0)
315                 return err;
316
317         err = stats_open_file(service, &handle->roaming, TRUE);
318         if (err < 0)
319                 return err;
320
321         return 0;
322 }
323
324 int __connman_stats_service_register(struct connman_service *service)
325 {
326         struct stats_handle *handle;
327         int err;
328
329         DBG("service %p", service);
330
331         handle = g_hash_table_lookup(stats_hash, service);
332         if (handle == NULL) {
333                 err = stats_create(service);
334
335                 if (err < 0)
336                         return err;
337
338                 handle = g_hash_table_lookup(stats_hash, service);
339         }
340
341         err = stats_open(service, handle);
342         if (err < 0)
343                 g_hash_table_remove(stats_hash, service);
344
345         return err;
346 }
347
348 void __connman_stats_service_unregister(struct connman_service *service)
349 {
350         DBG("service %p", service);
351
352         g_hash_table_remove(stats_hash, service);
353 }
354
355 int  __connman_stats_update(struct connman_service *service,
356                                 connman_bool_t roaming,
357                                 struct connman_stats_data *data)
358 {
359         struct stats_handle *handle;
360         struct stats_file *file;
361         struct stats_record *next;
362         int err;
363
364         handle = g_hash_table_lookup(stats_hash, service);
365         if (handle == NULL)
366                 return -EEXIST;
367
368         file = stats_file_get(handle, roaming);
369
370         if (file->len < file->max_len &&
371                         file->last == get_end(file)) {
372                 DBG("grow file %s", file->name);
373
374                 err = stats_file_remap(file, file->len + sysconf(_SC_PAGESIZE));
375                 if (err < 0)
376                         return err;
377         }
378
379         next = get_next(file, get_end(file));
380
381         if (next == get_begin(file))
382                 set_begin(file, get_next(file, next));
383
384         next->ts = time(NULL);
385         memcpy(&next->data, data, sizeof(struct connman_stats_data));
386
387         set_end(file, next);
388
389         return 0;
390 }
391
392 int __connman_stats_get(struct connman_service *service,
393                                 connman_bool_t roaming,
394                                 struct connman_stats_data *data)
395 {
396         struct stats_handle *handle;
397         struct stats_file *file;
398         struct stats_file_header *hdr;
399
400         handle = g_hash_table_lookup(stats_hash, service);
401         if (handle == NULL)
402                 return -EEXIST;
403
404         file = stats_file_get(handle, roaming);
405         hdr = get_hdr(file);
406
407         if (hdr->begin != hdr->end) {
408                 memcpy(data, &get_end(file)->data,
409                         sizeof(struct connman_stats_data));
410         }
411
412         return 0;
413 }
414
415 int __connman_stats_init(void)
416 {
417         DBG("");
418
419         stats_hash = g_hash_table_new_full(g_direct_hash, g_direct_equal,
420                                                         NULL, stats_free);
421
422         return 0;
423 }
424
425 void __connman_stats_cleanup(void)
426 {
427         DBG("");
428
429         g_hash_table_destroy(stats_hash);
430         stats_hash = NULL;
431 }