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 <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 }