1 : // Copyright 2012 Google Inc. All Rights Reserved.
2 : //
3 : // Licensed under the Apache License, Version 2.0 (the "License");
4 : // you may not use this file except in compliance with the License.
5 : // You may obtain a copy of the License at
6 : //
7 : // http://www.apache.org/licenses/LICENSE-2.0
8 : //
9 : // Unless required by applicable law or agreed to in writing, software
10 : // distributed under the License is distributed on an "AS IS" BASIS,
11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 : // See the License for the specific language governing permissions and
13 : // limitations under the License.
14 : //
15 : // This file defines the trace::logger::LoggerApp class which implements the
16 : // LoggerApp RPC interface.
17 :
18 : #include "syzygy/trace/logger/logger_app.h"
19 :
20 : #include "base/bind.h"
21 : #include "base/environment.h"
22 : #include "base/file_util.h"
23 : #include "base/path_service.h"
24 : #include "base/process.h"
25 : #include "base/process_util.h"
26 : #include "base/string_util.h"
27 : #include "base/stringprintf.h"
28 : #include "base/utf_string_conversions.h"
29 : #include "base/win/scoped_handle.h"
30 : #include "syzygy/trace/logger/logger.h"
31 : #include "syzygy/trace/logger/logger_rpc_impl.h"
32 : #include "syzygy/trace/protocol/call_trace_defs.h"
33 : #include "syzygy/trace/rpc/logger_rpc.h"
34 : #include "syzygy/trace/rpc/rpc_helpers.h"
35 :
36 : namespace trace {
37 : namespace logger {
38 :
39 : namespace {
40 :
41 : using trace::client::GetInstanceString;
42 :
43 : // The usage string for the logger app.
44 : const char kUsageFormatStr[] =
45 : "Usage: %ls [options] ACTION [-- command]\n"
46 : " Supported actions:\n"
47 : " start Run a new logger instance in the foreground (blocking). You\n"
48 : " may optionally specify an external command which will be\n"
49 : " run behind the logger. The logger will return once the\n"
50 : " external command has terminated or the logger is externally\n"
51 : " stopped. If no command is specified, Ctrl-C or an invocation\n"
52 : " of the stop action will stop the logger.\n"
53 : " spawn Run a new logger instance in the background (non-blocking).\n"
54 : " stop Stop a separately running logger instance.\n"
55 : " Options:\n"
56 : " --instance-id=ID A unique (up to 16 character) ID to identify\n"
57 : " the logger instance.\n"
58 : " --output-file=PATH> The file path to which logs should be written.\n"
59 : " This may be stdout (the default), stderr or a\n"
60 : " file path. This option is valid for the start\n"
61 : " and spawn actions.\n"
62 : " --append Append to (instead of truncating) the output\n"
63 : " file. This option is valid for the start and\n"
64 : " spawn actions.\n";
65 :
66 : // Names for kernel objects used to synchronize with a logger singleton.
67 : const wchar_t kLoggerMutexRoot[] = L"syzygy-logger-mutex";
68 : const wchar_t kLoggerStartEventRoot[] = L"syzygy-logger-started";
69 : const wchar_t kLoggerStopEventRoot[] = L"syzygy-logger-stopped";
70 :
71 : // A static location to which the current instance id can be saved. We
72 : // persist it here so that OnConsoleCtrl can have access to the instance
73 : // id when it is invoked on the signal handler thread.
74 : wchar_t saved_instance_id[16] = { 0 };
75 : const size_t kMaxInstanceIdLength = arraysize(saved_instance_id) - 1;
76 :
77 : // Send a stop request via RPC to the logger instance given by @p instance_id.
78 i : bool SendStopRequest(const base::StringPiece16& instance_id) {
79 i : std::wstring protocol(kLoggerRpcProtocol);
80 i : std::wstring endpoint(GetInstanceString(kLoggerRpcEndpointRoot, instance_id));
81 :
82 i : LOG(INFO) << "Stopping logging service instance at '"
83 : << endpoint << "' via " << protocol << '.';
84 :
85 i : handle_t binding = NULL;
86 i : if (!trace::client::CreateRpcBinding(protocol, endpoint, &binding)) {
87 i : LOG(ERROR) << "Failed to connect to logging service.";
88 i : return false;
89 : }
90 :
91 i : if (!trace::client::InvokeRpc(LoggerClient_Stop, binding).succeeded()) {
92 i : LOG(ERROR) << "Failed to stop logging service.";
93 i : return false;
94 : }
95 :
96 i : LOG(INFO) << "Logging service shutdown has been requested.";
97 :
98 i : return true;
99 i : }
100 :
101 : // Handler function to be called on exit signals (Ctrl-C, TERM, etc...).
102 i : BOOL WINAPI OnConsoleCtrl(DWORD ctrl_type) {
103 i : if (ctrl_type != CTRL_LOGOFF_EVENT) {
104 i : SendStopRequest(saved_instance_id);
105 i : return TRUE;
106 : }
107 i : return FALSE;
108 i : }
109 :
110 : // A helper class to manage a console handler for Control-C.
111 : // TODO(rogerm): Move this to a shared location (perhaps in common, next to
112 : // the application classes?).
113 : class ScopedConsoleCtrlHandler {
114 : public:
115 E : ScopedConsoleCtrlHandler() : handler_(NULL) {
116 E : }
117 :
118 E : ~ScopedConsoleCtrlHandler() {
119 E : if (handler_ != NULL) {
120 i : ignore_result(::SetConsoleCtrlHandler(handler_, FALSE));
121 i : handler_ = NULL;
122 : }
123 E : }
124 :
125 i : bool Init(PHANDLER_ROUTINE handler) {
126 i : DCHECK(handler != NULL);
127 i : DCHECK(handler_ == NULL);
128 :
129 i : if (!::SetConsoleCtrlHandler(handler, TRUE)) {
130 i : DWORD err = ::GetLastError();
131 i : LOG(ERROR) << "Failed to register console control handler: "
132 : << com::LogWe(err) << ".";
133 i : return false;
134 : }
135 :
136 i : handler_ = handler;
137 i : return true;
138 i : }
139 :
140 : protected:
141 : PHANDLER_ROUTINE handler_;
142 : };
143 :
144 : // Helper function to acquire a named mutex.
145 : // TODO(rogerm): Move this to a shared location (perhaps in common, next to
146 : // the application classes?).
147 : bool AcquireMutex(const std::wstring& mutex_name,
148 E : base::win::ScopedHandle* mutex) {
149 E : DCHECK(mutex != NULL);
150 E : DCHECK(!mutex->IsValid());
151 :
152 : base::win::ScopedHandle tmp_mutex(
153 E : ::CreateMutex(NULL, FALSE, mutex_name.c_str()));
154 E : if (!tmp_mutex.IsValid()) {
155 i : DWORD error = ::GetLastError();
156 i : LOG(ERROR) << "Failed to create mutex: " << com::LogWe(error) << ".";
157 i : return false;
158 : }
159 E : const DWORD kOneSecondInMs = 1000;
160 :
161 E : switch (::WaitForSingleObject(tmp_mutex, kOneSecondInMs)) {
162 : case WAIT_ABANDONED:
163 i : LOG(WARNING) << "Orphaned service mutex found!";
164 : // Fall through...
165 :
166 : case WAIT_OBJECT_0:
167 E : VLOG(1) << "Service mutex acquired.";
168 E : mutex->Set(tmp_mutex.Take());
169 E : return true;
170 :
171 : case WAIT_TIMEOUT:
172 i : LOG(ERROR) << "A synonymous instance of the logger is already running.";
173 i : break;
174 :
175 : default: {
176 i : DWORD error = ::GetLastError();
177 i : LOG(ERROR) << "Failed to acquire mutex: " << com::LogWe(error) << ".";
178 : break;
179 : }
180 : }
181 i : return false;
182 E : }
183 :
184 : // Helper function to initialize a named event.
185 : // TODO(rogerm): Move this to a shared location (perhaps in common, next to
186 : // the application classes?).
187 : bool InitEvent(const std::wstring& event_name,
188 E : base::win::ScopedHandle* handle) {
189 E : DCHECK(handle != NULL);
190 E : DCHECK(!handle->IsValid());
191 E : handle->Set(::CreateEvent(NULL, TRUE, FALSE, event_name.c_str()));
192 E : if (!handle->IsValid())
193 i : return false;
194 E : return true;
195 E : }
196 :
197 : // A helper function to signal an event. This is passable as a callback to
198 : // a Logger instance to be called on logger start/stop.
199 E : bool SignalEvent(HANDLE event_handle, Logger* /* logger */) {
200 E : DCHECK_NE(INVALID_HANDLE_VALUE, event_handle);
201 E : if (!::SetEvent(event_handle))
202 i : return false;
203 E : return true;
204 E : }
205 :
206 : // A helper to split a command line into two command lines. The split will
207 : // occur after the first non-switch parameter. The logger command line will
208 : // be populated by the switches and arguments up to and including the first
209 : // non-switch parameter. All remaining arguments and switches will be added
210 : // to the app command line. This function understands the "--" marker
211 : // which is used to allow switches to appear after the first non-switch
212 : // argument (otherwise CommandLine will sort the entire command line before
213 : // we get a chance to inspect it.).
214 : bool SplitCommandLine(const CommandLine* orig_command_line,
215 : CommandLine* logger_command_line,
216 E : scoped_ptr<CommandLine>* app_command_line) {
217 E : DCHECK(orig_command_line != NULL);
218 E : DCHECK(!orig_command_line->argv().empty());
219 E : DCHECK(logger_command_line != NULL);
220 E : DCHECK(app_command_line != NULL);
221 :
222 : // Copy the initial parts of the command-line, up to and including the
223 : // first non-switch argument (which should be the "action"), into a
224 : // string vector for the logger command line.
225 E : CommandLine::StringVector logger_argv;
226 : CommandLine::StringVector::const_iterator it =
227 E : orig_command_line->argv().begin();
228 E : logger_argv.push_back(*(it++)); // Always copy the program.
229 E : for (; it != orig_command_line->argv().end(); ++it) {
230 E : logger_argv.push_back(*it);
231 E : if ((*it)[0] != L'-') {
232 E : ++it;
233 E : break;
234 : }
235 E : }
236 :
237 : // Strip out the (optional) sentinel which marks the split between the
238 : // two command-lines.
239 E : if (it != orig_command_line->argv().end() && *it == L"--")
240 E : ++it;
241 :
242 : // Copy the rest of the command-line arguments into a string vector for the
243 : // app command line.
244 E : CommandLine::StringVector app_argv;
245 E : for (; it != orig_command_line->argv().end(); ++it) {
246 E : app_argv.push_back(*it);
247 E : }
248 :
249 : // Initialize the output command lines with the new arguments.
250 E : logger_command_line->InitFromArgv(logger_argv);
251 E : if (!app_argv.empty())
252 E : app_command_line->reset(new CommandLine(app_argv));
253 :
254 E : return true;
255 E : }
256 :
257 : // A helper function which sets the syzygy RPC instance id environment variable
258 : // then runs a given command line to completion.
259 : bool RunApp(const CommandLine& command_line,
260 : const std::wstring& instance_id,
261 E : int* exit_code) {
262 E : DCHECK(exit_code != NULL);
263 E : scoped_ptr<base::Environment> env(base::Environment::Create());
264 E : CHECK(env != NULL);
265 E : env->SetVar(kSyzygyRpcInstanceIdEnvVar, WideToUTF8(instance_id));
266 :
267 E : LOG(INFO) << "Launching '" << command_line.GetProgram().value() << "'.";
268 E : VLOG(1) << "Command Line: " << command_line.GetCommandLineString();
269 :
270 : // Launch a new process in the background.
271 : base::ProcessHandle process_handle;
272 E : base::LaunchOptions options;
273 E : options.start_hidden = false;
274 E : if (!base::LaunchProcess(command_line, options, &process_handle)) {
275 i : LOG(ERROR)
276 : << "Failed to launch '" << command_line.GetProgram().value() << "'.";
277 i : return false;
278 : }
279 :
280 : // Wait for and return the processes exit code.
281 : // Note that this closes the process handle.
282 E : if (!base::WaitForExitCode(process_handle, exit_code)) {
283 i : LOG(ERROR) << "Failed to get exit code.";
284 i : return false;
285 : }
286 :
287 E : return true;
288 E : }
289 :
290 : } // namespace
291 :
292 : // Keywords appearing on the command-line
293 : const wchar_t LoggerApp::kSpawn[] = L"spawn";
294 : const wchar_t LoggerApp::kStart[] = L"start";
295 : const wchar_t LoggerApp::kStatus[] = L"status";
296 : const wchar_t LoggerApp::kStop[] = L"stop";
297 : const char LoggerApp::kInstanceId[] = "instance-id";
298 : const char LoggerApp::kOutputFile[] = "output-file";
299 : const char LoggerApp::kAppend[] = "append";
300 : const wchar_t LoggerApp::kStdOut[] = L"stdout";
301 : const wchar_t LoggerApp::kStdErr[] = L"stderr";
302 :
303 : // A table mapping action keywords to their handler implementations.
304 E : const LoggerApp::ActionTableEntry LoggerApp::kActionTable[] = {
305 E : { LoggerApp::kSpawn, &LoggerApp::Spawn },
306 E : { LoggerApp::kStart, &LoggerApp::Start },
307 E : { LoggerApp::kStatus, &LoggerApp::Status },
308 E : { LoggerApp::kStop, &LoggerApp::Stop },
309 E : };
310 :
311 : LoggerApp::LoggerApp()
312 : : common::AppImplBase("Logger"),
313 : logger_command_line_(CommandLine::NO_PROGRAM),
314 : action_handler_(NULL),
315 E : append_(false) {
316 E : }
317 :
318 E : LoggerApp::~LoggerApp() {
319 E : }
320 :
321 E : bool LoggerApp::ParseCommandLine(const CommandLine* command_line) {
322 E : DCHECK(command_line != NULL);
323 :
324 : if (!SplitCommandLine(command_line,
325 : &logger_command_line_,
326 E : &app_command_line_)) {
327 i : LOG(ERROR) << "Failed to split command_line into logger and app parts.";
328 i : return false;
329 : }
330 :
331 : // Save the command-line in case we need to spawn.
332 E : command_line = &logger_command_line_;
333 :
334 : // Parse the instance id.
335 E : instance_id_ = command_line->GetSwitchValueNative(kInstanceId);
336 E : if (instance_id_.size() > kMaxInstanceIdLength) {
337 : return Usage(command_line,
338 : base::StringPrintf("The instance id '%ls' is too long. "
339 : "The max length is %d characters.",
340 : instance_id_.c_str(),
341 i : kMaxInstanceIdLength));
342 : }
343 :
344 : // Save the output file parameter.
345 E : output_file_path_ = command_line->GetSwitchValuePath(kOutputFile);
346 :
347 : // Make sure there's exactly one action.
348 E : if (command_line->GetArgs().size() != 1) {
349 : return Usage(command_line,
350 E : "Exactly 1 action is expected on the command line.");
351 : }
352 :
353 : // Check for the append flag.
354 E : append_ = command_line->HasSwitch(kAppend);
355 :
356 : // Parse the action.
357 E : action_ = command_line->GetArgs()[0];
358 E : const ActionTableEntry* entry = FindActionHandler(action_);
359 E : if (entry == NULL) {
360 : return Usage(
361 : command_line,
362 E : base::StringPrintf("Unrecognized action: %s.", action_.c_str()));
363 : }
364 :
365 : // Setup the action handler.
366 E : DCHECK(entry->handler != NULL);
367 E : action_handler_ = entry->handler;
368 :
369 E : return true;
370 E : }
371 :
372 E : int LoggerApp::Run() {
373 E : DCHECK(action_handler_ != NULL);
374 E : if (!(this->*action_handler_)())
375 i : return 1;
376 E : return 0;
377 E : }
378 :
379 : // A helper function to find the handler method for a given action.
380 : const LoggerApp::ActionTableEntry* LoggerApp::FindActionHandler(
381 E : const base::StringPiece16& action) {
382 E : const ActionTableEntry* const begin = &kActionTable[0];
383 E : const ActionTableEntry* const end = begin + arraysize(kActionTable);
384 : ActionTableEntryCompare compare_func;
385 :
386 : // Make sure that the array is sorted.
387 E : DCHECK(std::is_sorted(begin, end, compare_func));
388 :
389 : const ActionTableEntry* entry =
390 E : std::lower_bound(begin, end, action, compare_func);
391 E : if (entry == end)
392 E : return NULL;
393 :
394 E : return entry;
395 E : }
396 :
397 E : bool LoggerApp::Start() {
398 : std::wstring logger_name(
399 E : GetInstanceString(kLoggerRpcEndpointRoot, instance_id_));
400 :
401 : // Acquire the logger mutex.
402 E : base::win::ScopedHandle mutex;
403 E : std::wstring mutex_name(GetInstanceString(kLoggerMutexRoot, instance_id_));
404 E : if (!AcquireMutex(mutex_name, &mutex))
405 i : return false;
406 :
407 : // Setup the start event.
408 E : base::win::ScopedHandle start_event;
409 : std::wstring start_event_name(
410 E : GetInstanceString(kLoggerStartEventRoot, instance_id_));
411 E : if (!InitEvent(start_event_name, &start_event)) {
412 i : LOG(ERROR) << "Unable to init start event for '" << logger_name << "'.";
413 i : return false;
414 : }
415 :
416 : // Setup the stop event.
417 E : base::win::ScopedHandle stop_event;
418 : std::wstring stop_event_name(
419 E : GetInstanceString(kLoggerStopEventRoot, instance_id_));
420 E : if (!InitEvent(stop_event_name, &stop_event)) {
421 i : LOG(ERROR) << "Unable to init stop event for '" << logger_name << "'.";
422 i : return false;
423 : }
424 :
425 : // Get the log file output_file.
426 E : FILE* output_file = NULL;
427 E : bool must_close_output_file = false;
428 E : file_util::ScopedFILE auto_close;
429 E : if (!OpenOutputFile(&output_file, &must_close_output_file)) {
430 i : LOG(ERROR) << "Unable to open '" << output_file_path_.value() << "'.";
431 i : return false;
432 : }
433 :
434 : // Setup auto_close as appropriate.
435 E : if (must_close_output_file)
436 i : auto_close.reset(output_file);
437 :
438 : // Initialize the logger instance.
439 E : Logger logger;
440 E : logger.set_destination(output_file);
441 E : logger.set_instance_id(instance_id_);
442 : logger.set_logger_started_callback(
443 E : base::Bind(&SignalEvent, start_event.Get()));
444 : logger.set_logger_stopped_callback(
445 E : base::Bind(&SignalEvent, stop_event.Get()));
446 :
447 : // Save the instance_id for the Ctrl-C handler.
448 : ::wcsncpy_s(saved_instance_id,
449 : arraysize(saved_instance_id),
450 : instance_id_.c_str(),
451 E : -1);
452 :
453 : // Register the handler for Ctrl-C.
454 E : if (!SetConsoleCtrlHandler(&OnConsoleCtrl, TRUE)) {
455 i : DWORD error = ::GetLastError();
456 i : LOG(ERROR) << "Failed to register shutdown handler: "
457 : << com::LogWe(error) << ".";
458 i : return false;
459 : }
460 :
461 : // Start the logger.
462 E : RpcLoggerInstanceManager instance_manager(&logger);
463 E : if (!logger.Start()) {
464 i : LOG(ERROR) << "Failed to start '" << logger_name << "'.";
465 i : return false;
466 : }
467 :
468 E : bool error = false;
469 :
470 : // Run the logger, either standalone or as the parent of some application.
471 E : ScopedConsoleCtrlHandler ctrl_handler;
472 E : if (app_command_line_.get() != NULL) {
473 : // We have a command to run, so launch that command and when it finishes
474 : // stop the logger.
475 E : int exit_code = 0;
476 : if (!RunApp(*app_command_line_, instance_id_, &exit_code) ||
477 E : exit_code != 0) {
478 i : error = true;
479 : }
480 E : ignore_result(logger.Stop());
481 E : } else {
482 : // There is no command to wait for, so just register the control handler
483 : // (we stop the logger if this fails) and then let the logger run until
484 : // the control handler stops it or someone externally stops it using the
485 : // stop command.
486 i : if (!ctrl_handler.Init(&OnConsoleCtrl)) {
487 i : ignore_result(logger.Stop());
488 i : error = true;
489 : }
490 : }
491 :
492 : // Run the logger to completion.
493 E : if (!logger.RunToCompletion()) {
494 i : LOG(ERROR) << "Failed running to completion '" << logger_name << "'.";
495 i : error = true;
496 : }
497 :
498 : // And we're done.
499 E : return !error;
500 E : }
501 :
502 i : bool LoggerApp::Status() {
503 : // TODO(rogerm): Implement me.
504 i : return false;
505 i : }
506 :
507 i : bool LoggerApp::Spawn() {
508 : std::wstring logger_name(
509 i : GetInstanceString(kLoggerRpcEndpointRoot, instance_id_));
510 :
511 i : LOG(INFO) << "Launching background logging service '" << logger_name << "'.";
512 :
513 : // Get the path to ourselves.
514 i : FilePath self_path;
515 i : PathService::Get(base::FILE_EXE, &self_path);
516 :
517 : // Build a command line for starting a new instance of the logger.
518 i : CommandLine new_command_line(self_path);
519 i : new_command_line.AppendArg("start");
520 :
521 : // Copy over any other switches.
522 : CommandLine::SwitchMap::const_iterator it =
523 i : logger_command_line_.GetSwitches().begin();
524 i : for (; it != logger_command_line_.GetSwitches().end(); ++it)
525 i : new_command_line.AppendSwitchNative(it->first, it->second);
526 :
527 : // Launch a new process in the background.
528 : base::ProcessHandle service_process;
529 i : base::LaunchOptions options;
530 i : options.start_hidden = true;
531 i : if (!base::LaunchProcess(new_command_line, options, &service_process)) {
532 i : LOG(ERROR) << "Failed to launch process.";
533 i : return false;
534 : }
535 i : DCHECK_NE(base::kNullProcessHandle, service_process);
536 :
537 : // Setup the start event.
538 i : base::win::ScopedHandle start_event;
539 : std::wstring start_event_name(
540 i : GetInstanceString(kLoggerStartEventRoot, instance_id_));
541 i : if (!InitEvent(start_event_name, &start_event)) {
542 i : LOG(ERROR) << "Unable to init start event for '" << logger_name << "'.";
543 i : return false;
544 : }
545 :
546 : // We wait on both the start event and the process, as if the process fails
547 : // for any reason, it'll exit and its handle will become signaled.
548 i : HANDLE handles[] = { start_event, service_process };
549 : if (::WaitForMultipleObjects(arraysize(handles),
550 : handles,
551 : FALSE,
552 i : INFINITE) != WAIT_OBJECT_0) {
553 i : LOG(ERROR) << "The logger '" << logger_name << "' exited in error.";
554 i : return false;
555 : }
556 :
557 i : LOG(INFO) << "Background logger '" << logger_name << "' is running.";
558 :
559 i : return true;
560 i : }
561 :
562 i : bool LoggerApp::Stop() {
563 : std::wstring logger_name(
564 i : GetInstanceString(kLoggerRpcEndpointRoot, instance_id_));
565 :
566 : // Setup the stop event.
567 i : base::win::ScopedHandle stop_event;
568 : std::wstring stop_event_name(
569 i : GetInstanceString(kLoggerStopEventRoot, instance_id_));
570 i : if (!InitEvent(stop_event_name, &stop_event)) {
571 i : LOG(ERROR) << "Unable to init stop event for '" << logger_name << "'.";
572 i : return false;
573 : }
574 :
575 : // Send the stop request.
576 i : if (!SendStopRequest(instance_id_))
577 i : return false;
578 :
579 : // We wait on both the RPC event and the process, as if the process fails for
580 : // any reason, it'll exit and its handle will become signaled.
581 i : if (::WaitForSingleObject(stop_event, INFINITE) != WAIT_OBJECT_0) {
582 i : LOG(ERROR) << "Timed out waiting for '" << logger_name << "' to stop.";
583 i : return false;
584 : }
585 :
586 i : LOG(INFO) << "The logger instance has stopped.";
587 :
588 i : return true;
589 i : }
590 :
591 : // Helper to resolve @p path to an open file. This will set @p must_close
592 : // to true if @path denotes a newly opened file, and false if it denotes
593 : // stderr or stdout.
594 E : bool LoggerApp::OpenOutputFile(FILE** output_file, bool* must_close) {
595 E : DCHECK(output_file != NULL);
596 E : DCHECK(must_close != NULL);
597 :
598 E : *output_file = NULL;
599 E : *must_close = false;
600 :
601 : // Check for stdout.
602 : if (output_file_path_.empty() ||
603 : ::_wcsnicmp(output_file_path_.value().c_str(),
604 : kStdOut,
605 E : arraysize(kStdOut)) == 0) {
606 E : *output_file = stdout;
607 E : return true;
608 : }
609 :
610 : // Check for stderr.
611 : if (::_wcsnicmp(output_file_path_.value().c_str(),
612 : kStdErr,
613 i : arraysize(kStdErr)) == 0) {
614 i : *output_file = stderr;
615 i : return true;
616 : }
617 :
618 : // Setup the write mode.
619 i : const char* mode = "wb";
620 i : if (append_)
621 i : mode = "ab";
622 :
623 : // Create a new file, which the caller is responsible for closing.
624 i : *output_file = file_util::OpenFile(output_file_path_, mode);
625 i : if (*output_file == NULL)
626 i : return false;
627 :
628 i : *must_close = true;
629 i : return true;
630 E : }
631 :
632 : // Print the usage/help text, plus an optional @p message.
633 : bool LoggerApp::Usage(const CommandLine* command_line,
634 E : const base::StringPiece& message) const {
635 E : if (!message.empty()) {
636 E : ::fwrite(message.data(), 1, message.length(), err());
637 E : ::fprintf(err(), "\n\n");
638 : }
639 :
640 : ::fprintf(err(),
641 : kUsageFormatStr,
642 E : command_line->GetProgram().BaseName().value().c_str());
643 :
644 E : return false;
645 E : }
646 :
647 : } // namespace logger
648 : } // namespace trace
|