]>
Commit | Line | Data |
---|---|---|
1 | /* | |
2 | * Copyright (c) 2002, Robert Collins. | |
3 | * | |
4 | * This program is free software; you can redistribute it and/or modify | |
5 | * it under the terms of the GNU General Public License as published by | |
6 | * the Free Software Foundation; either version 2 of the License, or | |
7 | * (at your option) any later version. | |
8 | * | |
9 | * A copy of the GNU General Public License can be found at | |
10 | * http://www.gnu.org/ | |
11 | * | |
12 | * Written by Robert Collins <rbtcollins@hotmail.com> | |
13 | * | |
14 | */ | |
15 | ||
16 | /* Log to one or more files. */ | |
17 | ||
18 | #include <stdlib.h> | |
19 | #include "LogFile.h" | |
20 | #include "io_stream.h" | |
21 | #include "win32.h" | |
22 | #include "msg.h" | |
23 | #include "dialog.h" | |
24 | #include "resource.h" | |
25 | #include <iostream> | |
26 | #include <sstream> | |
27 | #include <set> | |
28 | #include <time.h> | |
29 | #include <string> | |
30 | #include <stdexcept> | |
31 | #include "AntiVirus.h" | |
32 | #include "filemanip.h" | |
33 | #include "String++.h" | |
34 | #include "getopt++/BoolOption.h" | |
35 | ||
36 | static BoolOption VerboseOutput (false, 'v', "verbose", IDS_HELPTEXT_VERBOSE); | |
37 | ||
38 | /* private helper class */ | |
39 | class filedef | |
40 | { | |
41 | public: | |
42 | int level; | |
43 | std::string key; | |
44 | bool append; | |
45 | filedef (const std::string& _path) : key (_path) {} | |
46 | bool operator == (filedef const &rhs) const | |
47 | { | |
48 | return casecompare(key, rhs.key) == 0; | |
49 | } | |
50 | bool operator < (filedef const &rhs) const | |
51 | { | |
52 | return casecompare(key, rhs.key) < 0; | |
53 | } | |
54 | }; | |
55 | ||
56 | /* another */ | |
57 | struct LogEnt | |
58 | { | |
59 | LogEnt *next; | |
60 | enum log_level level; | |
61 | time_t when; | |
62 | std::string msg; | |
63 | }; | |
64 | ||
65 | static LogEnt *first_logent = 0; | |
66 | static LogEnt **next_logent = &first_logent; | |
67 | static LogEnt *currEnt = 0; | |
68 | ||
69 | int LogFile::exit_msg = 0; | |
70 | ||
71 | typedef std::set<filedef> FileSet; | |
72 | static FileSet files; | |
73 | static std::stringbuf *theStream; | |
74 | ||
75 | LogFile * | |
76 | LogFile::createLogFile() | |
77 | { | |
78 | theStream = new std::stringbuf; | |
79 | return new LogFile(theStream); | |
80 | } | |
81 | ||
82 | LogFile::LogFile(std::stringbuf *aStream) : LogSingleton (aStream) | |
83 | { | |
84 | } | |
85 | LogFile::~LogFile(){} | |
86 | ||
87 | void | |
88 | LogFile::clearFiles () | |
89 | { | |
90 | files.clear (); | |
91 | } | |
92 | ||
93 | void | |
94 | LogFile::setFile (int minlevel, const std::string& path, bool append) | |
95 | { | |
96 | FileSet::iterator f = files.find (filedef(path)); | |
97 | if (f != files.end ()) | |
98 | files.erase (f); | |
99 | ||
100 | filedef t (path); | |
101 | t.level = minlevel; | |
102 | t.append = append; | |
103 | files.insert (t); | |
104 | } | |
105 | ||
106 | std::string | |
107 | LogFile::getFileName (int level) const | |
108 | { | |
109 | for (FileSet::iterator i = files.begin(); | |
110 | i != files.end(); ++i) | |
111 | { | |
112 | if (i->level == level) | |
113 | return i->key; | |
114 | } | |
115 | return "<no log was in use>"; | |
116 | } | |
117 | ||
118 | void | |
119 | LogFile::exit (int exit_code, bool show_end_install_msg) | |
120 | { | |
121 | AntiVirus::AtExit(); | |
122 | static int been_here = 0; | |
123 | /* Exitcode -1 is special... */ | |
124 | if (been_here) | |
125 | ::exit (exit_code); | |
126 | been_here = 1; | |
127 | ||
128 | if (exit_msg) | |
129 | { | |
130 | std::wstring fmt = LoadStringWEx(exit_msg, MAKELANGID(LANG_ENGLISH, SUBLANG_ENGLISH_US)); | |
131 | std::wstring buf = format(fmt, backslash(getFileName(LOG_BABBLE)).c_str()); | |
132 | Log (LOG_PLAIN) << "note: " << wstring_to_string(buf) << endLog; | |
133 | } | |
134 | ||
135 | /* ... in that it skips the boring log messages. Exit code -1 is used when | |
136 | just printing the help output and when we're self-elevating. */ | |
137 | if (show_end_install_msg) | |
138 | Log (LOG_TIMESTAMP) << "Ending cygwin install" << endLog; | |
139 | ||
140 | for (FileSet::iterator i = files.begin(); | |
141 | i != files.end(); ++i) | |
142 | { | |
143 | log_save (i->level, i->key, i->append); | |
144 | } | |
145 | // TODO: remove this when the ::exit issue is tidied up. | |
146 | ::exit (exit_code); | |
147 | } | |
148 | ||
149 | void | |
150 | LogFile::flushAll () | |
151 | { | |
152 | Log (LOG_TIMESTAMP) << "Writing messages to log files without exiting" << endLog; | |
153 | ||
154 | for (FileSet::iterator i = files.begin(); | |
155 | i != files.end(); ++i) | |
156 | { | |
157 | log_save (i->level, i->key, i->append); | |
158 | } | |
159 | } | |
160 | ||
161 | void | |
162 | LogFile::log_save (int babble, const std::string& filename, bool append) | |
163 | { | |
164 | static int been_here = 0; | |
165 | if (been_here) | |
166 | return; | |
167 | been_here = 1; | |
168 | ||
169 | io_stream::mkpath_p (PATH_TO_FILE, "file://" + filename, 0755); | |
170 | ||
171 | io_stream *f = io_stream::open("file://" + filename, append ? "at" : "wt", 0644); | |
172 | if (!f) | |
173 | { | |
174 | fatal (NULL, IDS_NOLOGFILE, filename.c_str()); | |
175 | return; | |
176 | } | |
177 | ||
178 | LogEnt *l; | |
179 | ||
180 | for (l = first_logent; l; l = l->next) | |
181 | { | |
182 | if (babble || !(l->level == LOG_BABBLE)) | |
183 | { | |
184 | const char *tstr = l->msg.c_str(); | |
185 | f->write (tstr, strlen (tstr)); | |
186 | if (tstr[strlen (tstr) - 1] != '\n') | |
187 | f->write ("\n", 1); | |
188 | } | |
189 | } | |
190 | ||
191 | delete f; | |
192 | been_here = 0; | |
193 | } | |
194 | ||
195 | std::ostream & | |
196 | LogFile::operator() (log_level theLevel) | |
197 | { | |
198 | if (theLevel < 1 || theLevel > 2) | |
199 | throw new std::invalid_argument("Invalid log_level"); | |
200 | if (!theStream) | |
201 | theStream = new std::stringbuf; | |
202 | rdbuf (theStream); | |
203 | currEnt = new LogEnt; | |
204 | currEnt->next = 0; | |
205 | currEnt->level = theLevel; | |
206 | return *this; | |
207 | } | |
208 | ||
209 | void | |
210 | LogFile::endEntry() | |
211 | { | |
212 | std::string buf = theStream->str(); | |
213 | delete theStream; | |
214 | ||
215 | /* also write to stdout */ | |
216 | if ((currEnt->level >= LOG_PLAIN) || VerboseOutput) | |
217 | { | |
218 | /* | |
219 | The log message is UTF-8 encoded. Re-encode this in the console output | |
220 | codepage (so it can be correctly decoded by a Windows terminal). | |
221 | Unfortunately there's no API for direct multibyte re-encoding, so we | |
222 | must do it in two steps UTF-8 -> UTF-16 -> CP_COCP. | |
223 | ||
224 | If the console output codepage is UTF-8, we already have the log message | |
225 | in the correct encoding, so we can avoid doing all that work. | |
226 | ||
227 | If the output is not a console, GetConsoleOutputCP() returns 0. | |
228 | Possibly it's a Cygwin pty? | |
229 | */ | |
230 | std::string cpbuf = buf; | |
231 | ||
232 | unsigned int ocp = GetConsoleOutputCP(); | |
233 | if ((ocp != 0 ) && (ocp != 65001)) | |
234 | cpbuf = wstring_to_string(string_to_wstring(buf), ocp); | |
235 | ||
236 | std::cout << cpbuf << std::endl; | |
237 | } | |
238 | ||
239 | if (!currEnt) | |
240 | { | |
241 | /* get a default LogEnt */ | |
242 | currEnt = new LogEnt; | |
243 | currEnt->next = 0; | |
244 | currEnt->level = LOG_PLAIN; | |
245 | } | |
246 | *next_logent = currEnt; | |
247 | next_logent = &(currEnt->next); | |
248 | time (&(currEnt->when)); | |
249 | if (currEnt->level == LOG_TIMESTAMP) | |
250 | { | |
251 | char b[100]; | |
252 | struct tm *tm = localtime (&(currEnt->when)); | |
253 | strftime (b, 1000, "%Y/%m/%d %H:%M:%S ", tm); | |
254 | currEnt->msg = b; | |
255 | } | |
256 | currEnt->msg += buf; | |
257 | ||
258 | /* reset for next use */ | |
259 | theStream = new std::stringbuf; | |
260 | rdbuf (theStream); | |
261 | init (theStream); | |
262 | } |