monotone

monotone Mtn Source Tree

Root/src/sanity.cc

1// Copyright (C) 2002 Graydon Hoare <graydon@pobox.com>
2//
3// This program is made available under the GNU GPL version 2.0 or
4// greater. See the accompanying file COPYING for details.
5//
6// This program is distributed WITHOUT ANY WARRANTY; without even the
7// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
8// PURPOSE.
9
10#include "base.hh"
11#include <algorithm>
12#include <iterator>
13#include <fstream>
14#include <sstream>
15#include "vector.hh"
16
17#include <boost/format.hpp>
18
19// circular_buffer is not in Boost 1.34; it's in monotone/boost.
20#include "boost/circular_buffer.hpp"
21
22#include "lexical_cast.hh"
23#include "constants.hh"
24#include "platform.hh"
25#include "file_io.hh" // make_dir_for
26#include "sanity.hh"
27#include "simplestring_xform.hh"
28
29using std::exception;
30using std::locale;
31using std::logic_error;
32using std::ofstream;
33using std::ostream;
34using std::ostream_iterator;
35using std::ostringstream;
36using std::out_of_range;
37using std::string;
38using std::vector;
39
40using boost::format;
41using boost::lexical_cast;
42
43// set by sanity::initialize
44std::string const * prog_name_ptr;
45
46string
47origin::type_to_string(origin::type t)
48{
49 switch (t)
50 {
51 case internal:
52 return string("internal");
53 case network:
54 return string("network");
55 case database:
56 return string("database");
57 case system:
58 return string("system");
59 case user:
60 return string("user");
61 case workspace:
62 return string("workspace");
63 case no_fault:
64 return string("general");
65 default:
66 return string("invalid error type");
67 }
68}
69
70struct sanity::impl
71{
72 int verbosity;
73 // logically this should be "verbosity >= 1", but debug messages aren't
74 // captured for automate output and doing so would probably be an
75 // information leak in the case of remote_automate. So track debug-ness
76 // separately so it can be unchanged when a subcommand changes the
77 // verbosity level.
78 bool is_debug;
79 boost::circular_buffer<char> logbuf;
80 std::string real_prog_name;
81 std::string filename;
82 std::string gasp_dump;
83 bool already_dumping;
84 std::vector<MusingI const *> musings;
85
86 void (*out_of_band_function)(char channel, std::string const& text, void *opaque);
87 void *out_of_band_opaque;
88
89 impl() :
90 verbosity(0), is_debug(false), logbuf(0xffff),
91 already_dumping(false), out_of_band_function(0), out_of_band_opaque(0)
92 {}
93};
94
95// debugging / logging system
96
97sanity::sanity()
98{
99 imp = 0;
100}
101
102sanity::~sanity()
103{
104 if (imp)
105 delete imp;
106}
107
108void
109sanity::initialize(int argc, char ** argv, char const * lc_all)
110{
111 imp = new impl;
112
113 // set up some marked strings, so even if our logbuf overflows, we'll get
114 // this data in a crash. This (and subclass overrides) are probably the
115 // only place PERM_MM should ever be used.
116
117 string system_flavour;
118 get_system_flavour(system_flavour);
119 PERM_MM(system_flavour);
120 L(FL("started up on %s") % system_flavour);
121
122 string cmdline_string;
123 {
124 ostringstream cmdline_ss;
125 for (int i = 0; i < argc; ++i)
126 {
127 if (i)
128 cmdline_ss << ", ";
129 cmdline_ss << '\'' << argv[i] << '\'';
130 }
131 cmdline_string = cmdline_ss.str();
132 }
133 PERM_MM(cmdline_string);
134 L(FL("command line: %s") % cmdline_string);
135
136 if (!lc_all)
137 lc_all = "n/a";
138 PERM_MM(string(lc_all));
139 L(FL("set locale: LC_ALL=%s") % lc_all);
140
141 // find base name of executable and save in the prog_name global. note that
142 // this does not bother with conversion to utf8.
143 {
144 string av0 = argv[0];
145 if (av0.size() > 4 && av0.rfind(".exe") == av0.size() - 4)
146 av0.erase(av0.size() - 4);
147 string::size_type last_slash = av0.find_last_of("/\\");
148 if (last_slash != string::npos)
149 av0.erase(0, last_slash+1);
150 imp->real_prog_name = av0;
151 prog_name_ptr = &imp->real_prog_name;
152 }
153}
154
155void
156sanity::dump_buffer()
157{
158 I(imp);
159 if (!imp->filename.empty())
160 {
161 ofstream out(imp->filename.c_str());
162 if (!out)
163 {
164 try
165 {
166 make_dir_for(system_path(imp->filename, origin::internal));
167 out.open(imp->filename.c_str());
168 }
169 catch (...)
170 {
171 inform_message((FL("failed to create directory for %s")
172 % imp->filename).str());
173 }
174 }
175 if (out)
176 {
177 copy(imp->logbuf.begin(), imp->logbuf.end(),
178 ostream_iterator<char>(out));
179 copy(imp->gasp_dump.begin(), imp->gasp_dump.end(),
180 ostream_iterator<char>(out));
181 inform_message((FL("wrote debugging log to %s\n"
182 "if reporting a bug, please include this file")
183 % imp->filename).str());
184 }
185 else
186 inform_message((FL("failed to write debugging log to %s")
187 % imp->filename).str());
188 }
189 else
190 inform_message("discarding debug log, because I have nowhere to write it\n"
191 "(maybe you want -v -v or --dump?)");
192}
193
194int
195sanity::set_verbosity(int level, bool allow_debug_change)
196{
197 I(imp);
198 int ret = imp->verbosity;
199 imp->verbosity = level;
200
201 if (allow_debug_change)
202 {
203 imp->is_debug = (level >= 1);
204
205 if (imp->is_debug)
206 {
207 // it is possible that some pre-setting-of-debug data
208 // accumulated in the log buffer (during earlier option processing)
209 // so we will dump it now
210 ostringstream oss;
211 vector<string> lines;
212 copy(imp->logbuf.begin(), imp->logbuf.end(), ostream_iterator<char>(oss));
213 split_into_lines(oss.str(), lines);
214 for (vector<string>::const_iterator i = lines.begin(); i != lines.end(); ++i)
215 inform_log((*i) + "\n");
216 }
217 }
218 return ret;
219}
220void
221sanity::set_debug()
222{
223 set_verbosity(1, true);
224}
225int
226sanity::get_verbosity() const
227{
228 I(imp);
229 return imp->verbosity;
230}
231
232void
233sanity::set_dump_path(std::string const & path)
234{
235 I(imp);
236 if (imp->filename.empty())
237 {
238 L(FL("setting dump path to %s") % path);
239 imp->filename = path;
240 }
241}
242
243string
244sanity::do_format(format_base const & fmt, char const * file, int line)
245{
246 try
247 {
248 return fmt.str();
249 }
250 catch (exception & e)
251 {
252 inform_error((F("fatal: formatter failed on %s:%d: %s")
253 % file
254 % line
255 % e.what()).str());
256 throw;
257 }
258}
259
260bool
261sanity::debug_p()
262{
263 if (!imp)
264 throw std::logic_error("sanity::debug_p called "
265 "before sanity::initialize");
266 return imp->is_debug;
267}
268
269void
270sanity::log(plain_format const & fmt,
271 char const * file, int line)
272{
273 string str = do_format(fmt, file, line);
274
275 if (str.size() > constants::log_line_sz)
276 {
277 str.resize(constants::log_line_sz);
278 if (str.at(str.size() - 1) != '\n')
279 str.at(str.size() - 1) = '\n';
280 }
281 copy(str.begin(), str.end(), back_inserter(imp->logbuf));
282 if (str[str.size() - 1] != '\n')
283 imp->logbuf.push_back('\n');
284
285 inform_log(str);
286}
287
288void
289sanity::progress(i18n_format const & i18nfmt,
290 char const * file, int line)
291{
292 string str = do_format(i18nfmt, file, line);
293
294 if (maybe_write_to_out_of_band_handler('p', str))
295 return;
296
297 if (str.size() > constants::log_line_sz)
298 {
299 str.resize(constants::log_line_sz);
300 if (str.at(str.size() - 1) != '\n')
301 str.at(str.size() - 1) = '\n';
302 }
303 copy(str.begin(), str.end(), back_inserter(imp->logbuf));
304 if (str[str.size() - 1] != '\n')
305 imp->logbuf.push_back('\n');
306
307 inform_message(str);
308}
309
310void
311sanity::warning(i18n_format const & i18nfmt,
312 char const * file, int line)
313{
314 string str = do_format(i18nfmt, file, line);
315
316 if (maybe_write_to_out_of_band_handler('w', str))
317 return;
318
319 if (str.size() > constants::log_line_sz)
320 {
321 str.resize(constants::log_line_sz);
322 if (str.at(str.size() - 1) != '\n')
323 str.at(str.size() - 1) = '\n';
324 }
325 string str2 = "warning: " + str;
326 copy(str2.begin(), str2.end(), back_inserter(imp->logbuf));
327 if (str[str.size() - 1] != '\n')
328 imp->logbuf.push_back('\n');
329
330 inform_warning(str);
331}
332
333void
334sanity::generic_failure(char const * expr,
335 origin::type caused_by,
336 i18n_format const & explain,
337 char const * file, int line)
338{
339 if (!imp)
340 throw std::logic_error("sanity::generic_failure occured "
341 "before sanity::initialize");
342
343 log(FL("Encountered an error while musing upon the following:"),
344 file, line);
345 gasp();
346 log(FL("%s:%d: detected %s error, '%s' violated")
347 % file % line % origin::type_to_string(caused_by) % expr,
348 file, line);
349
350 string prefix;
351 if (caused_by == origin::user)
352 {
353 prefix = _("misuse: ");
354 }
355 else
356 {
357 prefix = _("error: ");
358 }
359 string message;
360 prefix_lines_with(prefix, do_format(explain, file, line), message);
361 switch (caused_by)
362 {
363 case origin::database:
364 case origin::internal:
365 throw unrecoverable_failure(caused_by, message);
366 default:
367 throw recoverable_failure(caused_by, message);
368 }
369}
370
371void
372sanity::index_failure(char const * vec_expr,
373 char const * idx_expr,
374 unsigned long sz,
375 unsigned long idx,
376 char const * file, int line)
377{
378 char const * pattern
379 = N_("%s:%d: index '%s' = %d overflowed vector '%s' with size %d");
380 if (!imp)
381 throw std::logic_error("sanity::index_failure occured "
382 "before sanity::initialize");
383 if (debug_p())
384 log(FL(pattern) % file % line % idx_expr % idx % vec_expr % sz,
385 file, line);
386 gasp();
387 throw out_of_range((F(pattern)
388 % file % line % idx_expr % idx % vec_expr % sz).str());
389}
390
391// Last gasp dumps
392
393void
394sanity::push_musing(MusingI const *musing)
395{
396 I(imp);
397 if (!imp->already_dumping)
398 imp->musings.push_back(musing);
399}
400
401void
402sanity::pop_musing(MusingI const *musing)
403{
404 I(imp);
405 if (!imp->already_dumping)
406 {
407 I(imp->musings.back() == musing);
408 imp->musings.pop_back();
409 }
410}
411
412
413void
414sanity::gasp()
415{
416 if (!imp)
417 return;
418 if (imp->already_dumping)
419 {
420 L(FL("ignoring request to give last gasp; already in process of dumping"));
421 return;
422 }
423 imp->already_dumping = true;
424 L(FL("saving current work set: %i items") % imp->musings.size());
425 ostringstream out;
426 out << (F("Current work set: %i items") % imp->musings.size())
427 << '\n'; // final newline is kept out of the translation
428 for (vector<MusingI const *>::const_iterator
429 i = imp->musings.begin(); i != imp->musings.end(); ++i)
430 {
431 string tmp;
432 try
433 {
434 (*i)->gasp(tmp);
435 out << tmp;
436 }
437 catch (logic_error)
438 {
439 out << tmp;
440 out << "<caught logic_error>\n";
441 L(FL("ignoring error trigged by saving work set to debug log"));
442 }
443 catch (recoverable_failure)
444 {
445 out << tmp;
446 out << "<caught informative_failure>\n";
447 L(FL("ignoring error trigged by saving work set to debug log"));
448 }
449 }
450 imp->gasp_dump = out.str();
451 L(FL("finished saving work set"));
452 if (debug_p())
453 {
454 inform_log("contents of work set:");
455 inform_log(imp->gasp_dump);
456 }
457 imp->already_dumping = false;
458}
459
460void sanity::set_out_of_band_handler(void (*out_of_band_function)(char, std::string const&, void *), void *opaque_data)
461{
462 imp->out_of_band_function= out_of_band_function;
463 imp->out_of_band_opaque= opaque_data;
464}
465
466bool sanity::maybe_write_to_out_of_band_handler(char channel, std::string const& str)
467{
468 if (imp->out_of_band_function)
469 {
470 (*imp->out_of_band_function)(channel, str, imp->out_of_band_opaque);
471 return true;
472 }
473 return false;
474}
475
476template <> void
477dump(string const & obj, string & out)
478{
479 out = obj;
480}
481template<> void
482dump(char const * const & obj, string & out)
483{
484 out = obj;
485}
486template<> void
487dump(bool const & obj, string & out)
488{
489 out = (obj ? "true" : "false");
490}
491template <> void
492dump(int const & val, string & out)
493{
494 out = lexical_cast<string>(val);
495}
496template <> void
497dump(unsigned int const & val, string & out)
498{
499 out = lexical_cast<string>(val);
500}
501template <> void
502dump(long const & val, string & out)
503{
504 out = lexical_cast<string>(val);
505}
506template <> void
507dump(unsigned long const & val, string & out)
508{
509 out = lexical_cast<string>(val);
510}
511#ifdef USING_LONG_LONG
512template <> void
513dump(long long const & val, string & out)
514{
515 out = lexical_cast<string>(val);
516}
517template <> void
518dump(unsigned long long const & val, string & out)
519{
520 out = lexical_cast<string>(val);
521}
522#endif
523
524void
525sanity::print_var(std::string const & value, char const * var,
526 char const * file, int const line, char const * func)
527{
528 inform_message((FL("----- begin '%s' (in %s, at %s:%d)")
529 % var % func % file % line).str());
530 inform_message(value);
531 inform_message((FL("----- end '%s' (in %s, at %s:%d)\n\n")
532 % var % func % file % line).str());
533}
534
535void MusingBase::gasp_head(string & out) const
536{
537 out = (boost::format("----- begin '%s' (in %s, at %s:%d)\n")
538 % name % func % file % line
539 ).str();
540}
541
542void MusingBase::gasp_body(const string & objstr, string & out) const
543{
544 out += (boost::format("%s%s"
545 "----- end '%s' (in %s, at %s:%d)\n")
546 % objstr
547 % (*(objstr.end() - 1) == '\n' ? "" : "\n")
548 % name % func % file % line
549 ).str();
550}
551
552const locale &
553get_user_locale()
554{
555 // this is awkward because if LC_CTYPE is set to something the
556 // runtime doesn't know about, it will fail. in that case,
557 // the default will have to do.
558 static bool init = false;
559 static locale user_locale;
560 if (!init)
561 {
562 init = true;
563 try
564 {
565 user_locale = locale("");
566 }
567 catch( ... )
568 {}
569 }
570 return user_locale;
571}
572
573struct
574format_base::impl
575{
576 format fmt;
577 ostringstream oss;
578
579 impl(impl const & other) : fmt(other.fmt)
580 {}
581
582 impl & operator=(impl const & other)
583 {
584 if (&other != this)
585 {
586 fmt = other.fmt;
587 oss.str(string());
588 }
589 return *this;
590 }
591
592 impl(char const * pattern)
593 : fmt(pattern)
594 {}
595 impl(string const & pattern)
596 : fmt(pattern)
597 {}
598 impl(char const * pattern, locale const & loc)
599 : fmt(pattern, loc)
600 {}
601 impl(string const & pattern, locale const & loc)
602 : fmt(pattern, loc)
603 {}
604};
605
606format_base::format_base(format_base const & other)
607 : pimpl(other.pimpl ? new impl(*(other.pimpl)) : NULL)
608{
609
610}
611
612format_base::~format_base()
613{
614 delete pimpl;
615}
616
617format_base &
618format_base::operator=(format_base const & other)
619{
620 if (&other != this)
621 {
622 impl * tmp = NULL;
623
624 try
625 {
626 if (other.pimpl)
627 tmp = new impl(*(other.pimpl));
628 }
629 catch (...)
630 {
631 if (tmp)
632 delete tmp;
633 }
634
635 if (pimpl)
636 delete pimpl;
637
638 pimpl = tmp;
639 }
640 return *this;
641}
642
643format_base::format_base(char const * pattern, bool use_locale)
644 : pimpl(use_locale ? new impl(pattern, get_user_locale())
645 : new impl(pattern))
646{}
647
648format_base::format_base(std::string const & pattern, bool use_locale)
649 : pimpl(use_locale ? new impl(pattern, get_user_locale())
650 : new impl(pattern))
651{}
652
653ostream &
654format_base::get_stream() const
655{
656 return pimpl->oss;
657}
658
659void
660format_base::flush_stream() const
661{
662 pimpl->fmt % pimpl->oss.str();
663 pimpl->oss.str(string());
664}
665
666void format_base::put_and_flush_signed(s64 const & s) const { pimpl->fmt % s; }
667void format_base::put_and_flush_signed(s32 const & s) const { pimpl->fmt % s; }
668void format_base::put_and_flush_signed(s16 const & s) const { pimpl->fmt % s; }
669void format_base::put_and_flush_signed(s8 const & s) const { pimpl->fmt % s; }
670
671void format_base::put_and_flush_unsigned(u64 const & u) const { pimpl->fmt % u; }
672void format_base::put_and_flush_unsigned(u32 const & u) const { pimpl->fmt % u; }
673void format_base::put_and_flush_unsigned(u16 const & u) const { pimpl->fmt % u; }
674void format_base::put_and_flush_unsigned(u8 const & u) const { pimpl->fmt % u; }
675
676void format_base::put_and_flush_float(float const & f) const { pimpl->fmt % f; }
677void format_base::put_and_flush_double(double const & d) const { pimpl->fmt % d; }
678
679std::string
680format_base::str() const
681{
682 return pimpl->fmt.str();
683}
684
685ostream &
686operator<<(ostream & os, format_base const & fmt)
687{
688 return os << fmt.str();
689}
690
691i18n_format F(const char * str)
692{
693 return i18n_format(gettext(str));
694}
695
696
697i18n_format FP(const char * str1, const char * strn, unsigned long count)
698{
699 return i18n_format(ngettext(str1, strn, count));
700}
701
702plain_format FL(const char * str)
703{
704 return plain_format(str);
705}
706
707// Local Variables:
708// mode: C++
709// fill-column: 76
710// c-file-style: "gnu"
711// indent-tabs-mode: nil
712// End:
713// vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s:

Archive Download this file

Branches

Tags

Quick Links:     www.monotone.ca    -     Downloads    -     Documentation    -     Wiki    -     Code Forge    -     Build Status