| /* |
| * Copyright (c) 2017 Cisco and/or its affiliates. |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at: |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| #include <vppinfra/maplog.h> |
| |
| /** |
| * @brief Initialize a maplog object |
| * |
| * Compute record and file size parameters |
| * Create and map two log segments to seed the process |
| * |
| * @param[in/out] a init args structure |
| * @return 0 => success, <0 => failure |
| */ |
| int |
| clib_maplog_init (clib_maplog_init_args_t * a) |
| { |
| int i, fd, limit; |
| int rv = 0; |
| u8 zero = 0; |
| u32 record_size_in_cache_lines; |
| u64 file_size_in_records; |
| clib_maplog_main_t *mm; |
| clib_maplog_header_t _h, *h = &_h; |
| |
| ASSERT (a && a->mm); |
| mm = a->mm; |
| |
| /* Already initialized? */ |
| if (mm->flags & CLIB_MAPLOG_FLAG_INIT) |
| return (-2); |
| |
| clib_memset (mm, 0, sizeof (*mm)); |
| |
| record_size_in_cache_lines = |
| (a->record_size_in_bytes + CLIB_CACHE_LINE_BYTES - |
| 1) / CLIB_CACHE_LINE_BYTES; |
| |
| file_size_in_records = a->file_size_in_bytes |
| / (record_size_in_cache_lines * CLIB_CACHE_LINE_BYTES); |
| |
| /* Round up file size in records to a power of 2, for speed... */ |
| mm->log2_file_size_in_records = max_log2 (file_size_in_records); |
| file_size_in_records = 1ULL << (mm->log2_file_size_in_records); |
| a->file_size_in_bytes = file_size_in_records * record_size_in_cache_lines |
| * CLIB_CACHE_LINE_BYTES; |
| |
| mm->file_basename = format (0, "%s", a->file_basename); |
| if (vec_len (mm->file_basename) > ARRAY_LEN (h->file_basename)) |
| { |
| vec_free (mm->file_basename); |
| return -11; |
| } |
| |
| mm->file_size_in_records = file_size_in_records; |
| mm->flags |= CLIB_MAPLOG_FLAG_INIT; |
| mm->record_size_in_cachelines = record_size_in_cache_lines; |
| limit = 2; |
| if (a->maplog_is_circular) |
| { |
| mm->log2_file_size_in_records = 63; |
| mm->flags |= CLIB_MAPLOG_FLAG_CIRCULAR; |
| limit = 1; |
| } |
| |
| /* |
| * Map the one and only file for a circular log, |
| * two files for a normal log. |
| */ |
| for (i = 0; i < limit; i++) |
| { |
| mm->filenames[i] = format (0, "%v_%d", mm->file_basename, |
| mm->current_file_index++); |
| vec_add1 (mm->filenames[i], 0); |
| |
| fd = open ((char *) mm->filenames[i], O_CREAT | O_RDWR | O_TRUNC, 0600); |
| if (fd < 0) |
| { |
| rv = -3; |
| goto fail; |
| } |
| |
| if (lseek (fd, a->file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1) |
| { |
| rv = -4; |
| goto fail; |
| } |
| if (write (fd, &zero, 1) != 1) |
| { |
| rv = -5; |
| goto fail; |
| } |
| |
| mm->file_baseva[i] = mmap (0, a->file_size_in_bytes, |
| PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| if (mm->file_baseva[i] == (u8 *) MAP_FAILED) |
| { |
| clib_unix_warning ("mmap"); |
| goto fail; |
| } |
| (void) close (fd); |
| } |
| |
| clib_memset (h, 0, sizeof (*h)); |
| h->maplog_major_version = MAPLOG_MAJOR_VERSION; |
| h->maplog_minor_version = MAPLOG_MINOR_VERSION; |
| h->maplog_patch_version = MAPLOG_PATCH_VERSION; |
| h->application_id = a->application_id; |
| h->application_major_version = a->application_major_version; |
| h->application_minor_version = a->application_minor_version; |
| h->application_patch_version = a->application_patch_version; |
| h->record_size_in_cachelines = record_size_in_cache_lines; |
| h->cacheline_size = CLIB_CACHE_LINE_BYTES; |
| h->file_size_in_records = file_size_in_records; |
| h->number_of_records = ~0ULL; |
| h->number_of_files = ~0ULL; |
| h->maplog_flag_circular = a->maplog_is_circular; |
| memcpy (h->file_basename, mm->file_basename, vec_len (mm->file_basename)); |
| |
| mm->header_filename = format (0, "%v_header", mm->file_basename); |
| vec_add1 (mm->header_filename, 0); |
| |
| fd = open ((char *) mm->header_filename, O_CREAT | O_RDWR | O_TRUNC, 0600); |
| if (fd < 0) |
| { |
| clib_unix_warning ("header create"); |
| rv = -6; |
| goto fail; |
| } |
| rv = write (fd, h, sizeof (*h)); |
| if (rv != sizeof (*h)) |
| { |
| clib_unix_warning ("header write"); |
| rv = -7; |
| goto fail; |
| } |
| (void) close (fd); |
| return 0; |
| |
| fail: |
| if (fd >= 0) |
| (void) close (fd); |
| |
| for (i = 0; i < limit; i++) |
| { |
| if (mm->file_baseva[i]) |
| (void) munmap ((u8 *) mm->file_baseva[i], a->file_size_in_bytes); |
| if (mm->filenames[i]) |
| (void) unlink ((char *) mm->filenames[i]); |
| vec_free (mm->filenames[i]); |
| } |
| if (mm->header_filename) |
| { |
| (void) unlink ((char *) mm->header_filename); |
| vec_free (mm->header_filename); |
| } |
| return rv; |
| } |
| |
| /* slow path: unmap a full log segment, and replace it */ |
| |
| u8 * |
| _clib_maplog_get_entry_slowpath (clib_maplog_main_t * mm, u64 my_record_index) |
| { |
| int fd; |
| u8 *rv; |
| u8 zero = 0; |
| u32 unmap_index = (mm->current_file_index) & 1; |
| u64 file_size_in_bytes = mm->file_size_in_records |
| * mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES; |
| |
| /* This should never happen */ |
| ASSERT ((mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) == 0); |
| |
| /* |
| * Kill some time by calling format before we make the previous log |
| * segment disappear. Obviously it won't do to call clib_maplog_get_entry(), |
| * wait 100ms, and then fill in the log entry. |
| */ |
| vec_reset_length (mm->filenames[unmap_index]); |
| mm->filenames[unmap_index] = format (mm->filenames[unmap_index], |
| "%v_%d", mm->file_basename, |
| mm->current_file_index++); |
| |
| /* Unmap the previous (full) segment */ |
| (void) munmap ((u8 *) mm->file_baseva[unmap_index], file_size_in_bytes); |
| |
| /* Create a new segment */ |
| fd = open ((char *) mm->filenames[unmap_index], |
| O_CREAT | O_RDWR | O_TRUNC, 0600); |
| |
| /* This is not real error recovery... */ |
| if (fd < 0) |
| { |
| clib_unix_warning ("creat"); |
| abort (); |
| } |
| |
| if (lseek (fd, file_size_in_bytes - 1, SEEK_SET) == (off_t) - 1) |
| { |
| clib_unix_warning ("lseek"); |
| abort (); |
| } |
| if (write (fd, &zero, 1) != 1) |
| { |
| clib_unix_warning ("set-size write"); |
| abort (); |
| } |
| |
| mm->file_baseva[unmap_index] = |
| mmap (0, file_size_in_bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); |
| if (mm->file_baseva[unmap_index] == (u8 *) MAP_FAILED) |
| { |
| clib_unix_warning ("mmap"); |
| abort (); |
| } |
| (void) close (fd); |
| |
| rv = (u8 *) |
| mm->file_baseva[(my_record_index >> mm->log2_file_size_in_records) & 1] + |
| (my_record_index & (mm->file_size_in_records - 1)) |
| * mm->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES; |
| |
| return rv; |
| } |
| |
| /** |
| * @brief Update a mapped log header file |
| * |
| * Read the log header. Update the number of records, and number of files |
| * @param[in/out] mm mapped log object |
| */ |
| void |
| clib_maplog_update_header (clib_maplog_main_t * mm) |
| { |
| int fd, rv; |
| clib_maplog_header_t _h, *h = &_h; |
| |
| if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT)) |
| return; |
| |
| /* Open the log header */ |
| fd = open ((char *) mm->header_filename, O_RDWR, 0600); |
| if (fd < 0) |
| { |
| clib_unix_warning ("reopen maplog header"); |
| goto out; |
| } |
| |
| /* Read the log */ |
| rv = read (fd, h, sizeof (*h)); |
| if (rv != sizeof (*h)) |
| { |
| clib_unix_warning ("read maplog header"); |
| goto out; |
| } |
| /* Fix the header... */ |
| h->number_of_records = mm->next_record_index; |
| h->number_of_files = mm->current_file_index; |
| h->maplog_flag_wrapped = (mm->flags & CLIB_MAPLOG_FLAG_WRAPPED) ? 1 : 0; |
| |
| /* Back to the beginning of the log header... */ |
| if (lseek (fd, 0, SEEK_SET) < 0) |
| { |
| clib_unix_warning ("lseek to rewrite header"); |
| goto out; |
| } |
| /* Rewrite the log header */ |
| rv = write (fd, h, sizeof (*h)); |
| if (rv != sizeof (*h)) |
| clib_unix_warning ("rewrite header"); |
| |
| out: |
| if (fd >= 0) |
| (void) close (fd); |
| } |
| |
| /** |
| * @brief Close a mapped log, and update the log header file |
| * |
| * Unmap the current log segments. |
| * Read the log header. Update the number of records, and number of files |
| * |
| * @param[in/out] mm mapped log object |
| */ |
| void |
| clib_maplog_close (clib_maplog_main_t * mm) |
| { |
| int i, limit; |
| u64 file_size_in_bytes; |
| |
| if (!(mm->flags & CLIB_MAPLOG_FLAG_INIT)) |
| return; |
| |
| clib_maplog_update_header (mm); |
| |
| file_size_in_bytes = |
| mm->file_size_in_records * mm->record_size_in_cachelines * |
| CLIB_CACHE_LINE_BYTES; |
| |
| limit = (mm->flags & CLIB_MAPLOG_FLAG_CIRCULAR) ? 1 : 2; |
| |
| /* unmap current + next segments */ |
| for (i = 0; i < limit; i++) |
| { |
| (void) munmap ((u8 *) mm->file_baseva[i], file_size_in_bytes); |
| vec_free (mm->filenames[i]); |
| } |
| |
| vec_free (mm->file_basename); |
| vec_free (mm->header_filename); |
| clib_memset (mm, 0, sizeof (*mm)); |
| } |
| |
| /** |
| * @brief format a log header |
| * |
| * Usage: s = format (0, "%U", format_maplog_header, headerp, verbose); |
| * @param [in] h clib_maplog_header_t pointer |
| * @param [in] verbose self-explanatory |
| */ |
| u8 * |
| format_maplog_header (u8 * s, va_list * args) |
| { |
| clib_maplog_header_t *h = va_arg (*args, clib_maplog_header_t *); |
| int verbose = va_arg (*args, int); |
| |
| if (!verbose) |
| goto brief; |
| s = format (s, "basename %s ", h->file_basename); |
| s = format (s, "log ver %d.%d.%d app id %u ver %d.%d.%d %s %s\n", |
| h->maplog_major_version, |
| h->maplog_minor_version, |
| h->maplog_patch_version, |
| h->application_id, |
| h->application_major_version, |
| h->application_minor_version, h->application_patch_version, |
| h->maplog_flag_circular ? "circular" : "linear", |
| h->maplog_flag_wrapped ? "wrapped" : "not wrapped"); |
| s = format (s, " records are %d %d-byte cachelines\n", |
| h->record_size_in_cachelines, h->cacheline_size); |
| s = format (s, " files are %lld records long, %lld files\n", |
| h->file_size_in_records, h->number_of_files); |
| s = format (s, " %lld records total\n", h->number_of_records); |
| return s; |
| |
| brief: |
| s = format (s, "%s %lld records %lld files %lld records/file", |
| h->file_basename, h->number_of_records, h->number_of_files, |
| h->file_size_in_records); |
| return s; |
| } |
| |
| /** |
| * @brief Process a complete maplog |
| * |
| * Reads the maplog header. Map and process all log segments in order. |
| * Calls the callback function once per file with a record count. |
| * |
| * Note: if the file header isn't updated by calling |
| * clib_maplog_close(), it will appear to have an infinite |
| * number of records in an infinite number of files. |
| * |
| * So long as the callback function understands that possibility |
| * - by simply ignoring NULL records - the scheme still |
| * works... |
| * |
| * @param [in] file_basename Same basename supplied to clib_maplog_init |
| * @param [in] fp_arg Callback function pointer |
| */ |
| int |
| clib_maplog_process (char *file_basename, void *fp_arg) |
| { |
| clib_maplog_header_t _h, *h = &_h; |
| int fd, rv = 0; |
| u64 file_index; |
| u64 file_size_in_bytes; |
| u8 *header_filename, *this_filename = 0; |
| u8 *file_baseva; |
| int (*fp) (clib_maplog_header_t *, void *data, u64 count); |
| u64 records_this_file, records_left; |
| ASSERT (fp_arg); |
| |
| fp = fp_arg; |
| |
| header_filename = format (0, "%s_header%c", file_basename, 0); |
| |
| fd = open ((char *) header_filename, O_RDONLY, 0600); |
| if (fd < 0) |
| { |
| clib_unix_warning ("open maplog header"); |
| rv = -1; |
| goto out; |
| } |
| rv = read (fd, h, sizeof (*h)); |
| if (rv != sizeof (*h)) |
| { |
| clib_unix_warning ("read maplog header"); |
| rv = -2; |
| goto out; |
| } |
| (void) close (fd); |
| fd = -1; |
| |
| file_size_in_bytes = h->file_size_in_records |
| * h->record_size_in_cachelines * CLIB_CACHE_LINE_BYTES; |
| |
| records_left = h->number_of_records; |
| |
| for (file_index = 0; file_index < h->number_of_files; file_index++) |
| { |
| vec_reset_length (this_filename); |
| this_filename = format (this_filename, "%s_%llu%c", file_basename, |
| file_index, 0); |
| fd = open ((char *) this_filename, O_RDONLY, 0600); |
| if (fd < 0) |
| { |
| rv = -3; |
| goto out; |
| } |
| |
| file_baseva = |
| mmap (0, file_size_in_bytes, PROT_READ, MAP_SHARED, fd, 0); |
| (void) close (fd); |
| fd = -1; |
| if (file_baseva == (u8 *) MAP_FAILED) |
| { |
| clib_unix_warning ("mmap"); |
| rv = -4; |
| goto out; |
| } |
| |
| records_this_file = (records_left > h->file_size_in_records) ? |
| h->file_size_in_records : records_left; |
| |
| /* |
| * Normal log, or a circular non-wrapped log, or a circular |
| * wrapped log which happens to be exactly linear |
| */ |
| if (h->maplog_flag_circular == 0 || h->maplog_flag_wrapped == 0 || |
| ((h->number_of_records % h->file_size_in_records) == 0)) |
| (*fp) (h, file_baseva, records_this_file); |
| else |
| { |
| /* "Normal" wrapped circular log */ |
| u64 first_chunk_record_index = h->number_of_records & |
| (h->file_size_in_records - 1); |
| u64 first_chunk_number_of_records = records_this_file - |
| first_chunk_record_index; |
| u8 *chunk_baseva = file_baseva + |
| first_chunk_record_index * h->record_size_in_cachelines * |
| h->cacheline_size; |
| (*fp) (h, chunk_baseva, first_chunk_number_of_records); |
| (*fp) (h, file_baseva, |
| records_this_file - first_chunk_number_of_records); |
| } |
| |
| if (munmap (file_baseva, file_size_in_bytes) < 0) |
| { |
| clib_warning ("munmap"); |
| rv = -5; |
| /* but don't stop... */ |
| } |
| records_left -= records_this_file; |
| if (records_left == 0) |
| break; |
| } |
| |
| out: |
| if (fd >= 0) |
| (void) close (fd); |
| |
| vec_free (this_filename); |
| vec_free (header_filename); |
| return rv; |
| } |
| |
| |
| /* |
| * fd.io coding-style-patch-verification: ON |
| * |
| * Local Variables: |
| * eval: (c-set-style "gnu") |
| * End: |
| */ |