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 
46 using 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  */
66 BBLogFile::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  */
102 BBLogFile::BBLogFile(const char *filename, bool do_sanity_check)
103 {
104  ctor(filename, do_sanity_check);
105 
106  interface_ = NULL;
107 }
108 
109 void
110 BBLogFile::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. */
156 void
157 BBLogFile::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  */
193 void
194 BBLogFile::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  */
237 void
238 BBLogFile::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  */
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 /** Check if another entry is available.
263  * @return true if a consecutive read_next() will succeed, false otherwise
264  */
265 bool
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  */
282 void
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  */
301 void
302 BBLogFile::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 {
309  bblog_file_header *header = (bblog_file_header *)h;
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  */
322 void
323 BBLogFile::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  */
333 void
334 BBLogFile::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  */
412 void
413 BBLogFile::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_,
449  interface_hash,
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  */
458 void
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  */
493 void
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  */
513 const fawkes::Time &
515 {
516  return entry_offset_;
517 }
518 
519 /** Get file version.
520  * @return file version
521  */
522 uint32_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  */
531 bool
533 {
534  return (header_->endianess == 1);
535 }
536 
537 /** Get number of data items in file.
538  * @return number of data items
539  */
540 uint32_t
542 {
543  return header_->num_data_items;
544 }
545 
546 /** Get scenario identifier.
547  * @return scenario identifier
548  */
549 const char *
551 {
552  return scenario_;
553 }
554 
555 /** Get interface type.
556  * @return type of logged interface
557  */
558 const char *
560 {
561  return interface_type_;
562 }
563 
564 /** Get interface ID.
565  * @return ID of logged interface
566  */
567 const char *
569 {
570  return interface_id_;
571 }
572 
573 /** Get interface hash.
574  * Hash of logged interface.
575  * @return interface hash
576  */
577 unsigned 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  */
586 uint32_t
588 {
589  return header_->data_size;
590 }
591 
592 /** Get start time.
593  * @return starting time of log
594  */
595 fawkes::Time &
597 {
598  return start_time_;
599 }
600 
601 /** Get number of remaining entries.
602  * @return number of remaining entries
603  */
604 unsigned 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  */
623 size_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:79
const char * type() const
Get type of interface.
Definition: interface.cpp:643
const unsigned char * hash() const
Get interface hash.
Definition: interface.cpp:298
const char * id() const
Get identifier of interface.
Definition: interface.cpp:652
A class for handling time.
Definition: time.h:93
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
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