c11 safe string handling support
[vpp.git] / src / vppinfra / maplog.c
1 /*
2  * Copyright (c) 2017 Cisco and/or its affiliates.
3  * Licensed under the Apache License, Version 2.0 (the "License");
4  * you may not use this file except in compliance with the License.
5  * You may obtain a copy of the License at:
6  *
7  *     http://www.apache.org/licenses/LICENSE-2.0
8  *
9  * Unless required by applicable law or agreed to in writing, software
10  * distributed under the License is distributed on an "AS IS" BASIS,
11  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12  * See the License for the specific language governing permissions and
13  * limitations under the License.
14  */
15
16 #include <vppinfra/maplog.h>
17
18 /**
19  * @brief Initialize a maplog object
20  *
21  * Compute record and file size parameters
22  * Create and map two log segments to seed the process
23  *
24  * @param[in/out] a   init args structure
25  * @return    0 => success, <0 => failure
26  */
27 int
28 clib_maplog_init (clib_maplog_init_args_t * a)
29 {
30   int i, fd, limit;
31   int rv = 0;
32   u8 zero = 0;
33   u32 record_size_in_cache_lines;
34   u64 file_size_in_records;
35   clib_maplog_main_t *mm;
36   clib_maplog_header_t _h, *h = &_h;
37
38   ASSERT (a && a->mm);
39   mm = a->mm;
40
41   /* Already initialized? */
42   if (mm->flags & CLIB_MAPLOG_FLAG_INIT)
43     return (-2);
44
45   clib_memset (mm, 0, sizeof (*mm));
46
47   record_size_in_cache_lines =
48     (a->record_size_in_bytes + CLIB_CACHE_LINE_BYTES -
49      1) / CLIB_CACHE_LINE_BYTES;
50
51   file_size_in_records = a->file_size_in_bytes
52     / (record_size_in_cache_lines * CLIB_CACHE_LINE_BYTES);
53
54   /* Round up file size in records to a power of 2, for speed... */
55   mm->log2_file_size_in_records = max_log2 (file_size_in_records);
56   file_size_in_records = 1ULL << (mm->log2_file_size_in_records);
57   a->file_size_in_bytes = file_size_in_records * record_size_in_cache_lines
58     * CLIB_CACHE_LINE_BYTES;
59
60   mm->file_basename = format (0, "%s", a->file_basename);
61   if (vec_len (mm->file_basename) > ARRAY_LEN (h->file_basename))
62     {
63       vec_free (mm->file_basename);
64       return -11;
65     }
66
67   mm->file_size_in_records = file_size_in_records;
68   mm->flags |= CLIB_MAPLOG_FLAG_INIT;
69   mm->record_size_in_cachelines = record_size_in_cache_lines;
70   limit = 2;
71   if (a->maplog_is_circular)
72     {
73       mm->log2_file_size_in_records = 63;
74       mm->flags |= CLIB_MAPLOG_FLAG_CIRCULAR;
75       limit = 1;
76     }
77
78   /*
79    * Map the one and only file for a circular log,
80    * two files for a normal log.
81    */
82   for (i = 0; i < limit; i++)
83     {
84       mm->filenames[i] = format (0, "%v_%d", mm->file_basename,
85                                  mm->current_file_index++);
86       vec_add1 (mm->filenames[i], 0);
87
88       fd = open ((char *) mm->filenames[i], O_CREAT | O_RDWR | O_TRUNC, 0600);
89       if (fd < 0)
90         {
91           rv = -3;
92           goto fail;
93         }
94
95       if (lseek (fd, a->file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1)
96         {
97           rv = -4;
98           goto fail;
99         }
100       if (write (fd, &zero, 1) != 1)
101         {
102           rv = -5;
103           goto fail;
104         }
105
106       mm->file_baseva[i] = mmap (0, a->file_size_in_bytes,
107                                  PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
108       if (mm->file_baseva[i] == (u8 *) MAP_FAILED)
109         {
110           clib_unix_warning ("mmap");
111           goto fail;
112         }
113       (void) close (fd);
114     }
115
116   clib_memset (h, 0, sizeof (*h));
117   h->maplog_major_version = MAPLOG_MAJOR_VERSION;
118   h->maplog_minor_version = MAPLOG_MINOR_VERSION;
119   h->maplog_patch_version = MAPLOG_PATCH_VERSION;
120   h->application_id = a->application_id;
121   h->application_major_version = a->application_major_version;
122   h->application_minor_version = a->application_minor_version;
123   h->application_patch_version = a->application_patch_version;
124   h->record_size_in_cachelines = record_size_in_cache_lines;
125   h->cacheline_size = CLIB_CACHE_LINE_BYTES;
126   h->file_size_in_records = file_size_in_records;
127   h->number_of_records = ~0ULL;
128   h->number_of_files = ~0ULL;
129   h->maplog_flag_circular = a->maplog_is_circular;
130   memcpy (h->file_basename, mm->file_basename, vec_len (mm->file_basename));
131
132   mm->header_filename = format (0, "%v_header", mm->file_basename);
133   vec_add1 (mm->header_filename, 0);
134
135   fd = open ((char *) mm->header_filename, O_CREAT | O_RDWR | O_TRUNC, 0600);
136   if (fd < 0)
137     {
138       clib_unix_warning ("header create");
139       rv = -6;
140       goto fail;
141     }
142   rv = write (fd, h, sizeof (*h));
143   if (rv != sizeof (*h))
144     {
145       clib_unix_warning ("header write");
146       rv = -7;
147       goto fail;
148     }
149   (void) close (fd);
150   return 0;
151
152 fail:
153   if (fd >= 0)
154     (void) close (fd);
155
156   for (i = 0; i < limit; i++)
157     {
158       if (mm->file_baseva[i])
159         (void) munmap ((u8 *) mm->file_baseva[i], a->file_size_in_bytes);
160       if (mm->filenames[i])
161         (void) unlink ((char *) mm->filenames[i]);
162       vec_free (mm->filenames[i]);
163     }
164   if (mm->header_filename)
165     {
166       (void) unlink ((char *) mm->header_filename);
167       vec_free (mm->header_filename);
168     }
169   return rv;
170 }
171
172 /* slow path: unmap a full log segment, and replace it */
173
174 u8 *
175 _clib_maplog_get_entry_slowpath (clib_maplog_main_t * mm, u64 my_record_index)
176 {
177   int fd;
178   u8 *rv;
179   u8 zero = 0;
180   u32 unmap_index = (mm->current_file_index) & 1;
181   u64 file_size_in_bytes = mm->file_size_in_records
182     * mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;
183
184   /* This should never happen */
185   ASSERT ((mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) == 0);
186
187   /*
188    * Kill some time by calling format before we make the previous log
189    * segment disappear. Obviously it won't do to call clib_maplog_get_entry(),
190    * wait 100ms, and then fill in the log entry.
191    */
192   vec_reset_length (mm->filenames[unmap_index]);
193   mm->filenames[unmap_index] = format (mm->filenames[unmap_index],
194                                        "%v_%d", mm->file_basename,
195                                        mm->current_file_index++);
196
197   /* Unmap the previous (full) segment */
198   (void) munmap ((u8 *) mm->file_baseva[unmap_index], file_size_in_bytes);
199
200   /* Create a new segment */
201   fd = open ((char *) mm->filenames[unmap_index],
202              O_CREAT | O_RDWR | O_TRUNC, 0600);
203
204   /* This is not real error recovery... */
205   if (fd < 0)
206     {
207       clib_unix_warning ("creat");
208       abort ();
209     }
210
211   if (lseek (fd, file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1)
212     {
213       clib_unix_warning ("lseek");
214       abort ();
215     }
216   if (write (fd, &zero, 1) != 1)
217     {
218       clib_unix_warning ("set-size write");
219       abort ();
220     }
221
222   mm->file_baseva[unmap_index] =
223     mmap (0, file_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
224   if (mm->file_baseva[unmap_index] == (u8 *) MAP_FAILED)
225     {
226       clib_unix_warning ("mmap");
227       abort ();
228     }
229   (void) close (fd);
230
231   rv = (u8 *)
232     mm->file_baseva[(my_record_index >> mm->log2_file_size_in_records) & 1] +
233     (my_record_index & (mm->file_size_in_records - 1))
234     * mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;
235
236   return rv;
237 }
238
239 /**
240  * @brief Update a mapped log header file
241  *
242  * Read the log header. Update the number of records, and number of files
243  * @param[in/out] mm    mapped log object
244  */
245 void
246 clib_maplog_update_header (clib_maplog_main_t * mm)
247 {
248   int fd, rv;
249   clib_maplog_header_t _h, *h = &_h;
250
251   if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT))
252     return;
253
254   /* Open the log header */
255   fd = open ((char *) mm->header_filename, O_RDWR, 0600);
256   if (fd < 0)
257     {
258       clib_unix_warning ("reopen maplog header");
259       goto out;
260     }
261
262   /* Read the log */
263   rv = read (fd, h, sizeof (*h));
264   if (rv != sizeof (*h))
265     {
266       clib_unix_warning ("read maplog header");
267       goto out;
268     }
269   /* Fix the header... */
270   h->number_of_records = mm->next_record_index;
271   h->number_of_files = mm->current_file_index;
272   h->maplog_flag_wrapped = (mm->flags & CLIB_MAPLOG_FLAG_WRAPPED) ? 1 : 0;
273
274   /* Back to the beginning of the log header... */
275   if (lseek (fd, 0, SEEK_SET) < 0)
276     {
277       clib_unix_warning ("lseek to rewrite header");
278       goto out;
279     }
280   /* Rewrite the log header */
281   rv = write (fd, h, sizeof (*h));
282   if (rv != sizeof (*h))
283     clib_unix_warning ("rewrite header");
284
285 out:
286   if (fd >= 0)
287     (void) close (fd);
288 }
289
290 /**
291  * @brief Close a mapped log, and update the log header file
292  *
293  * Unmap the current log segments.
294  * Read the log header. Update the number of records, and number of files
295  *
296  * @param[in/out] mm    mapped log object
297  */
298 void
299 clib_maplog_close (clib_maplog_main_t * mm)
300 {
301   int i, limit;
302   u64 file_size_in_bytes;
303
304   if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT))
305     return;
306
307   clib_maplog_update_header (mm);
308
309   file_size_in_bytes =
310     mm->file_size_in_records * mm->record_size_in_cachelines *
311     CLIB_CACHE_LINE_BYTES;
312
313   limit = (mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) ? 1 : 2;
314
315   /* unmap current + next segments */
316   for (i = 0; i < limit; i++)
317     {
318       (void) munmap ((u8 *) mm->file_baseva[i], file_size_in_bytes);
319       vec_free (mm->filenames[i]);
320     }
321
322   vec_free (mm->file_basename);
323   vec_free (mm->header_filename);
324   clib_memset (mm, 0, sizeof (*mm));
325 }
326
327 /**
328  * @brief format a log header
329  *
330  * Usage: s = format (0, "%U", format_maplog_header, headerp, verbose);
331  * @param [in] h clib_maplog_header_t pointer
332  * @param [in] verbose self-explanatory
333  */
334 u8 *
335 format_maplog_header (u8 * s, va_list * args)
336 {
337   clib_maplog_header_t *h = va_arg (*args, clib_maplog_header_t *);
338   int verbose = va_arg (*args, int);
339
340   if (!verbose)
341     goto brief;
342   s = format (s, "basename %s ", h->file_basename);
343   s = format (s, "log ver %d.%d.%d app id %u ver %d.%d.%d %s %s\n",
344               h->maplog_major_version,
345               h->maplog_minor_version,
346               h->maplog_patch_version,
347               h->application_id,
348               h->application_major_version,
349               h->application_minor_version, h->application_patch_version,
350               h->maplog_flag_circular ? "circular" : "linear",
351               h->maplog_flag_wrapped ? "wrapped" : "not wrapped");
352   s = format (s, "  records are %d %d-byte cachelines\n",
353               h->record_size_in_cachelines, h->cacheline_size);
354   s = format (s, "  files are %lld records long, %lld files\n",
355               h->file_size_in_records, h->number_of_files);
356   s = format (s, "  %lld records total\n", h->number_of_records);
357   return s;
358
359 brief:
360   s = format (s, "%s %lld records %lld files %lld records/file",
361               h->file_basename, h->number_of_records, h->number_of_files,
362               h->file_size_in_records);
363   return s;
364 }
365
366 /**
367  * @brief Process a complete maplog
368  *
369  * Reads the maplog header. Map and process all log segments in order.
370  * Calls the callback function once per file with a record count.
371  *
372  * Note: if the file header isn't updated by calling
373  * clib_maplog_close(), it will appear to have an infinite
374  * number of records in an infinite number of files.
375  *
376  * So long as the callback function understands that possibility
377  * - by simply ignoring NULL records - the scheme still
378  * works...
379  *
380  * @param [in] file_basename Same basename supplied to clib_maplog_init
381  * @param [in] fp_arg Callback function pointer
382  */
383 int
384 clib_maplog_process (char *file_basename, void *fp_arg)
385 {
386   clib_maplog_header_t _h, *h = &_h;
387   int fd, rv = 0;
388   u64 file_index;
389   u64 file_size_in_bytes;
390   u8 *header_filename, *this_filename = 0;
391   u8 *file_baseva;
392   int (*fp) (clib_maplog_header_t *, void *data, u64 count);
393   u64 records_this_file, records_left;
394   ASSERT (fp_arg);
395
396   fp = fp_arg;
397
398   header_filename = format (0, "%s_header%c", file_basename, 0);
399
400   fd = open ((char *) header_filename, O_RDONLY, 0600);
401   if (fd < 0)
402     {
403       clib_unix_warning ("open maplog header");
404       rv = -1;
405       goto out;
406     }
407   rv = read (fd, h, sizeof (*h));
408   if (rv != sizeof (*h))
409     {
410       clib_unix_warning ("read maplog header");
411       rv = -2;
412       goto out;
413     }
414   (void) close (fd);
415   fd = -1;
416
417   file_size_in_bytes = h->file_size_in_records
418     * h->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES;
419
420   records_left = h->number_of_records;
421
422   for (file_index = 0; file_index < h->number_of_files; file_index++)
423     {
424       vec_reset_length (this_filename);
425       this_filename = format (this_filename, "%s_%llu%c", file_basename,
426                               file_index, 0);
427       fd = open ((char *) this_filename, O_RDONLY, 0600);
428       if (fd < 0)
429         {
430           rv = -3;
431           goto out;
432         }
433
434       file_baseva =
435         mmap (0, file_size_in_bytes, PROT_READ, MAP_SHARED, fd, 0);
436       (void) close (fd);
437       fd = -1;
438       if (file_baseva == (u8 *) MAP_FAILED)
439         {
440           clib_unix_warning ("mmap");
441           rv = -4;
442           goto out;
443         }
444
445       records_this_file = (records_left > h->file_size_in_records) ?
446         h->file_size_in_records : records_left;
447
448       /*
449        * Normal log, or a circular non-wrapped log, or a circular
450        * wrapped log which happens to be exactly linear
451        */
452       if (h->maplog_flag_circular == 0 || h->maplog_flag_wrapped == 0 ||
453           ((h->number_of_records % h->file_size_in_records) == 0))
454         (*fp) (h, file_baseva, records_this_file);
455       else
456         {
457           /* "Normal" wrapped circular log */
458           u64 first_chunk_record_index = h->number_of_records &
459             (h->file_size_in_records - 1);
460           u64 first_chunk_number_of_records = records_this_file -
461             first_chunk_record_index;
462           u8 *chunk_baseva = file_baseva +
463             first_chunk_record_index * h->record_size_in_cachelines *
464             h->cacheline_size;
465           (*fp) (h, chunk_baseva, first_chunk_number_of_records);
466           (*fp) (h, file_baseva,
467                  records_this_file - first_chunk_number_of_records);
468         }
469
470       if (munmap (file_baseva, file_size_in_bytes) < 0)
471         {
472           clib_warning ("munmap");
473           rv = -5;
474           /* but don't stop... */
475         }
476       records_left -= records_this_file;
477       if (records_left == 0)
478         break;
479     }
480
481 out:
482   if (fd >= 0)
483     (void) close (fd);
484
485   vec_free (this_filename);
486   vec_free (header_filename);
487   return rv;
488 }
489
490
491 /*
492  * fd.io coding-style-patch-verification: ON
493  *
494  * Local Variables:
495  * eval: (c-set-style "gnu")
496  * End:
497  */