1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "chrome/browser/chromeos/system_logs/debug_log_writer.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/callback.h" 10 #include "base/command_line.h" 11 #include "base/files/file.h" 12 #include "base/files/file_util.h" 13 #include "base/message_loop/message_loop.h" 14 #include "base/process/kill.h" 15 #include "base/process/launch.h" 16 #include "chrome/common/logging_chrome.h" 17 #include "chromeos/dbus/dbus_thread_manager.h" 18 #include "chromeos/dbus/debug_daemon_client.h" 19 #include "content/public/browser/browser_thread.h" 20 21 namespace chromeos { 22 23 namespace { 24 25 // Callback for returning status of executed external command. 26 typedef base::Callback<void(bool succeeded)> CommandCompletionCallback; 27 28 const char kGzipCommand[] = "/bin/gzip"; 29 const char kTarCommand[] = "/bin/tar"; 30 31 scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner( 32 const std::string sequence_name) { 33 base::SequencedWorkerPool* pool = content::BrowserThread::GetBlockingPool(); 34 return pool->GetSequencedTaskRunnerWithShutdownBehavior( 35 pool->GetNamedSequenceToken(sequence_name), 36 base::SequencedWorkerPool::BLOCK_SHUTDOWN); 37 } 38 39 // Called upon completion of |WriteDebugLogToFile|. Closes file 40 // descriptor, deletes log file in the case of failure and calls 41 // |callback|. 42 void WriteDebugLogToFileCompleted( 43 const base::FilePath& file_path, 44 const std::string& sequence_token_name, 45 const DebugLogWriter::StoreLogsCallback& callback, 46 bool succeeded) { 47 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 48 if (!succeeded) { 49 bool posted = GetSequencedTaskRunner(sequence_token_name)->PostTaskAndReply( 50 FROM_HERE, 51 base::Bind(base::IgnoreResult(&base::DeleteFile), file_path, false), 52 base::Bind(callback, file_path, false)); 53 DCHECK(posted); 54 return; 55 } 56 if (!callback.is_null()) 57 callback.Run(file_path, true); 58 } 59 60 // Stores into |file_path| debug logs in the .tgz format. Calls 61 // |callback| upon completion. 62 void WriteDebugLogToFile(base::File* file, 63 const std::string& sequence_token_name, 64 const base::FilePath& file_path, 65 bool should_compress, 66 const DebugLogWriter::StoreLogsCallback& callback) { 67 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 68 if (!file->IsValid()) { 69 LOG(ERROR) << "Can't create debug log file: " << file_path.AsUTF8Unsafe() 70 << ", " 71 << "error: " << file->error_details(); 72 return; 73 } 74 scoped_refptr<base::TaskRunner> task_runner = 75 GetSequencedTaskRunner(sequence_token_name); 76 chromeos::DBusThreadManager::Get()->GetDebugDaemonClient()->DumpDebugLogs( 77 should_compress, 78 file->Pass(), 79 task_runner, 80 base::Bind(&WriteDebugLogToFileCompleted, 81 file_path, 82 sequence_token_name, 83 callback)); 84 } 85 86 // Runs command with its parameters as defined in |argv|. 87 // Upon completion, it will report command run outcome via |callback| on the 88 // same thread from where it was initially called from. 89 void RunCommand(const std::vector<std::string>& argv, 90 const CommandCompletionCallback& callback) { 91 base::ProcessHandle handle = base::kNullProcessHandle; 92 if (!base::LaunchProcess(argv, base::LaunchOptions(), &handle)) { 93 LOG(ERROR) << "Failed to execute command " << argv[0]; 94 if (!callback.is_null()) 95 callback.Run(false); 96 97 return; 98 } 99 100 int exit_code = 0; 101 if (!base::WaitForExitCode(handle, &exit_code)) { 102 LOG(ERROR) << "Can't get exit code for pid " << handle; 103 if (!callback.is_null()) 104 callback.Run(false); 105 106 return; 107 } 108 if (!callback.is_null()) 109 callback.Run(exit_code == 0); 110 } 111 112 // Callback for handling the outcome of CompressArchive(). It reports 113 // the final outcome of log retreival process at via |callback|. 114 void OnCompressArchiveCompleted( 115 const base::FilePath& tar_file_path, 116 const base::FilePath& compressed_output_path, 117 const DebugLogWriter::StoreLogsCallback& callback, 118 bool compression_command_success) { 119 if (!compression_command_success) { 120 LOG(ERROR) << "Failed compressing " << compressed_output_path.value(); 121 content::BrowserThread::PostTask( 122 content::BrowserThread::UI, 123 FROM_HERE, 124 base::Bind(callback, base::FilePath(), false)); 125 base::DeleteFile(tar_file_path, true); 126 base::DeleteFile(compressed_output_path, true); 127 return; 128 } 129 130 content::BrowserThread::PostTask( 131 content::BrowserThread::UI, 132 FROM_HERE, 133 base::Bind(callback, compressed_output_path, true)); 134 } 135 136 // Gzips |tar_file_path| and stores results in |compressed_output_path|. 137 void CompressArchive(const base::FilePath& tar_file_path, 138 const base::FilePath& compressed_output_path, 139 const DebugLogWriter::StoreLogsCallback& callback, 140 bool add_user_logs_command_success) { 141 if (!add_user_logs_command_success) { 142 LOG(ERROR) << "Failed adding user logs to " << tar_file_path.value(); 143 content::BrowserThread::PostTask( 144 content::BrowserThread::UI, 145 FROM_HERE, 146 base::Bind(callback, base::FilePath(), false)); 147 base::DeleteFile(tar_file_path, true); 148 return; 149 } 150 151 std::vector<std::string> argv; 152 argv.push_back(kGzipCommand); 153 argv.push_back(tar_file_path.value()); 154 RunCommand(argv, 155 base::Bind(&OnCompressArchiveCompleted, 156 tar_file_path, 157 compressed_output_path, 158 callback)); 159 } 160 161 // Adds user sessions specific logs from |user_log_dir| into tar archive file 162 // at |tar_file_path|. Upon completion, it will call CompressArchive() to 163 // produce |compressed_output_path|. 164 void AddUserLogsToArchive(const base::FilePath& user_log_dir, 165 const base::FilePath& tar_file_path, 166 const base::FilePath& compressed_output_path, 167 const DebugLogWriter::StoreLogsCallback& callback) { 168 std::vector<std::string> argv; 169 argv.push_back(kTarCommand); 170 argv.push_back("-rvf"); 171 argv.push_back(tar_file_path.value()); 172 argv.push_back(user_log_dir.value()); 173 RunCommand( 174 argv, 175 base::Bind( 176 &CompressArchive, tar_file_path, compressed_output_path, callback)); 177 } 178 179 // Appends user logs after system logs are archived into |tar_file_path|. 180 void OnSystemLogsAdded(const DebugLogWriter::StoreLogsCallback& callback, 181 const base::FilePath& tar_file_path, 182 bool succeeded) { 183 if (!succeeded) { 184 if (!callback.is_null()) 185 callback.Run(base::FilePath(), false); 186 187 return; 188 } 189 190 base::FilePath compressed_output_path = 191 tar_file_path.AddExtension(FILE_PATH_LITERAL(".gz")); 192 base::FilePath user_log_dir = 193 logging::GetSessionLogDir(*CommandLine::ForCurrentProcess()); 194 195 content::BrowserThread::PostBlockingPoolTask( 196 FROM_HERE, 197 base::Bind(&AddUserLogsToArchive, 198 user_log_dir, 199 tar_file_path, 200 compressed_output_path, 201 callback)); 202 } 203 204 void IntializeLogFile(base::File* file, 205 const base::FilePath& file_path, 206 uint32 flags) { 207 base::FilePath dir = file_path.DirName(); 208 if (!base::DirectoryExists(dir)) { 209 if (!base::CreateDirectory(dir)) { 210 LOG(ERROR) << "Can not create " << dir.value(); 211 return; 212 } 213 } 214 215 file->Initialize(file_path, flags); 216 } 217 218 // Starts logs retrieval process. The output will be stored in file with name 219 // derived from |file_name_template|. 220 void StartLogRetrieval(const base::FilePath& file_name_template, 221 bool should_compress, 222 const std::string& sequence_token_name, 223 const DebugLogWriter::StoreLogsCallback& callback) { 224 base::FilePath file_path = 225 logging::GenerateTimestampedName(file_name_template, base::Time::Now()); 226 227 int flags = base::File::FLAG_CREATE_ALWAYS | base::File::FLAG_WRITE; 228 base::File* file = new base::File; 229 GetSequencedTaskRunner(sequence_token_name)->PostTaskAndReply( 230 FROM_HERE, 231 base::Bind(&IntializeLogFile, base::Unretained(file), file_path, flags), 232 base::Bind(&WriteDebugLogToFile, 233 base::Owned(file), 234 sequence_token_name, 235 file_path, 236 should_compress, 237 callback)); 238 } 239 240 const char kDefaultSequenceName[] = "DebugLogWriter"; 241 242 } // namespace 243 244 // static. 245 void DebugLogWriter::StoreLogs(const base::FilePath& fileshelf, 246 bool should_compress, 247 const StoreLogsCallback& callback) { 248 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 249 DCHECK(!callback.is_null()); 250 251 base::FilePath file_path = 252 fileshelf.Append(should_compress ? FILE_PATH_LITERAL("debug-logs.tgz") 253 : FILE_PATH_LITERAL("debug-logs.tar")); 254 255 StartLogRetrieval(file_path, should_compress, kDefaultSequenceName, callback); 256 } 257 258 // static. 259 void DebugLogWriter::StoreCombinedLogs(const base::FilePath& fileshelf, 260 const std::string& sequence_token_name, 261 const StoreLogsCallback& callback) { 262 DCHECK_CURRENTLY_ON(content::BrowserThread::UI); 263 DCHECK(!callback.is_null()); 264 265 base::FilePath file_path = 266 fileshelf.Append(FILE_PATH_LITERAL("combined-logs.tar")); 267 268 // Get system logs from /var/log first, then add user-specific stuff. 269 StartLogRetrieval(file_path, 270 false, 271 sequence_token_name, 272 base::Bind(&OnSystemLogsAdded, callback)); 273 } 274 275 } // namespace chromeos 276