Index: sql/connection.cc |
diff --git a/sql/connection.cc b/sql/connection.cc |
index 61aebbe0c94f8640904875d1a7d86a5627906181..5b442fdef323fe59455a607672d585352750b79d 100644 |
--- a/sql/connection.cc |
+++ b/sql/connection.cc |
@@ -9,6 +9,7 @@ |
#include "base/bind.h" |
#include "base/files/file_path.h" |
#include "base/files/file_util.h" |
+#include "base/json/json_file_value_serializer.h" |
#include "base/lazy_instance.h" |
#include "base/logging.h" |
#include "base/message_loop/message_loop.h" |
@@ -1533,4 +1534,127 @@ base::TimeTicks TimeSource::Now() { |
return base::TimeTicks::Now(); |
} |
+base::FilePath Connection::DbPath() const { |
+ if (!is_open()) |
+ return base::FilePath(); |
+ |
+ const char* path = sqlite3_db_filename(db_, "main"); |
+ const base::StringPiece db_path(path); |
+#if defined(OS_WIN) |
+ return base::FilePath(base::UTF8ToWide(db_path)); |
+#elif defined(OS_POSIX) |
+ return base::FilePath(db_path); |
+#else |
+ NOTREACHED(); |
+ return base::FilePath(); |
+#endif |
+} |
+ |
+// Decide whether to upload based on a shared breadcrumb file in the database |
+// directory. The breadcrumb file records whether a past upload has occurred, |
+// and also acts as a probe to determine if basic filesystem actions work in the |
+// profile directory. |
+// |
+// Top level is a dictionary storing a version number, and an array of databases |
+// which have been dumped. |
+bool Connection::ShouldUploadDiagnosticDump() const { |
pkotwicz
2015/10/15 18:02:29
Nit: Can you please move this function so that it
Scott Hess - ex-Googler
2015/10/15 21:12:20
I can put it after OnMemoryDump(), but the functio
|
+ static const char* kVersionKey = "version"; |
+ static const char* kDiagnosticDumpsKey = "DiagnosticDumps"; |
+ static int kVersion = 1; |
+ |
+ AssertIOAllowed(); |
+ |
+ if (histogram_tag_.empty()) |
+ return false; |
+ |
+ if (!is_open()) |
+ return false; |
+ |
+ if (in_memory_) |
+ return false; |
+ |
+ const base::FilePath db_path = DbPath(); |
+ if (db_path.empty()) |
+ return false; |
+ |
+ // Put the collection of diagnostic data next to the databases. In most |
+ // cases, this is the profile directory, but safe-browsing stores a Cookies |
+ // file in the directory above the profile directory. |
+ base::FilePath breadcrumb_path( |
+ db_path.DirName().Append(FILE_PATH_LITERAL("sqlite-diag"))); |
+ |
+ // Lock against multiple updates to the diagnostics file. This code should |
+ // seldom be called in the first place, and when called it should seldom be |
+ // called for multiple databases, and when called for multiple databases there |
+ // is _probably_ something systemic wrong with the user's system. So the lock |
+ // should never be contended, but when it is the database experience is |
+ // already bad. |
+ base::AutoLock lock(g_sqlite_init_lock.Get()); |
+ |
+ scoped_ptr<base::Value> root; |
+ if (!base::PathExists(breadcrumb_path)) { |
+ scoped_ptr<base::DictionaryValue> root_dict(new base::DictionaryValue()); |
+ root_dict->SetInteger(kVersionKey, kVersion); |
+ |
+ scoped_ptr<base::ListValue> dumps(new base::ListValue); |
+ dumps->AppendString(histogram_tag_); |
+ root_dict->Set(kDiagnosticDumpsKey, dumps.Pass()); |
+ |
+ root = root_dict.Pass(); |
+ } else { |
+ // Failure to read a valid dictionary implies that something is going wrong |
+ // on the system. |
+ JSONFileValueDeserializer deserializer(breadcrumb_path); |
+ scoped_ptr<base::Value> read_root(deserializer.Deserialize(NULL, NULL)); |
pkotwicz
2015/10/15 18:02:30
Nit: NULL -> nullptr
Scott Hess - ex-Googler
2015/10/15 21:12:21
Acknowledged.
|
+ if (!read_root.get()) |
+ return false; |
+ scoped_ptr<base::DictionaryValue> root_dict = |
+ base::DictionaryValue::From(read_root.Pass()); |
+ if (!root_dict) |
+ return false; |
+ |
+ // Don't upload if the version is missing or newer. |
+ int version = 0; |
+ if (!root_dict->GetInteger(kVersionKey, &version) || version > kVersion) |
+ return false; |
+ |
+ base::ListValue* dumps = NULL; |
pkotwicz
2015/10/15 18:02:29
Nit: NULL -> nullptr
Scott Hess - ex-Googler
2015/10/15 21:12:21
Acknowledged.
|
+ if (!root_dict->GetList(kDiagnosticDumpsKey, &dumps)) |
+ return false; |
+ |
+ const size_t size = dumps->GetSize(); |
+ for (size_t i = 0; i < size; ++i) { |
+ std::string s; |
+ |
+ // Don't upload if the value isn't a string, or indicates a prior upload. |
+ if (!dumps->GetString(i, &s) || s == histogram_tag_) |
+ return false; |
+ } |
+ |
+ // Record intention to proceed with upload. |
+ dumps->AppendString(histogram_tag_); |
+ root = root_dict.Pass(); |
+ } |
+ |
+ const base::FilePath breadcrumb_new = |
+ breadcrumb_path.AddExtension(FILE_PATH_LITERAL("new")); |
pkotwicz
2015/10/15 18:02:29
Should we use base::CreateTemporaryFile() instead?
Scott Hess - ex-Googler
2015/10/15 21:12:20
I'll assume you intended CreateTemporaryFileInDir(
pkotwicz
2015/10/15 23:31:06
Fair enough
|
+ base::DeleteFile(breadcrumb_new, false); |
+ |
+ // No upload if the breadcrumb file cannot be updated. |
+ // TODO(shess): Consider ImportantFileWriter::WriteFileAtomically() to land |
+ // the data on disk. For now, losing the data is not a big problem, so the |
+ // sync overhead would probably not be worth it. |
+ JSONFileValueSerializer serializer(breadcrumb_new); |
+ if (!serializer.Serialize(*root)) |
+ return false; |
+ if (!base::PathExists(breadcrumb_new)) |
+ return false; |
+ if (!base::ReplaceFile(breadcrumb_new, breadcrumb_path, nullptr)) { |
+ base::DeleteFile(breadcrumb_new, false); |
+ return false; |
+ } |
+ |
+ return true; |
+} |
+ |
} // namespace sql |