# from [037dbbf47c45e082f21ef1ab46401721f474037b] # to [6ea2aba0cee2ff0feaf6eacb93327e340fcc982b] --- +++ @@ -0,0 +1,711 @@ +// Copyright (C) 2002 Graydon Hoare +// +// This program is made available under the GNU GPL version 2.0 or +// greater. See the accompanying file COPYING for details. +// +// This program is distributed WITHOUT ANY WARRANTY; without even the +// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +// PURPOSE. + +#include "base.hh" +#include +#include +#include +#include +#include "vector.hh" + +#include +#include + +#include "lexical_cast.hh" +#include "constants.hh" +#include "platform.hh" +#include "file_io.hh" // make_dir_for +#include "sanity.hh" +#include "simplestring_xform.hh" + +using std::exception; +using std::locale; +using std::logic_error; +using std::ofstream; +using std::ostream; +using std::ostream_iterator; +using std::ostringstream; +using std::out_of_range; +using std::string; +using std::vector; + +using boost::format; +using boost::lexical_cast; + +// set by sanity::initialize +std::string const * prog_name_ptr; + +string +origin::type_to_string(origin::type t) +{ + switch (t) + { + case internal: + return string("internal"); + case network: + return string("network"); + case database: + return string("database"); + case system: + return string("system"); + case user: + return string("user"); + case workspace: + return string("workspace"); + case no_fault: + return string("general"); + default: + return string("invalid error type"); + } +} + +struct sanity::impl +{ + int verbosity; + // logically this should be "verbosity >= 1", but debug messages aren't + // captured for automate output and doing so would probably be an + // information leak in the case of remote_automate. So track debug-ness + // separately so it can be unchanged when a subcommand changes the + // verbosity level. + bool is_debug; + boost::circular_buffer logbuf; + std::string real_prog_name; + std::string filename; + std::string gasp_dump; + bool already_dumping; + std::vector musings; + + void (*out_of_band_function)(char channel, std::string const& text, void *opaque); + void *out_of_band_opaque; + + impl() : + verbosity(0), is_debug(false), logbuf(0xffff), + already_dumping(false), out_of_band_function(0), out_of_band_opaque(0) + {} +}; + +// debugging / logging system + +sanity::sanity() +{ + imp = 0; +} + +sanity::~sanity() +{ + if (imp) + delete imp; +} + +void +sanity::initialize(int argc, char ** argv, char const * lc_all) +{ + imp = new impl; + + // set up some marked strings, so even if our logbuf overflows, we'll get + // this data in a crash. This (and subclass overrides) are probably the + // only place PERM_MM should ever be used. + + string system_flavour; + get_system_flavour(system_flavour); + PERM_MM(system_flavour); + L(FL("started up on %s") % system_flavour); + + string cmdline_string; + { + ostringstream cmdline_ss; + for (int i = 0; i < argc; ++i) + { + if (i) + cmdline_ss << ", "; + cmdline_ss << '\'' << argv[i] << '\''; + } + cmdline_string = cmdline_ss.str(); + } + PERM_MM(cmdline_string); + L(FL("command line: %s") % cmdline_string); + + if (!lc_all) + lc_all = "n/a"; + PERM_MM(string(lc_all)); + L(FL("set locale: LC_ALL=%s") % lc_all); + + // find base name of executable and save in the prog_name global. note that + // this does not bother with conversion to utf8. + { + string av0 = argv[0]; + if (av0.size() > 4 && av0.rfind(".exe") == av0.size() - 4) + av0.erase(av0.size() - 4); + string::size_type last_slash = av0.find_last_of("/\\"); + if (last_slash != string::npos) + av0.erase(0, last_slash+1); + imp->real_prog_name = av0; + prog_name_ptr = &imp->real_prog_name; + } +} + +void +sanity::dump_buffer() +{ + I(imp); + if (!imp->filename.empty()) + { + ofstream out(imp->filename.c_str()); + if (!out) + { + try + { + make_dir_for(system_path(imp->filename, origin::internal)); + out.open(imp->filename.c_str()); + } + catch (...) + { + inform_message((FL("failed to create directory for %s") + % imp->filename).str()); + } + } + if (out) + { + copy(imp->logbuf.begin(), imp->logbuf.end(), + ostream_iterator(out)); + copy(imp->gasp_dump.begin(), imp->gasp_dump.end(), + ostream_iterator(out)); + inform_message((FL("wrote debugging log to %s\n" + "if reporting a bug, please include this file") + % imp->filename).str()); + } + else + inform_message((FL("failed to write debugging log to %s") + % imp->filename).str()); + } + else + inform_message("discarding debug log, because I have nowhere to write it\n" + "(maybe you want --debug or --dump?)"); +} + +int +sanity::set_verbosity(int level, bool allow_debug_change) +{ + I(imp); + int ret = imp->verbosity; + imp->verbosity = level; + + if (allow_debug_change) + { + imp->is_debug = (level >= 1); + + if (imp->is_debug) + { + // it is possible that some pre-setting-of-debug data + // accumulated in the log buffer (during earlier option processing) + // so we will dump it now + ostringstream oss; + vector lines; + copy(imp->logbuf.begin(), imp->logbuf.end(), ostream_iterator(oss)); + split_into_lines(oss.str(), lines); + for (vector::const_iterator i = lines.begin(); i != lines.end(); ++i) + inform_log((*i) + "\n"); + } + } + return ret; +} +void +sanity::set_debug() +{ + set_verbosity(1, true); +} +int +sanity::get_verbosity() const +{ + I(imp); + return imp->verbosity; +} + +void +sanity::set_dump_path(std::string const & path) +{ + I(imp); + if (imp->filename.empty()) + { + L(FL("setting dump path to %s") % path); + imp->filename = path; + } +} + +string +sanity::do_format(format_base const & fmt, char const * file, int line) +{ + try + { + return fmt.str(); + } + catch (exception & e) + { + inform_error((F("fatal: formatter failed on %s:%d: %s") + % file + % line + % e.what()).str()); + throw; + } +} + +bool +sanity::debug_p() +{ + if (!imp) + throw std::logic_error("sanity::debug_p called " + "before sanity::initialize"); + return imp->is_debug; +} + +void +sanity::log(plain_format const & fmt, + char const * file, int line) +{ + string str = do_format(fmt, file, line); + + if (str.size() > constants::log_line_sz) + { + str.resize(constants::log_line_sz); + if (str.at(str.size() - 1) != '\n') + str.at(str.size() - 1) = '\n'; + } + copy(str.begin(), str.end(), back_inserter(imp->logbuf)); + if (str[str.size() - 1] != '\n') + imp->logbuf.push_back('\n'); + + inform_log(str); +} + +void +sanity::progress(i18n_format const & i18nfmt, + char const * file, int line) +{ + string str = do_format(i18nfmt, file, line); + + if (maybe_write_to_out_of_band_handler('p', str)) + return; + + if (str.size() > constants::log_line_sz) + { + str.resize(constants::log_line_sz); + if (str.at(str.size() - 1) != '\n') + str.at(str.size() - 1) = '\n'; + } + copy(str.begin(), str.end(), back_inserter(imp->logbuf)); + if (str[str.size() - 1] != '\n') + imp->logbuf.push_back('\n'); + + inform_message(str); +} + +void +sanity::warning(i18n_format const & i18nfmt, + char const * file, int line) +{ + string str = do_format(i18nfmt, file, line); + + if (maybe_write_to_out_of_band_handler('w', str)) + return; + + if (str.size() > constants::log_line_sz) + { + str.resize(constants::log_line_sz); + if (str.at(str.size() - 1) != '\n') + str.at(str.size() - 1) = '\n'; + } + string str2 = "warning: " + str; + copy(str2.begin(), str2.end(), back_inserter(imp->logbuf)); + if (str[str.size() - 1] != '\n') + imp->logbuf.push_back('\n'); + + inform_warning(str); +} + +void +sanity::generic_failure(char const * expr, + origin::type caused_by, + i18n_format const & explain, + char const * file, int line) +{ + if (!imp) + throw std::logic_error("sanity::generic_failure occured " + "before sanity::initialize"); + + log(FL("Encountered an error while musing upon the following:"), + file, line); + gasp(); + log(FL("%s:%d: detected %s error, '%s' violated") + % file % line % origin::type_to_string(caused_by) % expr, + file, line); + + string prefix; + if (caused_by == origin::user) + { + prefix = _("misuse: "); + } + else + { + prefix = _("error: "); + } + string message; + prefix_lines_with(prefix, do_format(explain, file, line), message); + switch (caused_by) + { + case origin::database: + case origin::internal: + throw unrecoverable_failure(caused_by, message); + default: + throw recoverable_failure(caused_by, message); + } +} + +void +sanity::index_failure(char const * vec_expr, + char const * idx_expr, + unsigned long sz, + unsigned long idx, + char const * file, int line) +{ + char const * pattern + = N_("%s:%d: index '%s' = %d overflowed vector '%s' with size %d"); + if (!imp) + throw std::logic_error("sanity::index_failure occured " + "before sanity::initialize"); + if (debug_p()) + log(FL(pattern) % file % line % idx_expr % idx % vec_expr % sz, + file, line); + gasp(); + throw out_of_range((F(pattern) + % file % line % idx_expr % idx % vec_expr % sz).str()); +} + +// Last gasp dumps + +void +sanity::push_musing(MusingI const *musing) +{ + I(imp); + if (!imp->already_dumping) + imp->musings.push_back(musing); +} + +void +sanity::pop_musing(MusingI const *musing) +{ + I(imp); + if (!imp->already_dumping) + { + I(imp->musings.back() == musing); + imp->musings.pop_back(); + } +} + + +void +sanity::gasp() +{ + if (!imp) + return; + if (imp->already_dumping) + { + L(FL("ignoring request to give last gasp; already in process of dumping")); + return; + } + imp->already_dumping = true; + L(FL("saving current work set: %i items") % imp->musings.size()); + ostringstream out; + out << (F("Current work set: %i items") % imp->musings.size()) + << '\n'; // final newline is kept out of the translation + for (vector::const_iterator + i = imp->musings.begin(); i != imp->musings.end(); ++i) + { + string tmp; + try + { + (*i)->gasp(tmp); + out << tmp; + } + catch (logic_error) + { + out << tmp; + out << "\n"; + L(FL("ignoring error trigged by saving work set to debug log")); + } + catch (recoverable_failure) + { + out << tmp; + out << "\n"; + L(FL("ignoring error trigged by saving work set to debug log")); + } + } + imp->gasp_dump = out.str(); + L(FL("finished saving work set")); + if (debug_p()) + { + inform_log("contents of work set:"); + inform_log(imp->gasp_dump); + } + imp->already_dumping = false; +} + +void sanity::set_out_of_band_handler(void (*out_of_band_function)(char, std::string const&, void *), void *opaque_data) +{ + imp->out_of_band_function= out_of_band_function; + imp->out_of_band_opaque= opaque_data; +} + +bool sanity::maybe_write_to_out_of_band_handler(char channel, std::string const& str) +{ + if (imp->out_of_band_function) + { + (*imp->out_of_band_function)(channel, str, imp->out_of_band_opaque); + return true; + } + return false; +} + +template <> void +dump(string const & obj, string & out) +{ + out = obj; +} +template<> void +dump(char const * const & obj, string & out) +{ + out = obj; +} +template<> void +dump(bool const & obj, string & out) +{ + out = (obj ? "true" : "false"); +} +template <> void +dump(int const & val, string & out) +{ + out = lexical_cast(val); +} +template <> void +dump(unsigned int const & val, string & out) +{ + out = lexical_cast(val); +} +template <> void +dump(long const & val, string & out) +{ + out = lexical_cast(val); +} +template <> void +dump(unsigned long const & val, string & out) +{ + out = lexical_cast(val); +} +#ifdef USING_LONG_LONG +template <> void +dump(long long const & val, string & out) +{ + out = lexical_cast(val); +} +template <> void +dump(unsigned long long const & val, string & out) +{ + out = lexical_cast(val); +} +#endif + +void +sanity::print_var(std::string const & value, char const * var, + char const * file, int const line, char const * func) +{ + inform_message((FL("----- begin '%s' (in %s, at %s:%d)") + % var % func % file % line).str()); + inform_message(value); + inform_message((FL("----- end '%s' (in %s, at %s:%d)\n\n") + % var % func % file % line).str()); +} + +void MusingBase::gasp_head(string & out) const +{ + out = (boost::format("----- begin '%s' (in %s, at %s:%d)\n") + % name % func % file % line + ).str(); +} + +void MusingBase::gasp_body(const string & objstr, string & out) const +{ + out += (boost::format("%s%s" + "----- end '%s' (in %s, at %s:%d)\n") + % objstr + % (*(objstr.end() - 1) == '\n' ? "" : "\n") + % name % func % file % line + ).str(); +} + +const locale & +get_user_locale() +{ + // this is awkward because if LC_CTYPE is set to something the + // runtime doesn't know about, it will fail. in that case, + // the default will have to do. + static bool init = false; + static locale user_locale; + if (!init) + { + init = true; + try + { + user_locale = locale(""); + } + catch( ... ) + {} + } + return user_locale; +} + +struct +format_base::impl +{ + format fmt; + ostringstream oss; + + impl(impl const & other) : fmt(other.fmt) + {} + + impl & operator=(impl const & other) + { + if (&other != this) + { + fmt = other.fmt; + oss.str(string()); + } + return *this; + } + + impl(char const * pattern) + : fmt(pattern) + {} + impl(string const & pattern) + : fmt(pattern) + {} + impl(char const * pattern, locale const & loc) + : fmt(pattern, loc) + {} + impl(string const & pattern, locale const & loc) + : fmt(pattern, loc) + {} +}; + +format_base::format_base(format_base const & other) + : pimpl(other.pimpl ? new impl(*(other.pimpl)) : NULL) +{ + +} + +format_base::~format_base() +{ + delete pimpl; +} + +format_base & +format_base::operator=(format_base const & other) +{ + if (&other != this) + { + impl * tmp = NULL; + + try + { + if (other.pimpl) + tmp = new impl(*(other.pimpl)); + } + catch (...) + { + if (tmp) + delete tmp; + } + + if (pimpl) + delete pimpl; + + pimpl = tmp; + } + return *this; +} + +format_base::format_base(char const * pattern, bool use_locale) + : pimpl(use_locale ? new impl(pattern, get_user_locale()) + : new impl(pattern)) +{} + +format_base::format_base(std::string const & pattern, bool use_locale) + : pimpl(use_locale ? new impl(pattern, get_user_locale()) + : new impl(pattern)) +{} + +ostream & +format_base::get_stream() const +{ + return pimpl->oss; +} + +void +format_base::flush_stream() const +{ + pimpl->fmt % pimpl->oss.str(); + pimpl->oss.str(string()); +} + +void format_base::put_and_flush_signed(s64 const & s) const { pimpl->fmt % s; } +void format_base::put_and_flush_signed(s32 const & s) const { pimpl->fmt % s; } +void format_base::put_and_flush_signed(s16 const & s) const { pimpl->fmt % s; } +void format_base::put_and_flush_signed(s8 const & s) const { pimpl->fmt % s; } + +void format_base::put_and_flush_unsigned(u64 const & u) const { pimpl->fmt % u; } +void format_base::put_and_flush_unsigned(u32 const & u) const { pimpl->fmt % u; } +void format_base::put_and_flush_unsigned(u16 const & u) const { pimpl->fmt % u; } +void format_base::put_and_flush_unsigned(u8 const & u) const { pimpl->fmt % u; } + +void format_base::put_and_flush_float(float const & f) const { pimpl->fmt % f; } +void format_base::put_and_flush_double(double const & d) const { pimpl->fmt % d; } + +std::string +format_base::str() const +{ + return pimpl->fmt.str(); +} + +ostream & +operator<<(ostream & os, format_base const & fmt) +{ + return os << fmt.str(); +} + +i18n_format F(const char * str) +{ + return i18n_format(gettext(str)); +} + + +i18n_format FP(const char * str1, const char * strn, unsigned long count) +{ + return i18n_format(ngettext(str1, strn, count)); +} + +plain_format FL(const char * str) +{ + return plain_format(str); +} + +// Local Variables: +// mode: C++ +// fill-column: 76 +// c-file-style: "gnu" +// indent-tabs-mode: nil +// End: +// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: