bblogfile.cpp

00001 
00002 /***************************************************************************
00003  *  bblogfile.cpp - BlackBoard log file access convenience class
00004  *
00005  *  Created: Sun Feb 21 11:27:41 2010
00006  *  Copyright  2006-2010  Tim Niemueller [www.niemueller.de]
00007  *
00008  ****************************************************************************/
00009 
00010 /*  This program is free software; you can redistribute it and/or modify
00011  *  it under the terms of the GNU General Public License as published by
00012  *  the Free Software Foundation; either version 2 of the License, or
00013  *  (at your option) any later version.
00014  *
00015  *  This program is distributed in the hope that it will be useful,
00016  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00017  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00018  *  GNU Library General Public License for more details.
00019  *
00020  *  Read the full text in the LICENSE.GPL file in the doc directory.
00021  */
00022 
00023 #include "bblogfile.h"
00024 
00025 #include <core/exceptions/system.h>
00026 #include <blackboard/internal/instance_factory.h>
00027 
00028 #include <cstdlib>
00029 #include <cerrno>
00030 #include <cstring>
00031 #ifdef __FreeBSD__
00032 #  include <sys/endian.h>
00033 #else
00034 #  include <endian.h>
00035 #endif
00036 #include <arpa/inet.h>
00037 #include <sys/types.h>
00038 #include <sys/stat.h>
00039 #include <unistd.h>
00040 #include <sys/mman.h>
00041 
00042 using namespace fawkes;
00043 
00044 /** @class BBLogFile "bblogfile.h"
00045  * Class to easily access bblogger log files.
00046  * This class provides an easy way to interact with bblogger log files.
00047  * @author Tim Niemueller
00048  */
00049 
00050 /** Constructor.
00051  * Opens the given file and performs basic sanity checks.
00052  * @param filename log file to open
00053  * @param interface optional interface instance which must match the data
00054  * from the log file. Read methods will store read data in this interface
00055  * instance. If no interface is given an instance is created that is not
00056  * tied to a blackboard.
00057  * @param do_sanity_check true to perform a sanity check on the file on
00058  * opening. Turn this off only if you know what you are doing.
00059  * @exception CouldNotOpenFileException thrown if file cannot be opened
00060  * @exception FileReadException some error occured while reading data from
00061  */
00062 BBLogFile::BBLogFile(const char *filename, fawkes::Interface *interface,
00063                      bool do_sanity_check)
00064 {
00065   ctor(filename, do_sanity_check);
00066 
00067   if (interface) {
00068     __instance_factory = NULL;
00069     __interface = interface;
00070     if ((strcmp(__interface->type(), __interface_type) != 0) ||
00071         (strcmp(__interface->id(), __interface_id) != 0)) {
00072       fclose(__f);
00073       free(__filename);
00074       free(__scenario);
00075       free(__interface_type);
00076       free(__interface_id);
00077       throw Exception("Interface UID %s does not match expected %s:%s",
00078                       __interface->uid(), __interface_type, __interface_id);
00079     }
00080   } else {
00081     __instance_factory = new BlackBoardInstanceFactory();
00082     __interface = __instance_factory->new_interface_instance(__interface_type,
00083                                                              __interface_id);
00084   }
00085 }
00086 
00087 
00088 /** Constructor.
00089  * Opens the given file and performs basic sanity checks.
00090  * No internal interface is created. You must take care to set it using
00091  * set_interface() before any reading is done.
00092  * @param filename log file to open
00093  * @param do_sanity_check true to perform a sanity check on the file on
00094  * opening. Turn this off only if you know what you are doing.
00095  * @exception CouldNotOpenFileException thrown if file cannot be opened
00096  * @exception FileReadException some error occured while reading data from
00097  */
00098 BBLogFile::BBLogFile(const char *filename, bool do_sanity_check)
00099 {
00100   ctor(filename, do_sanity_check);
00101 
00102   __instance_factory = NULL;
00103   __interface        = NULL;
00104 }
00105 
00106 
00107 void
00108 BBLogFile::ctor(const char *filename, bool do_sanity_check)
00109 {
00110   __f = fopen(filename, "r");
00111   if (!__f) {
00112     throw CouldNotOpenFileException(filename, errno);
00113   }
00114 
00115   __filename = strdup(filename);
00116   __header   = (bblog_file_header *)malloc(sizeof(bblog_file_header));
00117 
00118   try {
00119     read_file_header();
00120     if (do_sanity_check)  sanity_check();
00121   } catch (Exception &e) {
00122     free(__filename);
00123     free(__scenario);
00124     free(__interface_type);
00125     free(__interface_id);
00126     fclose(__f);
00127     throw;
00128   }
00129 
00130   __ifdata = malloc(__header->data_size);
00131 }
00132 
00133 /** Destructor. */
00134 BBLogFile::~BBLogFile()
00135 {
00136   if (__instance_factory) {
00137     __instance_factory->delete_interface_instance(__interface);
00138     delete __instance_factory;
00139   }
00140 
00141   fclose(__f);
00142 
00143   free(__filename);
00144   free(__scenario);
00145   free(__interface_type);
00146   free(__interface_id);
00147 
00148   free(__header);
00149   free(__ifdata);
00150 }
00151 
00152 
00153 /** Read file header. */
00154 void
00155 BBLogFile::read_file_header()
00156 {
00157   uint32_t magic;
00158   uint32_t version;
00159   if ((fread(&magic, sizeof(uint32_t), 1, __f) == 1) &&
00160       (fread(&version, sizeof(uint32_t), 1, __f) == 1) ) {
00161     if ( (ntohl(magic) == BBLOGGER_FILE_MAGIC) &&
00162          (ntohl(version) == BBLOGGER_FILE_VERSION) ) {
00163       ::rewind(__f);
00164       if (fread(__header, sizeof(bblog_file_header), 1, __f) != 1) {
00165         throw FileReadException(__filename, errno, "Failed to read file header");
00166       }
00167     } else {
00168       throw Exception("File magic/version %X/%u does not match (expected %X/%u)",
00169                       ntohl(magic), ntohl(version),
00170                       BBLOGGER_FILE_VERSION, BBLOGGER_FILE_MAGIC);
00171     }
00172   } else {
00173     throw Exception(__filename, errno, "Failed to read magic/version from file");
00174   }
00175 
00176   __scenario = strndup(__header->scenario, BBLOG_SCENARIO_SIZE);
00177   __interface_type = strndup(__header->interface_type, BBLOG_INTERFACE_TYPE_SIZE);
00178   __interface_id = strndup(__header->interface_id, BBLOG_INTERFACE_ID_SIZE);
00179 
00180   __start_time.set_time(__header->start_time_sec, __header->start_time_usec);
00181 }
00182 
00183 
00184 /** Perform sanity checks.
00185  * This methods performs some sanity checks like:
00186  * - check if number of items is 0
00187  * - check if number of items and file size match
00188  * - check endianess of file and system
00189  * - check if file seems to be truncated
00190  */
00191 void
00192 BBLogFile::sanity_check()
00193 {
00194   if (__header->num_data_items == 0) {
00195     Exception e("File %s does not specify number of data items", __filename);
00196     e.set_type_id("bblogfile-num-items-zero");
00197     throw e;
00198   }
00199 
00200   struct stat fs;
00201   if (fstat(fileno(__f), &fs) != 0) {
00202     Exception e(errno, "Failed to stat file %s", __filename);
00203     e.set_type_id("bblogfile-stat-failed");
00204     throw e;
00205   }
00206 
00207   long int expected_size = sizeof(bblog_file_header)
00208     + __header->num_data_items * __header->data_size
00209     + __header->num_data_items * sizeof(bblog_entry_header);
00210   if (expected_size != fs.st_size) {
00211     Exception e("Size of file %s does not match expectation "
00212                 "(actual: %li, actual: %li)",
00213                 __filename, expected_size, (long int)fs.st_size);
00214     e.set_type_id("bblogfile-file-size-mismatch");
00215     throw e;
00216   }
00217 
00218 #if __BYTE_ORDER == __LITTLE_ENDIAN
00219   if (__header->endianess == 1)
00220 #else
00221   if (__header->endianess == 0)
00222 #endif
00223   {
00224     Exception e("File %s has incompatible endianess", __filename);
00225     e.set_type_id("bblogfile-endianess-mismatch");
00226     throw e;
00227   }
00228 }
00229 
00230 /** Read entry at particular index.
00231  * @param index index of entry, 0-based
00232  */
00233 void
00234 BBLogFile::read_index(unsigned int index)
00235 {
00236   long offset = sizeof(bblog_file_header)
00237     + (sizeof(bblog_entry_header) + __header->data_size) * index;
00238 
00239   if (fseek(__f, offset, SEEK_SET) != 0) {
00240     throw Exception(errno, "Cannot seek to index %u", index);
00241   }
00242 
00243   read_next();
00244 }
00245 
00246 
00247 /** Rewind file to start.
00248  * This moves the file cursor immediately before the first entry.
00249  */
00250 void
00251 BBLogFile::rewind()
00252 {
00253   if (fseek(__f, sizeof(bblog_file_header), SEEK_SET) != 0) {
00254     throw Exception(errno, "Cannot reset file");
00255   }
00256   __entry_offset.set_time(0, 0);
00257 }
00258 
00259 
00260 /** Check if another entry is available.
00261  * @return true if a consecutive read_next() will succeed, false otherwise
00262  */
00263 bool
00264 BBLogFile::has_next()
00265 {
00266   // we always re-test to support continuous file watching
00267   clearerr(__f);
00268   if (getc(__f) == EOF) {
00269     return false;
00270   } else {
00271     fseek(__f, -1, SEEK_CUR);
00272     return true;
00273   }
00274 }
00275 
00276 /** Read next entry.
00277  * @exception Exception thrown if reading fails, for example because no more
00278  * entries are left.
00279  */
00280 void
00281 BBLogFile::read_next()
00282 {
00283   bblog_entry_header entryh;
00284 
00285   if ( (fread(&entryh, sizeof(bblog_entry_header), 1, __f) == 1) &&
00286        (fread(__ifdata, __header->data_size, 1, __f) == 1) ) {
00287     __entry_offset.set_time(entryh.rel_time_sec, entryh.rel_time_usec);
00288     __interface->set_from_chunk(__ifdata);
00289   } else {
00290     throw Exception("Cannot read interface data");
00291   }
00292 }
00293 
00294 
00295 /** Set number of entries.
00296  * Set the number of entries in the file. Attention, this is only to be used
00297  * by the repair() method.
00298  * @param num_entries number of entries
00299  */
00300 void
00301 BBLogFile::set_num_entries(size_t num_entries)
00302 {
00303 #if _POSIX_MAPPED_FILES
00304   void *h = mmap(NULL, sizeof(bblog_file_header), PROT_WRITE, MAP_SHARED,
00305                    fileno(__f), 0);
00306   if (h == MAP_FAILED) {
00307     throw Exception(errno, "Failed to mmap log, not updating number of data items");
00308   } else {
00309     bblog_file_header *header = (bblog_file_header *)h;
00310     header->num_data_items = num_entries;
00311     munmap(h, sizeof(bblog_file_header));
00312   }
00313 #else
00314   throw Exception("Cannot set number of entries, mmap not available.");
00315 #endif    
00316 }
00317 
00318 
00319 /** Repair file.
00320  * @param filename file to repair
00321  * @see repair()
00322  */
00323 void
00324 BBLogFile::repair_file(const char *filename)
00325 {
00326   BBLogFile file(filename, NULL, false);
00327   file.repair();
00328 }
00329 
00330 
00331 /** This tries to fix files which are in an inconsistent state.
00332  * On success, an exception is thrown with a type_id of "repair-success", which
00333  * will have an entry for each successful operation.
00334  */
00335 void
00336 BBLogFile::repair()
00337 {
00338   FILE *f = freopen(__filename, "r+", __f);
00339   if (! f) {
00340     throw Exception("Reopening file %s with new mode failed", __filename);
00341   }
00342   __f = f;
00343 
00344   bool repair_done = false;
00345 
00346   Exception success("Successfully repaired file");
00347   success.set_type_id("repair-success");
00348 
00349 #if __BYTE_ORDER == __LITTLE_ENDIAN
00350   if (__header->endianess == 1)
00351 #else
00352   if (__header->endianess == 0)
00353 #endif
00354   {
00355     throw Exception("File %s has incompatible endianess. Cannot repair.",
00356                     __filename);
00357   }
00358 
00359   struct stat fs;
00360   if (fstat(fileno(__f), &fs) != 0) {
00361     throw Exception(errno, "Failed to stat file %s", __filename);
00362   }
00363 
00364   size_t entry_size = sizeof(bblog_entry_header) + __header->data_size;
00365   size_t all_entries_size = fs.st_size - sizeof(bblog_file_header);
00366   size_t num_entries = all_entries_size / entry_size;
00367   size_t extra_bytes = all_entries_size % entry_size;
00368 
00369   if (extra_bytes != 0) {
00370     success.append("FIXING: errorneous bytes at end of file, "
00371                    "truncating by %zu b", extra_bytes);
00372     if (ftruncate(fileno(__f), fs.st_size - extra_bytes) == -1) {
00373       throw Exception(errno, "Failed to truncate file %s", __filename);
00374     }
00375     all_entries_size -= extra_bytes;
00376     extra_bytes = 0;
00377     if (fstat(fileno(__f), &fs) != 0) {
00378       throw Exception(errno, "Failed to update information of file %s "
00379                       "after truncate", __filename);
00380     }
00381     repair_done = true;
00382   }
00383   if (__header->num_data_items == 0) {
00384     success.append("FIXING: header of file %s has 0 data items, setting to %zu.",
00385                    __filename, num_entries);
00386     set_num_entries(num_entries);
00387     repair_done = true;
00388   } else if (__header->num_data_items != num_entries) {
00389     success.append("FIXING: header has %u data items, but expecting %zu, setting",
00390                    __header->num_data_items, num_entries);
00391     set_num_entries(num_entries);
00392     repair_done = true;
00393   }
00394 
00395   f = freopen(__filename, "r", __f);
00396   if (! f) {
00397     throw Exception("Reopening file %s with read-only mode failed", __filename);
00398   }
00399   __f = f;
00400 
00401   if (repair_done) {
00402     throw success;
00403   }
00404 }
00405 
00406 
00407 /** Print file meta info.
00408  * @param line_prefix a prefix printed before each line
00409  * @param outf file handle to print to
00410  */
00411 void
00412 BBLogFile::print_info(const char *line_prefix, FILE *outf)
00413 {
00414   char interface_hash[BBLOG_INTERFACE_HASH_SIZE * 2 + 1];
00415 
00416   for (unsigned int i = 0; i < BBLOG_INTERFACE_HASH_SIZE; ++i) {
00417     snprintf(&interface_hash[i*2], 3, "%02X", __header->interface_hash[i]);
00418   }
00419 
00420   struct stat fs;
00421   if (fstat(fileno(__f), &fs) != 0) {
00422     throw Exception(errno, "Failed to get stat file");
00423   }
00424 
00425   fprintf(outf,
00426           "%sFile version: %-10u  Endianess: %s Endian\n"
00427           "%s# data items: %-10u  Data size: %u bytes\n"
00428           "%sHeader size:  %zu bytes   File size: %li bytes\n"
00429           "%s\n"
00430           "%sScenario:   %s\n"
00431           "%sInterface:  %s::%s (%s)\n"
00432           "%sStart time: %s\n",
00433           line_prefix, ntohl(__header->file_version),
00434           (__header->endianess == 1) ? "Big" : "Little",
00435           line_prefix, __header->num_data_items, __header->data_size,
00436           line_prefix, sizeof(bblog_file_header), (long int)fs.st_size,
00437           line_prefix,
00438           line_prefix, __scenario,
00439           line_prefix, __interface_type, __interface_id, interface_hash,
00440           line_prefix, __start_time.str());
00441 
00442 }
00443 
00444 
00445 /** Print an entry.
00446  * Verbose print of a single entry.
00447  * @param outf file handle to print to
00448  */
00449 void
00450 BBLogFile::print_entry(FILE *outf)
00451 {
00452   fprintf(outf, "Time Offset: %f\n", __entry_offset.in_sec());
00453 
00454   InterfaceFieldIterator i;
00455   for (i = __interface->fields(); i != __interface->fields_end(); ++i) {
00456     char *typesize;
00457     if (i.get_length() > 1) {
00458       if (asprintf(&typesize, "%s[%zu]", i.get_typename(), i.get_length()) == -1) {
00459         throw Exception("Out of memory");
00460       }
00461     } else {
00462       if (asprintf(&typesize, "%s", i.get_typename()) == -1) {
00463         throw Exception("Out of memory");
00464       }
00465     }
00466     fprintf(outf, "%-16s %-18s: %s\n",
00467             i.get_name(), typesize, i.get_value_string());
00468     free(typesize);
00469   }
00470 }
00471 
00472 
00473 /** Get interface instance.
00474  * @return internally used interface
00475  */
00476 fawkes::Interface *
00477 BBLogFile::interface()
00478 {
00479   return __interface;
00480 }
00481 
00482 
00483 /** Set the internal interface.
00484  * @param interface an interface matching the type and ID given in the
00485  * log file.
00486  */
00487 void
00488 BBLogFile::set_interface(fawkes::Interface *interface)
00489 {
00490   if ( (strcmp(interface->type(), __interface_type) == 0) &&
00491        (strcmp(interface->id(), __interface_id) == 0) &&
00492        (memcmp(interface->hash(), __header->interface_hash,
00493                __INTERFACE_HASH_SIZE) == 0) ) {
00494     if (__instance_factory) {
00495       __instance_factory->delete_interface_instance(__interface);
00496       delete __instance_factory;
00497       __instance_factory = NULL;
00498     }
00499     __interface = interface;
00500   } else {
00501     throw TypeMismatchException("Interfaces incompatible");
00502   }
00503 }
00504 
00505 
00506 /** Get current entry offset.
00507  * @return offset from start time of current entry (may be 0 if no entry has
00508  * been read, yet, or after rewind()).
00509  */
00510 const fawkes::Time &
00511 BBLogFile::entry_offset() const
00512 {
00513   return __entry_offset;
00514 }
00515 
00516 
00517 /** Get file version.
00518  * @return file version
00519  */
00520 uint32_t
00521 BBLogFile::file_version() const
00522 {
00523   return ntohl(__header->file_version);
00524 }
00525 
00526 
00527 /** Check if file is big endian.
00528  * @return true if file is big endian, false otherwise
00529  */
00530 bool
00531 BBLogFile::is_big_endian() const
00532 {
00533   return (__header->endianess == 1);
00534 }
00535 
00536 /** Get number of data items in file.
00537  * @return number of data items
00538  */
00539 uint32_t
00540 BBLogFile::num_data_items() const
00541 {
00542   return __header->num_data_items;
00543 }
00544 
00545 
00546 /** Get scenario identifier.
00547  * @return scenario identifier
00548  */
00549 const char *
00550 BBLogFile::scenario() const
00551 {
00552   return __scenario;
00553 }
00554 
00555 
00556 /** Get interface type.
00557  * @return type of logged interface
00558  */
00559 const char *
00560 BBLogFile::interface_type() const
00561 {
00562   return __interface_type;
00563 }
00564 
00565 
00566 /** Get interface ID.
00567  * @return ID of logged interface
00568  */
00569 const char *
00570 BBLogFile::interface_id() const
00571 {
00572   return __interface_id;
00573 }
00574 
00575 
00576 /** Get interface hash.
00577  * Hash of logged interface.
00578  * @return interface hash
00579  */
00580 unsigned char *
00581 BBLogFile::interface_hash() const
00582 {
00583   return __header->interface_hash;
00584 }
00585 
00586 
00587 /** Get data size.
00588  * @return size of the pure data part of the log entries
00589  */
00590 uint32_t
00591 BBLogFile::data_size()
00592 {
00593   return __header->data_size;
00594 }
00595 
00596 
00597 /** Get start time.
00598  * @return starting time of log
00599  */
00600 fawkes::Time &
00601 BBLogFile::start_time()
00602 {
00603   return __start_time;
00604 }
00605 
00606 
00607 /** Get number of remaining entries.
00608  * @return number of remaining entries
00609  */
00610 unsigned int
00611 BBLogFile::remaining_entries()
00612 {
00613   // we make this so "complicated" to be able to use it from a FAM handler
00614   size_t entry_size = sizeof(bblog_entry_header) + __header->data_size;
00615   long   curpos     = ftell(__f);
00616   size_t fsize      = file_size();
00617   size_t sizediff   = fsize - curpos;
00618 
00619   if (sizediff < 0) {
00620     throw Exception("File %s shrank while reading it", __filename);
00621   }
00622 
00623   return sizediff / entry_size;
00624 }
00625 
00626 /** Get file size.
00627  * @return total size of log file including all headers
00628  */
00629 size_t
00630 BBLogFile::file_size() const
00631 {
00632   struct stat fs;
00633   if (fstat(fileno(__f), &fs) != 0) {
00634     Exception e(errno, "Failed to stat file %s", __filename);
00635     e.set_type_id("bblogfile-stat-failed");
00636     throw e;
00637   }
00638   return fs.st_size;
00639 }

Generated on 1 Mar 2011 for Fawkes API by  doxygen 1.6.1