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 <core/exceptions/system.h>
26 #include <utils/misc/strndup.h>
27 #include <blackboard/internal/instance_factory.h>
28 
29 #include <cstdlib>
30 #include <cerrno>
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/types.h>
41 #include <sys/stat.h>
42 #include <unistd.h>
43 #include <sys/mman.h>
44 
45 using namespace fawkes;
46 
47 /** @class BBLogFile "bblogfile.h"
48  * Class to easily access bblogger log files.
49  * This class provides an easy way to interact with bblogger log files.
50  * @author Tim Niemueller
51  */
52 
53 /** Constructor.
54  * Opens the given file and performs basic sanity checks.
55  * @param filename log file to open
56  * @param interface optional interface instance which must match the data
57  * from the log file. Read methods will store read data in this interface
58  * instance. If no interface is given an instance is created that is not
59  * tied to a blackboard.
60  * @param do_sanity_check true to perform a sanity check on the file on
61  * opening. Turn this off only if you know what you are doing.
62  * @exception CouldNotOpenFileException thrown if file cannot be opened
63  * @exception FileReadException some error occured while reading data from
64  */
65 BBLogFile::BBLogFile(const char *filename, fawkes::Interface *interface,
66  bool do_sanity_check)
67 {
68  ctor(filename, do_sanity_check);
69 
70  if (interface) {
71  __instance_factory = NULL;
72  __interface = interface;
73  if ((strcmp(__interface->type(), __interface_type) != 0) ||
74  (strcmp(__interface->id(), __interface_id) != 0)) {
75  fclose(__f);
76  free(__filename);
77  free(__scenario);
78  free(__interface_type);
79  free(__interface_id);
80  throw Exception("Interface UID %s does not match expected %s:%s",
81  __interface->uid(), __interface_type, __interface_id);
82  }
83  } else {
84  __instance_factory = new BlackBoardInstanceFactory();
85  __interface = __instance_factory->new_interface_instance(__interface_type,
86  __interface_id);
87  }
88 }
89 
90 
91 /** Constructor.
92  * Opens the given file and performs basic sanity checks.
93  * No internal interface is created. You must take care to set it using
94  * set_interface() before any reading is done.
95  * @param filename log file to open
96  * @param do_sanity_check true to perform a sanity check on the file on
97  * opening. Turn this off only if you know what you are doing.
98  * @exception CouldNotOpenFileException thrown if file cannot be opened
99  * @exception FileReadException some error occured while reading data from
100  */
101 BBLogFile::BBLogFile(const char *filename, bool do_sanity_check)
102 {
103  ctor(filename, do_sanity_check);
104 
105  __instance_factory = NULL;
106  __interface = NULL;
107 }
108 
109 
110 void
111 BBLogFile::ctor(const char *filename, bool do_sanity_check)
112 {
113  __f = fopen(filename, "r");
114  if (!__f) {
115  throw CouldNotOpenFileException(filename, errno);
116  }
117 
118  __filename = strdup(filename);
119  __header = (bblog_file_header *)malloc(sizeof(bblog_file_header));
120 
121  try {
122  read_file_header();
123  if (do_sanity_check) 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  delete __instance_factory;
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 
156 /** Read file header. */
157 void
158 BBLogFile::read_file_header()
159 {
160  uint32_t magic;
161  uint32_t version;
162  if ((fread(&magic, sizeof(uint32_t), 1, __f) == 1) &&
163  (fread(&version, sizeof(uint32_t), 1, __f) == 1) ) {
164  if ( (ntohl(magic) == BBLOGGER_FILE_MAGIC) &&
165  (ntohl(version) == BBLOGGER_FILE_VERSION) ) {
166  ::rewind(__f);
167  if (fread(__header, sizeof(bblog_file_header), 1, __f) != 1) {
168  throw FileReadException(__filename, errno, "Failed to read file header");
169  }
170  } else {
171  throw Exception("File magic/version %X/%u does not match (expected %X/%u)",
172  ntohl(magic), ntohl(version),
173  BBLOGGER_FILE_VERSION, 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 
187 /** Perform sanity checks.
188  * This methods performs some sanity checks like:
189  * - check if number of items is 0
190  * - check if number of items and file size match
191  * - check endianess of file and system
192  * - check if file seems to be truncated
193  */
194 void
195 BBLogFile::sanity_check()
196 {
197  if (__header->num_data_items == 0) {
198  Exception e("File %s does not specify number of data items", __filename);
199  e.set_type_id("bblogfile-num-items-zero");
200  throw e;
201  }
202 
203  struct stat fs;
204  if (fstat(fileno(__f), &fs) != 0) {
205  Exception e(errno, "Failed to stat file %s", __filename);
206  e.set_type_id("bblogfile-stat-failed");
207  throw e;
208  }
209 
210  long int expected_size = sizeof(bblog_file_header)
211  + __header->num_data_items * __header->data_size
212  + __header->num_data_items * sizeof(bblog_entry_header);
213  if (expected_size != fs.st_size) {
214  Exception e("Size of file %s does not match expectation "
215  "(actual: %li, actual: %li)",
216  __filename, expected_size, (long int)fs.st_size);
217  e.set_type_id("bblogfile-file-size-mismatch");
218  throw e;
219  }
220 
221 #if __BYTE_ORDER == __LITTLE_ENDIAN
222  if (__header->endianess == 1)
223 #else
224  if (__header->endianess == 0)
225 #endif
226  {
227  Exception e("File %s has incompatible endianess", __filename);
228  e.set_type_id("bblogfile-endianess-mismatch");
229  throw e;
230  }
231 }
232 
233 /** Read entry at particular index.
234  * @param index index of entry, 0-based
235  */
236 void
237 BBLogFile::read_index(unsigned int index)
238 {
239  long offset = sizeof(bblog_file_header)
240  + (sizeof(bblog_entry_header) + __header->data_size) * index;
241 
242  if (fseek(__f, offset, SEEK_SET) != 0) {
243  throw Exception(errno, "Cannot seek to index %u", index);
244  }
245 
246  read_next();
247 }
248 
249 
250 /** Rewind file to start.
251  * This moves the file cursor immediately before the first entry.
252  */
253 void
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 
263 /** Check if another entry is available.
264  * @return true if a consecutive read_next() will succeed, false otherwise
265  */
266 bool
268 {
269  // we always re-test to support continuous file watching
270  clearerr(__f);
271  if (getc(__f) == EOF) {
272  return false;
273  } else {
274  fseek(__f, -1, SEEK_CUR);
275  return true;
276  }
277 }
278 
279 /** Read next entry.
280  * @exception Exception thrown if reading fails, for example because no more
281  * entries are left.
282  */
283 void
285 {
286  bblog_entry_header entryh;
287 
288  if ( (fread(&entryh, sizeof(bblog_entry_header), 1, __f) == 1) &&
289  (fread(__ifdata, __header->data_size, 1, __f) == 1) ) {
290  __entry_offset.set_time(entryh.rel_time_sec, entryh.rel_time_usec);
291  __interface->set_from_chunk(__ifdata);
292  } else {
293  throw Exception("Cannot read interface data");
294  }
295 }
296 
297 
298 /** Set number of entries.
299  * Set the number of entries in the file. Attention, this is only to be used
300  * by the repair() method.
301  * @param num_entries number of entries
302  */
303 void
304 BBLogFile::set_num_entries(size_t num_entries)
305 {
306 #if _POSIX_MAPPED_FILES
307  void *h = mmap(NULL, sizeof(bblog_file_header), PROT_WRITE, MAP_SHARED,
308  fileno(__f), 0);
309  if (h == MAP_FAILED) {
310  throw Exception(errno, "Failed to mmap log, not updating number of data items");
311  } else {
312  bblog_file_header *header = (bblog_file_header *)h;
313  header->num_data_items = num_entries;
314  munmap(h, sizeof(bblog_file_header));
315  }
316 #else
317  throw Exception("Cannot set number of entries, mmap not available.");
318 #endif
319 }
320 
321 
322 /** Repair file.
323  * @param filename file to repair
324  * @see repair()
325  */
326 void
327 BBLogFile::repair_file(const char *filename)
328 {
329  BBLogFile file(filename, NULL, false);
330  file.repair();
331 }
332 
333 
334 /** This tries to fix files which are in an inconsistent state.
335  * On success, an exception is thrown with a type_id of "repair-success", which
336  * will have an entry for each successful operation.
337  */
338 void
339 BBLogFile::repair()
340 {
341  FILE *f = freopen(__filename, "r+", __f);
342  if (! f) {
343  throw Exception("Reopening file %s with new mode failed", __filename);
344  }
345  __f = f;
346 
347  bool repair_done = false;
348 
349  Exception success("Successfully repaired file");
350  success.set_type_id("repair-success");
351 
352 #if __BYTE_ORDER == __LITTLE_ENDIAN
353  if (__header->endianess == 1)
354 #else
355  if (__header->endianess == 0)
356 #endif
357  {
358  throw Exception("File %s has incompatible endianess. Cannot repair.",
359  __filename);
360  }
361 
362  struct stat fs;
363  if (fstat(fileno(__f), &fs) != 0) {
364  throw Exception(errno, "Failed to stat file %s", __filename);
365  }
366 
367  size_t entry_size = sizeof(bblog_entry_header) + __header->data_size;
368  size_t all_entries_size = fs.st_size - sizeof(bblog_file_header);
369  size_t num_entries = all_entries_size / entry_size;
370  size_t extra_bytes = all_entries_size % entry_size;
371 
372  if (extra_bytes != 0) {
373  success.append("FIXING: errorneous bytes at end of file, "
374  "truncating by %zu b", extra_bytes);
375  if (ftruncate(fileno(__f), fs.st_size - extra_bytes) == -1) {
376  throw Exception(errno, "Failed to truncate file %s", __filename);
377  }
378  all_entries_size -= extra_bytes;
379  extra_bytes = 0;
380  if (fstat(fileno(__f), &fs) != 0) {
381  throw Exception(errno, "Failed to update information of file %s "
382  "after truncate", __filename);
383  }
384  repair_done = true;
385  }
386  if (__header->num_data_items == 0) {
387  success.append("FIXING: header of file %s has 0 data items, setting to %zu.",
388  __filename, num_entries);
389  set_num_entries(num_entries);
390  repair_done = true;
391  } else if (__header->num_data_items != num_entries) {
392  success.append("FIXING: header has %u data items, but expecting %zu, setting",
393  __header->num_data_items, num_entries);
394  set_num_entries(num_entries);
395  repair_done = true;
396  }
397 
398  f = freopen(__filename, "r", __f);
399  if (! f) {
400  throw Exception("Reopening file %s with read-only mode failed", __filename);
401  }
402  __f = f;
403 
404  if (repair_done) {
405  throw success;
406  }
407 }
408 
409 
410 /** Print file meta info.
411  * @param line_prefix a prefix printed before each line
412  * @param outf file handle to print to
413  */
414 void
415 BBLogFile::print_info(const char *line_prefix, FILE *outf)
416 {
417  char interface_hash[BBLOG_INTERFACE_HASH_SIZE * 2 + 1];
418 
419  for (unsigned int i = 0; i < BBLOG_INTERFACE_HASH_SIZE; ++i) {
420  snprintf(&interface_hash[i*2], 3, "%02X", __header->interface_hash[i]);
421  }
422 
423  struct stat fs;
424  if (fstat(fileno(__f), &fs) != 0) {
425  throw Exception(errno, "Failed to get stat file");
426  }
427 
428  fprintf(outf,
429  "%sFile version: %-10u Endianess: %s Endian\n"
430  "%s# data items: %-10u Data size: %u bytes\n"
431  "%sHeader size: %zu bytes File size: %li bytes\n"
432  "%s\n"
433  "%sScenario: %s\n"
434  "%sInterface: %s::%s (%s)\n"
435  "%sStart time: %s\n",
436  line_prefix, ntohl(__header->file_version),
437  (__header->endianess == 1) ? "Big" : "Little",
438  line_prefix, __header->num_data_items, __header->data_size,
439  line_prefix, sizeof(bblog_file_header), (long int)fs.st_size,
440  line_prefix,
441  line_prefix, __scenario,
442  line_prefix, __interface_type, __interface_id, interface_hash,
443  line_prefix, __start_time.str());
444 
445 }
446 
447 
448 /** Print an entry.
449  * Verbose print of a single entry.
450  * @param outf file handle to print to
451  */
452 void
454 {
455  fprintf(outf, "Time Offset: %f\n", __entry_offset.in_sec());
456 
458  for (i = __interface->fields(); i != __interface->fields_end(); ++i) {
459  char *typesize;
460  if (i.get_length() > 1) {
461  if (asprintf(&typesize, "%s[%zu]", i.get_typename(), i.get_length()) == -1) {
462  throw Exception("Out of memory");
463  }
464  } else {
465  if (asprintf(&typesize, "%s", i.get_typename()) == -1) {
466  throw Exception("Out of memory");
467  }
468  }
469  fprintf(outf, "%-16s %-18s: %s\n",
470  i.get_name(), typesize, i.get_value_string());
471  free(typesize);
472  }
473 }
474 
475 
476 /** Get interface instance.
477  * @return internally used interface
478  */
481 {
482  return __interface;
483 }
484 
485 
486 /** Set the internal interface.
487  * @param interface an interface matching the type and ID given in the
488  * log file.
489  */
490 void
492 {
493  if ( (strcmp(interface->type(), __interface_type) == 0) &&
494  (strcmp(interface->id(), __interface_id) == 0) &&
495  (memcmp(interface->hash(), __header->interface_hash,
496  __INTERFACE_HASH_SIZE) == 0) ) {
497  if (__instance_factory) {
498  __instance_factory->delete_interface_instance(__interface);
499  delete __instance_factory;
500  __instance_factory = NULL;
501  }
502  __interface = interface;
503  } else {
504  throw TypeMismatchException("Interfaces incompatible");
505  }
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  */
513 const fawkes::Time &
515 {
516  return __entry_offset;
517 }
518 
519 
520 /** Get file version.
521  * @return file version
522  */
523 uint32_t
525 {
526  return ntohl(__header->file_version);
527 }
528 
529 
530 /** Check if file is big endian.
531  * @return true if file is big endian, false otherwise
532  */
533 bool
535 {
536  return (__header->endianess == 1);
537 }
538 
539 /** Get number of data items in file.
540  * @return number of data items
541  */
542 uint32_t
544 {
545  return __header->num_data_items;
546 }
547 
548 
549 /** Get scenario identifier.
550  * @return scenario identifier
551  */
552 const char *
554 {
555  return __scenario;
556 }
557 
558 
559 /** Get interface type.
560  * @return type of logged interface
561  */
562 const char *
564 {
565  return __interface_type;
566 }
567 
568 
569 /** Get interface ID.
570  * @return ID of logged interface
571  */
572 const char *
574 {
575  return __interface_id;
576 }
577 
578 
579 /** Get interface hash.
580  * Hash of logged interface.
581  * @return interface hash
582  */
583 unsigned char *
585 {
586  return __header->interface_hash;
587 }
588 
589 
590 /** Get data size.
591  * @return size of the pure data part of the log entries
592  */
593 uint32_t
595 {
596  return __header->data_size;
597 }
598 
599 
600 /** Get start time.
601  * @return starting time of log
602  */
603 fawkes::Time &
605 {
606  return __start_time;
607 }
608 
609 
610 /** Get number of remaining entries.
611  * @return number of remaining entries
612  */
613 unsigned int
615 {
616  // we make this so "complicated" to be able to use it from a FAM handler
617  size_t entry_size = sizeof(bblog_entry_header) + __header->data_size;
618  long curpos = ftell(__f);
619  size_t fsize = file_size();
620  ssize_t sizediff = fsize - curpos;
621 
622  if (sizediff < 0) {
623  throw Exception("File %s shrank while reading it", __filename);
624  }
625 
626  return sizediff / entry_size;
627 }
628 
629 /** Get file size.
630  * @return total size of log file including all headers
631  */
632 size_t
634 {
635  struct stat fs;
636  if (fstat(fileno(__f), &fs) != 0) {
637  Exception e(errno, "Failed to stat file %s", __filename);
638  e.set_type_id("bblogfile-stat-failed");
639  throw e;
640  }
641  return fs.st_size;
642 }
Interface field iterator.
uint32_t rel_time_sec
time since start time, seconds
Definition: file.h:76
size_t file_size() const
Get file size.
Definition: bblogfile.cpp:633
BlackBoard instance factory.
File could not be opened.
Definition: system.h:53
BBLogger file header definition.
Definition: file.h:53
bool is_big_endian() const
Check if file is big endian.
Definition: bblogfile.cpp:534
File could not be read.
Definition: system.h:61
uint32_t data_size()
Get data size.
Definition: bblogfile.cpp:594
void rewind()
Rewind file to start.
Definition: bblogfile.cpp:254
Fawkes library namespace.
void set_num_entries(size_t num_entries)
Set number of entries.
Definition: bblogfile.cpp:304
const char * id() const
Get identifier of interface.
Definition: interface.cpp:661
const char * interface_id() const
Get interface ID.
Definition: bblogfile.cpp:573
A class for handling time.
Definition: time.h:91
Base class for all Fawkes BlackBoard interfaces.
Definition: interface.h:79
bool has_next()
Check if another entry is available.
Definition: bblogfile.cpp:267
unsigned char * interface_hash() const
Get interface hash.
Definition: bblogfile.cpp:584
void read_next()
Read next entry.
Definition: bblogfile.cpp:284
void print_entry(FILE *outf=stdout)
Print an entry.
Definition: bblogfile.cpp:453
const fawkes::Time & entry_offset() const
Get current entry offset.
Definition: bblogfile.cpp:514
BBLogFile(const char *filename, bool do_sanity_check)
Constructor.
Definition: bblogfile.cpp:101
const unsigned char * hash() const
Get interface hash.
Definition: interface.cpp:294
uint32_t num_data_items() const
Get number of data items in file.
Definition: bblogfile.cpp:543
void set_interface(fawkes::Interface *interface)
Set the internal interface.
Definition: bblogfile.cpp:491
const char * type() const
Get type of interface.
Definition: interface.cpp:651
Base class for exceptions in Fawkes.
Definition: exception.h:36
void print_info(const char *line_prefix="", FILE *outf=stdout)
Print file meta info.
Definition: bblogfile.cpp:415
BBLogger entry header.
Definition: file.h:75
static void repair_file(const char *filename)
Repair file.
Definition: bblogfile.cpp:327
fawkes::Time & start_time()
Get start time.
Definition: bblogfile.cpp:604
void read_index(unsigned int index)
Read entry at particular index.
Definition: bblogfile.cpp:237
fawkes::Interface * interface()
Get interface instance.
Definition: bblogfile.cpp:480
const char * scenario() const
Get scenario identifier.
Definition: bblogfile.cpp:553
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
uint32_t file_version() const
Get file version.
Definition: bblogfile.cpp:524
unsigned int remaining_entries()
Get number of remaining entries.
Definition: bblogfile.cpp:614
const char * interface_type() const
Get interface type.
Definition: bblogfile.cpp:563
uint32_t rel_time_usec
time since start time, microseconds
Definition: file.h:77
void set_type_id(const char *id)
Set exception type ID.
Definition: exception.cpp:292
~BBLogFile()
Destructor.
Definition: bblogfile.cpp:137
Class to easily access bblogger log files.
Definition: bblogfile.h:38
void append(const char *format,...)
Append messages to the message list.
Definition: exception.cpp:341