Fawkes API Fawkes Development Version
bblogfile.cpp
1
2/***************************************************************************
3 * bblogfile.cpp - BlackBoard log file access convenience class
4 *
5 * Created: Sun Feb 21 11:27:41 2010
6 * Copyright 2006-2010 Tim Niemueller [www.niemueller.de]
7 *
8 ****************************************************************************/
9
10/* This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU Library General Public License for more details.
19 *
20 * Read the full text in the LICENSE.GPL file in the doc directory.
21 */
22
23#include "bblogfile.h"
24
25#include <blackboard/internal/instance_factory.h>
26#include <core/exceptions/system.h>
27#include <utils/misc/strndup.h>
28
29#include <cerrno>
30#include <cstdlib>
31#include <cstring>
32#ifdef __FreeBSD__
33# include <sys/endian.h>
34#elif defined(__MACH__) && defined(__APPLE__)
35# include <sys/_endian.h>
36#else
37# include <endian.h>
38#endif
39#include <arpa/inet.h>
40#include <sys/mman.h>
41#include <sys/stat.h>
42#include <sys/types.h>
43
44#include <unistd.h>
45
46using namespace fawkes;
47
48/** @class BBLogFile "bblogfile.h"
49 * Class to easily access bblogger log files.
50 * This class provides an easy way to interact with bblogger log files.
51 * @author Tim Niemueller
52 */
53
54/** Constructor.
55 * Opens the given file and performs basic sanity checks.
56 * @param filename log file to open
57 * @param interface optional interface instance which must match the data
58 * from the log file. Read methods will store read data in this interface
59 * instance. If no interface is given an instance is created that is not
60 * tied to a blackboard.
61 * @param do_sanity_check true to perform a sanity check on the file on
62 * opening. Turn this off only if you know what you are doing.
63 * @exception CouldNotOpenFileException thrown if file cannot be opened
64 * @exception FileReadException some error occured while reading data from
65 */
66BBLogFile::BBLogFile(const char *filename, fawkes::Interface *interface, bool do_sanity_check)
67{
68 ctor(filename, do_sanity_check);
69
70 if (interface) {
71 interface_ = interface;
72 if ((strcmp(interface_->type(), interface_type_) != 0)
73 || (strcmp(interface_->id(), interface_id_) != 0)) {
74 fclose(f_);
75 free(filename_);
76 free(scenario_);
77 std::string interface_type(interface_type_);
78 std::string interface_id(interface_id_);
79 free(interface_type_);
80 free(interface_id_);
81 throw Exception("Interface UID %s does not match expected %s:%s",
82 interface_->uid(),
83 interface_type.c_str(),
84 interface_id.c_str());
85 }
86 } else {
87 instance_factory_.reset(new BlackBoardInstanceFactory());
88 interface_ = instance_factory_->new_interface_instance(interface_type_, interface_id_);
89 }
90}
91
92/** Constructor.
93 * Opens the given file and performs basic sanity checks.
94 * No internal interface is created. You must take care to set it using
95 * set_interface() before any reading is done.
96 * @param filename log file to open
97 * @param do_sanity_check true to perform a sanity check on the file on
98 * opening. Turn this off only if you know what you are doing.
99 * @exception CouldNotOpenFileException thrown if file cannot be opened
100 * @exception FileReadException some error occured while reading data from
101 */
102BBLogFile::BBLogFile(const char *filename, bool do_sanity_check)
103{
104 ctor(filename, do_sanity_check);
105
106 interface_ = NULL;
107}
108
109void
110BBLogFile::ctor(const char *filename, bool do_sanity_check)
111{
112 f_ = fopen(filename, "r");
113 if (!f_) {
114 throw CouldNotOpenFileException(filename, errno);
115 }
116
117 filename_ = strdup(filename);
118 header_ = (bblog_file_header *)malloc(sizeof(bblog_file_header));
119
120 try {
121 read_file_header();
122 if (do_sanity_check)
123 sanity_check();
124 } catch (Exception &e) {
125 free(filename_);
126 free(scenario_);
127 free(interface_type_);
128 free(interface_id_);
129 fclose(f_);
130 throw;
131 }
132
133 ifdata_ = malloc(header_->data_size);
134}
135
136/** Destructor. */
138{
139 if (instance_factory_) {
140 instance_factory_->delete_interface_instance(interface_);
141 instance_factory_.reset();
142 }
143
144 fclose(f_);
145
146 free(filename_);
147 free(scenario_);
148 free(interface_type_);
149 free(interface_id_);
150
151 free(header_);
152 free(ifdata_);
153}
154
155/** Read file header. */
156void
157BBLogFile::read_file_header()
158{
159 uint32_t magic;
160 uint32_t version;
161 if ((fread(&magic, sizeof(uint32_t), 1, f_) == 1)
162 && (fread(&version, sizeof(uint32_t), 1, f_) == 1)) {
163 if ((ntohl(magic) == BBLOGGER_FILE_MAGIC) && (ntohl(version) == BBLOGGER_FILE_VERSION)) {
164 ::rewind(f_);
165 if (fread(header_, sizeof(bblog_file_header), 1, f_) != 1) {
166 throw FileReadException(filename_, errno, "Failed to read file header");
167 }
168 } else {
169 throw Exception("File magic/version %X/%u does not match (expected %X/%u)",
170 ntohl(magic),
171 ntohl(version),
172 BBLOGGER_FILE_VERSION,
173 BBLOGGER_FILE_MAGIC);
174 }
175 } else {
176 throw Exception(filename_, errno, "Failed to read magic/version from file");
177 }
178
179 scenario_ = strndup(header_->scenario, BBLOG_SCENARIO_SIZE);
180 interface_type_ = strndup(header_->interface_type, BBLOG_INTERFACE_TYPE_SIZE);
181 interface_id_ = strndup(header_->interface_id, BBLOG_INTERFACE_ID_SIZE);
182
183 start_time_.set_time(header_->start_time_sec, header_->start_time_usec);
184}
185
186/** Perform sanity checks.
187 * This methods performs some sanity checks like:
188 * - check if number of items is 0
189 * - check if number of items and file size match
190 * - check endianess of file and system
191 * - check if file seems to be truncated
192 */
193void
194BBLogFile::sanity_check()
195{
196 if (header_->num_data_items == 0) {
197 Exception e("File %s does not specify number of data items", filename_);
198 e.set_type_id("bblogfile-num-items-zero");
199 throw e;
200 }
201
202 struct stat fs;
203 if (fstat(fileno(f_), &fs) != 0) {
204 Exception e(errno, "Failed to stat file %s", filename_);
205 e.set_type_id("bblogfile-stat-failed");
206 throw e;
207 }
208
209 long int expected_size = sizeof(bblog_file_header)
210 + (size_t)header_->num_data_items * header_->data_size
211 + (size_t)header_->num_data_items * sizeof(bblog_entry_header);
212 if (expected_size != fs.st_size) {
213 Exception e("Size of file %s does not match expectation "
214 "(actual: %li, actual: %li)",
215 filename_,
216 expected_size,
217 (long int)fs.st_size);
218 e.set_type_id("bblogfile-file-size-mismatch");
219 throw e;
220 }
221
222#if BYTE_ORDER_ == LITTLE_ENDIAN_
223 if (header_->endianess == 1)
224#else
225 if (header_->endianess == 0)
226#endif
227 {
228 Exception e("File %s has incompatible endianess", filename_);
229 e.set_type_id("bblogfile-endianess-mismatch");
230 throw e;
231 }
232}
233
234/** Read entry at particular index.
235 * @param index index of entry, 0-based
236 */
237void
238BBLogFile::read_index(unsigned int index)
239{
240 long offset =
241 sizeof(bblog_file_header) + (sizeof(bblog_entry_header) + header_->data_size) * index;
242
243 if (fseek(f_, offset, SEEK_SET) != 0) {
244 throw Exception(errno, "Cannot seek to index %u", index);
245 }
246
247 read_next();
248}
249
250/** Rewind file to start.
251 * This moves the file cursor immediately before the first entry.
252 */
253void
255{
256 if (fseek(f_, sizeof(bblog_file_header), SEEK_SET) != 0) {
257 throw Exception(errno, "Cannot reset file");
258 }
259 entry_offset_.set_time(0, 0);
260}
261
262/** Check if another entry is available.
263 * @return true if a consecutive read_next() will succeed, false otherwise
264 */
265bool
267{
268 // we always re-test to support continuous file watching
269 clearerr(f_);
270 if (getc(f_) == EOF) {
271 return false;
272 } else {
273 fseek(f_, -1, SEEK_CUR);
274 return true;
275 }
276}
277
278/** Read next entry.
279 * @exception Exception thrown if reading fails, for example because no more
280 * entries are left.
281 */
282void
284{
285 bblog_entry_header entryh;
286
287 if ((fread(&entryh, sizeof(bblog_entry_header), 1, f_) == 1)
288 && (fread(ifdata_, header_->data_size, 1, f_) == 1)) {
289 entry_offset_.set_time(entryh.rel_time_sec, entryh.rel_time_usec);
290 interface_->set_from_chunk(ifdata_);
291 } else {
292 throw Exception("Cannot read interface data");
293 }
294}
295
296/** Set number of entries.
297 * Set the number of entries in the file. Attention, this is only to be used
298 * by the repair() method.
299 * @param num_entries number of entries
300 */
301void
302BBLogFile::set_num_entries(size_t num_entries)
303{
304#if _POSIX_MAPPED_FILES
305 void *h = mmap(NULL, sizeof(bblog_file_header), PROT_WRITE, MAP_SHARED, fileno(f_), 0);
306 if (h == MAP_FAILED) {
307 throw Exception(errno, "Failed to mmap log, not updating number of data items");
308 } else {
310 header->num_data_items = num_entries;
311 munmap(h, sizeof(bblog_file_header));
312 }
313#else
314 throw Exception("Cannot set number of entries, mmap not available.");
315#endif
316}
317
318/** Repair file.
319 * @param filename file to repair
320 * @see repair()
321 */
322void
323BBLogFile::repair_file(const char *filename)
324{
325 BBLogFile file(filename, NULL, false);
326 file.repair();
327}
328
329/** This tries to fix files which are in an inconsistent state.
330 * On success, an exception is thrown with a type_id of "repair-success", which
331 * will have an entry for each successful operation.
332 */
333void
334BBLogFile::repair()
335{
336 FILE *f = freopen(filename_, "r+", f_);
337 if (!f) {
338 throw Exception("Reopening file %s with new mode failed", filename_);
339 }
340 f_ = f;
341
342 bool repair_done = false;
343
344 Exception success("Successfully repaired file");
345 success.set_type_id("repair-success");
346
347#if BYTE_ORDER_ == LITTLE_ENDIAN_
348 if (header_->endianess == 1)
349#else
350 if (header_->endianess == 0)
351#endif
352 {
353 throw Exception("File %s has incompatible endianess. Cannot repair.", filename_);
354 }
355
356 struct stat fs;
357 if (fstat(fileno(f_), &fs) != 0) {
358 throw Exception(errno, "Failed to stat file %s", filename_);
359 }
360
361 size_t entry_size = sizeof(bblog_entry_header) + header_->data_size;
362 size_t all_entries_size = fs.st_size - sizeof(bblog_file_header);
363 size_t num_entries = all_entries_size / entry_size;
364 size_t extra_bytes = all_entries_size % entry_size;
365
366 if (extra_bytes != 0) {
367 success.append("FIXING: errorneous bytes at end of file, "
368 "truncating by %zu b",
369 extra_bytes);
370 if (ftruncate(fileno(f_), fs.st_size - extra_bytes) == -1) {
371 throw Exception(errno, "Failed to truncate file %s", filename_);
372 }
373 //all_entries_size -= extra_bytes;
374 extra_bytes = 0;
375 if (fstat(fileno(f_), &fs) != 0) {
376 throw Exception(errno,
377 "Failed to update information of file %s "
378 "after truncate",
379 filename_);
380 }
381 repair_done = true;
382 }
383 if (header_->num_data_items == 0) {
384 success.append("FIXING: header of file %s has 0 data items, setting to %zu.",
385 filename_,
386 num_entries);
387 set_num_entries(num_entries);
388 repair_done = true;
389 } else if (header_->num_data_items != num_entries) {
390 success.append("FIXING: header has %u data items, but expecting %zu, setting",
391 header_->num_data_items,
392 num_entries);
393 set_num_entries(num_entries);
394 repair_done = true;
395 }
396
397 f = freopen(filename_, "r", f_);
398 if (!f) {
399 throw Exception("Reopening file %s with read-only mode failed", filename_);
400 }
401 f_ = f;
402
403 if (repair_done) {
404 throw success;
405 }
406}
407
408/** Print file meta info.
409 * @param line_prefix a prefix printed before each line
410 * @param outf file handle to print to
411 */
412void
413BBLogFile::print_info(const char *line_prefix, FILE *outf)
414{
415 char interface_hash[BBLOG_INTERFACE_HASH_SIZE * 2 + 1];
416
417 for (unsigned int i = 0; i < BBLOG_INTERFACE_HASH_SIZE; ++i) {
418 snprintf(&interface_hash[i * 2], 3, "%02X", header_->interface_hash[i]);
419 }
420
421 struct stat fs;
422 if (fstat(fileno(f_), &fs) != 0) {
423 throw Exception(errno, "Failed to get stat file");
424 }
425
426 fprintf(outf,
427 "%sFile version: %-10u Endianess: %s Endian\n"
428 "%s# data items: %-10u Data size: %u bytes\n"
429 "%sHeader size: %zu bytes File size: %li bytes\n"
430 "%s\n"
431 "%sScenario: %s\n"
432 "%sInterface: %s::%s (%s)\n"
433 "%sStart time: %s\n",
434 line_prefix,
435 ntohl(header_->file_version),
436 (header_->endianess == 1) ? "Big" : "Little",
437 line_prefix,
438 header_->num_data_items,
439 header_->data_size,
440 line_prefix,
441 sizeof(bblog_file_header),
442 (long int)fs.st_size,
443 line_prefix,
444 line_prefix,
445 scenario_,
446 line_prefix,
447 interface_type_,
448 interface_id_,
450 line_prefix,
451 start_time_.str());
452}
453
454/** Print an entry.
455 * Verbose print of a single entry.
456 * @param outf file handle to print to
457 */
458void
460{
461 fprintf(outf, "Time Offset: %f\n", entry_offset_.in_sec());
462
464 for (i = interface_->fields(); i != interface_->fields_end(); ++i) {
465 char *typesize;
466 if (i.get_length() > 1) {
467 if (asprintf(&typesize, "%s[%zu]", i.get_typename(), i.get_length()) == -1) {
468 throw Exception("Out of memory");
469 }
470 } else {
471 if (asprintf(&typesize, "%s", i.get_typename()) == -1) {
472 throw Exception("Out of memory");
473 }
474 }
475 fprintf(outf, "%-16s %-18s: %s\n", i.get_name(), typesize, i.get_value_string());
476 free(typesize);
477 }
478}
479
480/** Get interface instance.
481 * @return internally used interface
482 */
485{
486 return interface_;
487}
488
489/** Set the internal interface.
490 * @param interface an interface matching the type and ID given in the
491 * log file.
492 */
493void
495{
496 if ((strcmp(interface->type(), interface_type_) == 0)
497 && (strcmp(interface->id(), interface_id_) == 0)
498 && (memcmp(interface->hash(), header_->interface_hash, INTERFACE_HASH_SIZE_) == 0)) {
499 if (instance_factory_) {
500 instance_factory_->delete_interface_instance(interface_);
501 instance_factory_.reset();
502 }
503 interface_ = interface;
504 } else {
505 throw TypeMismatchException("Interfaces incompatible");
506 }
507}
508
509/** Get current entry offset.
510 * @return offset from start time of current entry (may be 0 if no entry has
511 * been read, yet, or after rewind()).
512 */
513const fawkes::Time &
515{
516 return entry_offset_;
517}
518
519/** Get file version.
520 * @return file version
521 */
522uint32_t
524{
525 return ntohl(header_->file_version);
526}
527
528/** Check if file is big endian.
529 * @return true if file is big endian, false otherwise
530 */
531bool
533{
534 return (header_->endianess == 1);
535}
536
537/** Get number of data items in file.
538 * @return number of data items
539 */
540uint32_t
542{
543 return header_->num_data_items;
544}
545
546/** Get scenario identifier.
547 * @return scenario identifier
548 */
549const char *
551{
552 return scenario_;
553}
554
555/** Get interface type.
556 * @return type of logged interface
557 */
558const char *
560{
561 return interface_type_;
562}
563
564/** Get interface ID.
565 * @return ID of logged interface
566 */
567const char *
569{
570 return interface_id_;
571}
572
573/** Get interface hash.
574 * Hash of logged interface.
575 * @return interface hash
576 */
577unsigned char *
579{
580 return header_->interface_hash;
581}
582
583/** Get data size.
584 * @return size of the pure data part of the log entries
585 */
586uint32_t
588{
589 return header_->data_size;
590}
591
592/** Get start time.
593 * @return starting time of log
594 */
597{
598 return start_time_;
599}
600
601/** Get number of remaining entries.
602 * @return number of remaining entries
603 */
604unsigned int
606{
607 // we make this so "complicated" to be able to use it from a FAM handler
608 size_t entry_size = sizeof(bblog_entry_header) + header_->data_size;
609 long curpos = ftell(f_);
610 size_t fsize = file_size();
611 ssize_t sizediff = fsize - curpos;
612
613 if (sizediff < 0) {
614 throw Exception("File %s shrank while reading it", filename_);
615 }
616
617 return sizediff / entry_size;
618}
619
620/** Get file size.
621 * @return total size of log file including all headers
622 */
623size_t
625{
626 struct stat fs;
627 if (fstat(fileno(f_), &fs) != 0) {
628 Exception e(errno, "Failed to stat file %s", filename_);
629 e.set_type_id("bblogfile-stat-failed");
630 throw e;
631 }
632 return fs.st_size;
633}
Class to easily access bblogger log files.
Definition: bblogfile.h:40
void set_num_entries(size_t num_entries)
Set number of entries.
Definition: bblogfile.cpp:302
uint32_t num_data_items() const
Get number of data items in file.
Definition: bblogfile.cpp:541
uint32_t file_version() const
Get file version.
Definition: bblogfile.cpp:523
bool is_big_endian() const
Check if file is big endian.
Definition: bblogfile.cpp:532
const char * interface_type() const
Get interface type.
Definition: bblogfile.cpp:559
bool has_next()
Check if another entry is available.
Definition: bblogfile.cpp:266
void set_interface(fawkes::Interface *interface)
Set the internal interface.
Definition: bblogfile.cpp:494
void read_next()
Read next entry.
Definition: bblogfile.cpp:283
const char * interface_id() const
Get interface ID.
Definition: bblogfile.cpp:568
const fawkes::Time & entry_offset() const
Get current entry offset.
Definition: bblogfile.cpp:514
static void repair_file(const char *filename)
Repair file.
Definition: bblogfile.cpp:323
fawkes::Interface * interface()
Get interface instance.
Definition: bblogfile.cpp:484
fawkes::Time & start_time()
Get start time.
Definition: bblogfile.cpp:596
void rewind()
Rewind file to start.
Definition: bblogfile.cpp:254
BBLogFile(const char *filename, bool do_sanity_check)
Constructor.
Definition: bblogfile.cpp:102
void read_index(unsigned int index)
Read entry at particular index.
Definition: bblogfile.cpp:238
unsigned char * interface_hash() const
Get interface hash.
Definition: bblogfile.cpp:578
void print_info(const char *line_prefix="", FILE *outf=stdout)
Print file meta info.
Definition: bblogfile.cpp:413
size_t file_size() const
Get file size.
Definition: bblogfile.cpp:624
const char * scenario() const
Get scenario identifier.
Definition: bblogfile.cpp:550
uint32_t data_size()
Get data size.
Definition: bblogfile.cpp:587
~BBLogFile()
Destructor.
Definition: bblogfile.cpp:137
unsigned int remaining_entries()
Get number of remaining entries.
Definition: bblogfile.cpp:605
void print_entry(FILE *outf=stdout)
Print an entry.
Definition: bblogfile.cpp:459
BlackBoard instance factory.
File could not be opened.
Definition: system.h:53
Base class for exceptions in Fawkes.
Definition: exception.h:36
void set_type_id(const char *id)
Set exception type ID.
Definition: exception.cpp:286
File could not be read.
Definition: system.h:62
Interface field iterator.
size_t get_length() const
Get length of current field.
const char * get_name() const
Get name of current field.
const char * get_value_string(const char *array_sep=", ")
Get value of current field as string.
const char * get_typename() const
Get type of current field as string.
Base class for all Fawkes BlackBoard interfaces.
Definition: interface.h:80
const char * type() const
Get type of interface.
Definition: interface.cpp:652
InterfaceFieldIterator fields_end()
Invalid iterator.
Definition: interface.cpp:1240
const unsigned char * hash() const
Get interface hash.
Definition: interface.cpp:305
const char * id() const
Get identifier of interface.
Definition: interface.cpp:661
InterfaceFieldIterator fields()
Get iterator over all fields of this interface instance.
Definition: interface.cpp:1231
void set_from_chunk(void *chunk)
Set from a raw data chunk.
Definition: interface.cpp:831
const char * uid() const
Get unique identifier of interface.
Definition: interface.cpp:686
A class for handling time.
Definition: time.h:93
double in_sec() const
Convet time to seconds.
Definition: time.cpp:219
const char * str(bool utc=false) const
Output function.
Definition: time.cpp:790
void set_time(const timeval *tv)
Sets the time.
Definition: time.cpp:246
Fawkes library namespace.
BBLogger entry header.
Definition: file.h:76
uint32_t rel_time_usec
time since start time, microseconds
Definition: file.h:78
uint32_t rel_time_sec
time since start time, seconds
Definition: file.h:77
BBLogger file header definition.
Definition: file.h:53
char interface_type[BBLOG_INTERFACE_TYPE_SIZE]
Interface type.
Definition: file.h:64
char scenario[BBLOG_SCENARIO_SIZE]
Scenario as defined in config.
Definition: file.h:62
uint64_t start_time_sec
Start time, timestamp seconds.
Definition: file.h:68
uint32_t data_size
size of one interface data block
Definition: file.h:67
char interface_id[BBLOG_INTERFACE_ID_SIZE]
Interface ID.
Definition: file.h:65
uint64_t start_time_usec
Start time, timestamp microseconds.
Definition: file.h:69
uint32_t endianess
Endianess, 0 little endian, 1 big endian.
Definition: file.h:58
uint32_t file_version
File version, set to BBLOGGER_FILE_VERSION on write and verify on read (big endian)
Definition: file.h:56
uint32_t num_data_items
Number of data items in file, if set to zero reader must scan the file for this number.
Definition: file.h:60
unsigned char interface_hash[BBLOG_INTERFACE_HASH_SIZE]
Interface Hash.
Definition: file.h:66