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