/* * freescope - Free source browser * Copyright (C) 2001 Olivier Deme * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * */ // FILE: dbuser.cpp /************/ /* INCLUDES */ /************/ #include #include #include #include #include #include #include #include #include #include #include "dbuser.h" #include "cli.h" #include "parser.h" #include "cursui.h" #include "fconfig.h" #ifdef erase // The curses library defines this macro... #undef erase #endif // erase extern "C" int scan_c(char*, size_t); /*********************/ /* NAMESPACE SYMBOLS */ /*********************/ using std::list; /********************/ /* GLOBAL VARIABLES */ /********************/ DoLater parse_later; /**************************/ /* STATIC INITIALIZATIONS */ /**************************/ DbUser* DbUser::m_instance = NULL; /************************/ /* FUNCTION DEFINITIONS */ /************************/ /* * FUNCTION: DbUser::DbUser * * DESCRIPTION: Constructor * * IN: * IN-OUT: * RETURN CODE: */ DbUser::DbUser () { string path("/usr/include"); string sfx(".h"); find_sys_headers(path, sfx); m_dbinf = DbInf::instance(); } /* * FUNCTION: DbUser::~DbUser * * DESCRIPTION: Destructor * * IN: * IN-OUT: * RETURN CODE: */ DbUser::~DbUser () { if (m_resFile != NULL) delete m_resFile; delete m_dbinf; } /* * FUNCTION: DbUser::create * * DESCRIPTION: Creates an instance. * * IN: type The type of DB user to create * IN-OUT: * RETURN CODE: A pointer to the newly created instance */ DbUser* DbUser::create (const ui_type_t type) { // Can be called only once assert(m_instance == NULL); // Create an instance switch (type) { case UI_NONE: m_instance = new DbUser; break; case UI_CLI: m_instance = new Cli; break; case UI_TK: case UI_CURSES: try { m_instance = new CursUI; } catch (DbUser_Exception& de) { // TODO: error handling LOG(ALL, FATAL) << "Cursui::Cursui error: " << de.what() << endl; exit(-1); } break; // TODO: default... } m_instance->m_resFile = NULL; /* * Initialize the global structure. This provides a hook for the C scanner * for accessing the database. */ parse_later = m_instance->store_file; if (FCONFIG->get_dumping_db() == true) { m_instance->m_dbinf->dump(); exit(0); } if (FCONFIG->get_update_on_startup() != 0) m_instance->m_dbinf->open(FCONFIG->get_db_file()); if (type != UI_NONE) m_instance->do_init(); else m_instance->update(); return m_instance; } /* * FUNCTION: DbUser::instance * * DESCRIPTION: return a pointer to the instance. * * IN: * IN-OUT: * RETURN CODE: A pointer to the instance */ DbUser* DbUser::instance () { assert(m_instance != 0); return m_instance; } /* * FUNCTION: DbUser::update * * DESCRIPTION: This function updates the database of symbols. * * THROW: * IN: * IN-OUT: * RETURN CODE: */ void DbUser::update () { ifstream in_files; streampos file_size; list mod_files; list::iterator it; set_status("Updating..."); in_files.open(FCONFIG->get_input_file_str()); if (in_files == 0) { in_files.open(DB_CSCOPE_INPUT_FILES); if (in_files == 0) { terminate(string("Couldn't open ") + FCONFIG->get_input_file()); } } in_files.seekg(0, ios::end); file_size = in_files.tellg(); in_files.seekg(0, ios::beg); // Process each file specified in input file while (in_files.tellg() < file_size) { getline(in_files, m_file); if (update(m_file) == false) mod_files.push_back(m_file); } set_status("Invalidating records. Please wait..."); m_dbinf->invalidate_records(mod_files); // Update modified files if (mod_files.size() != 0) { for (it = mod_files.begin(); it != mod_files.end(); ++it) { set_status("Scanning " + *it); m_dbinf->set_file(*it); if (::find(m_added_files.begin(), m_added_files.end(), *it) != m_added_files.end()) { m_do_headers = true; } else { m_do_headers = false; } scan_c((char*)it->c_str(), it->size()); } } // Do system header files parse_headers(); m_added_files.erase(m_added_files.begin(), m_added_files.end()); set_status("Flushing database..."); m_instance->m_dbinf->flush(); set_status("Update complete"); m_file.erase(); } /* * FUNCTION: DbUser::update * * DESCRIPTION: This function checks if a file has been modified since * the last databse update. And update the database in this * the case. * * THROW: * IN: filename The file to check * IN-OUT: * RETURN CODE: true if the database is up-to-date for this file. * false otherwise. */ bool DbUser::update (string& filename) { struct stat file_stat; time_t old_ts; bool ret = true; stat(filename.c_str(), &file_stat); if (S_ISDIR(file_stat.st_mode)) return true; if (m_dbinf->is_file_in_db(filename) == false) { m_dbinf->add_timestamp(filename, file_stat.st_mtime); m_added_files.insert(filename); return false; } // Get the time stamp from the database. if ((old_ts = m_dbinf->get_timestamp(filename)) == 0) { // The file is not in the database. Add it. m_dbinf->add_timestamp(filename, file_stat.st_mtime); m_file = filename; // Update database set_status ("Scanning " + m_file); m_dbinf->set_file((char*)m_file.c_str()); m_do_headers = true; scan_c((char*)m_file.c_str(), m_file.size()); } else if (old_ts != file_stat.st_mtime) { // The file has been modified m_dbinf->add_timestamp(filename, file_stat.st_mtime); m_file = filename; ret = false; } return ret; } /* * FUNCTION: DbUser::read_line * * DESCRIPTION: This function returns a specified line in a specified * file. * * THROW: * IN: filename The file name * lineno The line number * IN-OUT: * RETURN CODE: A string containing the searched line */ string DbUser::read_line (const string& filename, const line_nbr_t lineno) { int offset; string::size_type i; string line; static line_nbr_t old_lineno; // Do we need to open a new file? if ((m_resFile == NULL) || (m_file != filename)) { if (m_resFile != NULL) delete m_resFile; // Open file m_resFile = new ifstream(filename.c_str()); /* TODO: HANDLE ERROR !!!! */ assert(m_resFile); m_file = filename; offset = lineno-1; } else { // TODO: No great, improve the code so can search bacward for '\n' if ((offset = lineno - old_lineno - 1) <= 0) { offset = lineno - 1; m_resFile->seekg(0, ios::beg); } } // Go to line # try { for (int i = 0; i < offset; i++) { for (;;) { char c; c = m_resFile->get(); if (c == (char)-1) return ""; if (c == '\n') break; } } getline(*m_resFile, line); } catch (...) { LOG(ALL, FATAL) << "Exception caught" << endl; // TODO: HANDLE ERROR HERE abort(); } old_lineno = lineno; // Find the first non-blank character if (((i = line.find_first_not_of("\t ")) != string::npos) && ( i != 0)) { line.erase(0, i); } // Replace all tabs by white spaces while ((i = line.find('\t')) != string::npos) line.replace(i, 1, " "); // Replace all line feed by white spaces while ((i = line.find('\r')) != string::npos) line.erase(i); return line; } /* * FUNCTION: DbUser::do_query * * DESCRIPTION: Makes a query to the database and displays the result. * * THROW: * IN: type The the type of query * findme The symbol or text to look for. * IN-OUT: * RETURN CODE: */ void DbUser::do_query (const query_type_t type, string& findme) { switch(type) { case QUERY_SYMBOL: m_dbinf->find(findme, ANY, m_result); break; case QUERY_DEFINITION: m_dbinf->find(findme, DEFINITION, m_result); break; case QUERY_CALLED: m_dbinf->find(findme, FUNC_CALLED, m_result); break; case QUERY_CALL: m_dbinf->find(findme, FUNC_CALL, m_result); break; case QUERY_TEXT: find_text(findme); break; case QUERY_REGEXP: find_regexp(findme); break; case QUERY_FILE: find_file(findme); break; case QUERY_INCLUDE: m_dbinf->find(findme, INCLUDE, m_result); break; case QUERY_NOT_SUPPORTED: return; } RemoveDuplicates(); display_result(); } /* * FUNCTION: DbUser::RemoveDuplicates * * DESCRIPTION: This function goes through all the results and remove the * duplicates. Two results are duplicated if their file name and * line number are identical. * * THROW: * IN: * IN-OUT: * RETURN CODE: The resulting number of results */ void DbUser::RemoveDuplicates () { list::iterator it; unsigned int lineno1; unsigned int lineno2; string filename1; string filename2; if (m_result.size() < 2) return ; it = m_result.begin(); lineno1 = it->lineno; filename1 = it->file; ++it; while (it != m_result.end()) { lineno2 = it->lineno; filename2 = it->file; if ((lineno1 == lineno2) && (filename1 == filename2)) { list::iterator new_it; /* Duplicates! */ new_it = --it; ++it; m_result.erase(it); it = new_it; } lineno1 = lineno2; filename1 = filename2; ++it; } return; } /* * FUNCTION: DbUser:find_sys_headers * * DESCRIPTION: This function finds in the specified directory a list of * file with the given suffix and store them in a set. * Each time a file is parsed, it is removed from the set * so that it cannot be parsed again. * * THROW: * IN: path The path to the directory to be searched * sfx The suffix of the files to search * IN-OUT: * RETURN CODE: */ void DbUser::find_sys_headers (string& path, string& sfx) { struct stat statbuf; struct dirent* dirp; DIR* dp; if (stat(path.c_str(), &statbuf) < 0) { // TODO: Error handling return; } if (S_ISREG(statbuf.st_mode)) { // This is a normal file. Add it in the set if the suffixes match. if (path.compare(sfx, path.size() - sfx.size()) == 0) m_headers.insert(path); return; } else if (S_ISDIR(statbuf.st_mode) == 0) return; // Not a directory // This is a directory // Open the directory if ((dp = opendir(path.c_str())) == NULL) { // TODO: Error handling return; } // Read directory while ((dirp = readdir(dp)) != NULL) { if ((strcmp(dirp->d_name, ".") == 0) || (strcmp(dirp->d_name, "..") == 0)) { continue; } // Append name after slash path.append("/"); path.append(dirp->d_name); find_sys_headers(path, sfx); path.erase(path.size() - strlen(dirp->d_name) - 1); } // Close directory if (closedir(dp) < 0) { // TODO: Error handling } } /* * FUNCTION: DbUser::store_file * * DESCRIPTION: This function checks if the header is a system header file and * if so, registers it for later parsing * * THROW: * IN: header The header file to be checked * length The size in character of the header * IN-OUT: * RETURN CODE: 1 if the file needs to be parsed, 0 otherwise */ void DbUser::store_file (char* header, size_t length) { string path(header, 0, length); if (m_instance->m_do_headers == false) return; path.insert(0, "/usr/include/"); if (m_instance->m_headers.find(path) != m_instance->m_headers.end()) m_instance->m_headers_found.insert(path); } /* * FUNCTION: DbUser::parse_headers * * DESCRIPTION: This function calls the parser for all system headers * encountered during previous parsing. * * THROW: * IN: * IN-OUT: * RETURN CODE: */ void DbUser::parse_headers () { while(m_instance->m_headers_found.size() != 0) { set::iterator it; it = m_instance->m_headers_found.begin(); // Check if this header file has not been parsed if (m_instance->m_headers.find(*it) == m_instance->m_headers.end()) continue; set_status("Scanning " + *it); // Remove this header file from the set so that it can't be parsed again m_instance->m_headers.erase(*it); // Parse header file m_instance->m_file = *it; m_dbinf->set_file((char*)it->c_str()); m_do_headers = true; scan_c((char*)it->c_str(), it->size()); m_instance->m_headers_found.erase(it); } } /* * FUNCTION: DbUser::find_text * * DESCRIPTION: This function is called when some text needs to be * found inside the input files. * The m_results attribute is initialized with the * found occurences. * * IN: text The text to look for. * IN-OUT: * RETURN CODE: */ void DbUser::find_text (string& text) { /* * We are going to call "find_regexp", so we need to prepend all * regular expression characters with a '\'. */ string::size_type pos = 0; while ((pos = text.find_first_of(".*[\\^$+?|()", pos)) != string::npos) { text.insert(pos++, 1, '\\'); pos += 2; } find_regexp(text); } /* * FUNCTION: DbUser::find_regexp * * DESCRIPTION: This function searches all files for a regular expression. * It stores the result in the result list. * * IN: regexp The pattern to look for. * IN-OUT: * RETURN CODE: */ void DbUser::find_regexp (const string& regexp) { ifstream in_files; streampos size; match_t match; string tmp; regex_t regx; int err; if (m_result.empty() == false) m_result.erase(m_result.begin(), m_result.end()); in_files.open(FCONFIG->get_input_file_str()); if (in_files == 0) { in_files.open(DB_CSCOPE_INPUT_FILES); if (in_files == 0) { set_status("Couldn't open " + FCONFIG->get_input_file()); return; } } // Compile the regular expression if ((err = regcomp(®x, regexp.c_str(), REG_EXTENDED | REG_NOSUB)) != 0) { set_status("Invalid regular expression"); return; } in_files.seekg(0, ios::end); size = in_files.tellg(); in_files.seekg(0, ios::beg); while (in_files.tellg() < size) { string file; ifstream in; streampos in_size; line_nbr_t lineno = 1; vector buffer; vector::iterator it; getline(in_files, file); in.open(file.c_str()); if (in == 0) { LOG(ALL, ERROR) << "Couldn't open " << file << endl; regfree(®x); return; } // Get size of file. in.seekg(0, ios::end); in_size = in.tellg(); in.seekg(0, ios::beg); if (in_size == 0) { in.close(); continue; } buffer.resize(in_size); in.read(&buffer[0], in_size + 1); if (buffer[in_size - 1] != '\n') buffer[in_size] = '\n'; it = buffer.begin(); // Process each line of the file for(;;) { vector::iterator it2; // Find the end of the line. it2 = ::find(it, buffer.end(), '\n'); if (it2 == buffer.end()) break; tmp.assign(it, it2 - it); it = it2 + 1; // Search pattern if (regexec(®x, tmp.c_str(), 0, NULL, 0) == 0) { // This is a match! match.file = file; match.lineno = lineno; m_result.push_back(match); } ++lineno; } in.close(); } // while loop regfree(®x); } /* * FUNCTION: DbUser::find_file * * DESCRIPTION: This function fills the result list with the files that contain * a substring that matches the given argument. * * IN: file The file to search. * IN-OUT: * RETURN CODE: */ void DbUser::find_file (string& file) { ifstream in_files; streampos size; string tmp; match_t match; regex_t regx; int err; match.lineno = 0; /* * Check if file starts with "*". If so, the user probably wants to find * all files with given suffix. */ if (file.at(0) == '*') { file = '.' + file; file += '$'; } if (m_result.empty() == false) m_result.erase(m_result.begin(), m_result.end()); // Compile the file as a regular expression if ((err = regcomp(®x, file.c_str(), REG_EXTENDED | REG_NOSUB)) != 0) { set_status("Invalid regular expression for file search"); return; } in_files.open(FCONFIG->get_input_file_str()); if (in_files == 0) { in_files.open(DB_CSCOPE_INPUT_FILES); if (in_files == 0) { set_status("Couldn't open " + FCONFIG->get_input_file()); regfree(®x); return; } } in_files.seekg(0, ios::end); size = in_files.tellg(); in_files.seekg(0, ios::beg); while (in_files.tellg() < size) { getline(in_files, tmp); // Does it match? if (regexec(®x, tmp.c_str(), 0, NULL, 0) == 0) { match.file = tmp; m_result.push_back(match); } } regfree(®x); } /* * FUNCTION: DbUser::next_completion * * DESCRIPTION: This function queries the database for the next symbol * completion. * * IN: stub The beginning of the symbol. * index The index of the match * total The total number of completions * IN-OUT: * RETURN CODE: The next completion */ string DbUser::next_completion (const string& stub, int& index, int& total) { return m_dbinf->next_completion(stub, index, total); } /* * FUNCTION: DbUser::prev_completion * * DESCRIPTION: This function queries the database for the previous symbol * completion. * * IN: stub The beginning of the symbol. * index The index of the match * total The total number of completions * IN-OUT: * RETURN CODE: The previous completion */ string DbUser::prev_completion (const string& stub, int& index, int& total) { return m_dbinf->prev_completion(stub, index, total); }