OLD | NEW |
| (Empty) |
1 // Copyright 2013 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 "content/browser/media/webrtc/webrtc_identity_store_backend.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdint.h> | |
9 | |
10 #include <tuple> | |
11 | |
12 #include "base/files/file_path.h" | |
13 #include "base/files/file_util.h" | |
14 #include "base/macros.h" | |
15 #include "base/memory/scoped_vector.h" | |
16 #include "base/strings/string_util.h" | |
17 #include "content/public/browser/browser_thread.h" | |
18 #include "net/base/net_errors.h" | |
19 #include "sql/statement.h" | |
20 #include "sql/transaction.h" | |
21 #include "storage/browser/quota/special_storage_policy.h" | |
22 #include "url/gurl.h" | |
23 | |
24 namespace content { | |
25 | |
26 static const char kWebRTCIdentityStoreDBName[] = "webrtc_identity_store"; | |
27 | |
28 static const base::FilePath::CharType kWebRTCIdentityStoreDirectory[] = | |
29 FILE_PATH_LITERAL("WebRTCIdentityStore"); | |
30 | |
31 // Initializes the identity table, returning true on success. | |
32 static bool InitDB(sql::Connection* db) { | |
33 if (db->DoesTableExist(kWebRTCIdentityStoreDBName)) { | |
34 if (db->DoesColumnExist(kWebRTCIdentityStoreDBName, "origin") && | |
35 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "identity_name") && | |
36 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "common_name") && | |
37 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "certificate") && | |
38 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "private_key") && | |
39 db->DoesColumnExist(kWebRTCIdentityStoreDBName, "creation_time")) | |
40 return true; | |
41 | |
42 if (!db->Execute("DROP TABLE webrtc_identity_store")) | |
43 return false; | |
44 } | |
45 | |
46 return db->Execute( | |
47 "CREATE TABLE webrtc_identity_store" | |
48 " (" | |
49 "origin TEXT NOT NULL," | |
50 "identity_name TEXT NOT NULL," | |
51 "common_name TEXT NOT NULL," | |
52 "certificate BLOB NOT NULL," | |
53 "private_key BLOB NOT NULL," | |
54 "creation_time INTEGER)"); | |
55 } | |
56 | |
57 struct WebRTCIdentityStoreBackend::IdentityKey { | |
58 IdentityKey(const GURL& origin, const std::string& identity_name) | |
59 : origin(origin), identity_name(identity_name) {} | |
60 | |
61 bool operator<(const IdentityKey& other) const { | |
62 return std::tie(origin, identity_name) < | |
63 std::tie(other.origin, other.identity_name); | |
64 } | |
65 | |
66 GURL origin; | |
67 std::string identity_name; | |
68 }; | |
69 | |
70 struct WebRTCIdentityStoreBackend::Identity { | |
71 Identity(const std::string& common_name, | |
72 const std::string& certificate, | |
73 const std::string& private_key) | |
74 : common_name(common_name), | |
75 certificate(certificate), | |
76 private_key(private_key), | |
77 creation_time(base::Time::Now().ToInternalValue()) {} | |
78 | |
79 Identity(const std::string& common_name, | |
80 const std::string& certificate, | |
81 const std::string& private_key, | |
82 int64_t creation_time) | |
83 : common_name(common_name), | |
84 certificate(certificate), | |
85 private_key(private_key), | |
86 creation_time(creation_time) {} | |
87 | |
88 std::string common_name; | |
89 std::string certificate; | |
90 std::string private_key; | |
91 int64_t creation_time; | |
92 }; | |
93 | |
94 struct WebRTCIdentityStoreBackend::PendingFindRequest { | |
95 PendingFindRequest(const GURL& origin, | |
96 const std::string& identity_name, | |
97 const std::string& common_name, | |
98 const FindIdentityCallback& callback) | |
99 : origin(origin), | |
100 identity_name(identity_name), | |
101 common_name(common_name), | |
102 callback(callback) {} | |
103 | |
104 ~PendingFindRequest() {} | |
105 | |
106 GURL origin; | |
107 std::string identity_name; | |
108 std::string common_name; | |
109 FindIdentityCallback callback; | |
110 }; | |
111 | |
112 // The class encapsulates the database operations. All members except ctor and | |
113 // dtor should be accessed on the DB thread. | |
114 // It can be created/destroyed on any thread. | |
115 class WebRTCIdentityStoreBackend::SqlLiteStorage | |
116 : public base::RefCountedThreadSafe<SqlLiteStorage> { | |
117 public: | |
118 SqlLiteStorage(base::TimeDelta validity_period, | |
119 const base::FilePath& path, | |
120 storage::SpecialStoragePolicy* policy) | |
121 : validity_period_(validity_period), special_storage_policy_(policy) { | |
122 if (!path.empty()) | |
123 path_ = path.Append(kWebRTCIdentityStoreDirectory); | |
124 } | |
125 | |
126 void Load(IdentityMap* out_map); | |
127 void Close(); | |
128 void AddIdentity(const GURL& origin, | |
129 const std::string& identity_name, | |
130 const Identity& identity); | |
131 void DeleteIdentity(const GURL& origin, | |
132 const std::string& identity_name, | |
133 const Identity& identity); | |
134 void DeleteBetween(base::Time delete_begin, base::Time delete_end); | |
135 | |
136 void SetValidityPeriodForTesting(base::TimeDelta validity_period) { | |
137 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
138 DCHECK(!db_.get()); | |
139 validity_period_ = validity_period; | |
140 } | |
141 | |
142 private: | |
143 friend class base::RefCountedThreadSafe<SqlLiteStorage>; | |
144 | |
145 enum OperationType { | |
146 ADD_IDENTITY, | |
147 DELETE_IDENTITY | |
148 }; | |
149 struct PendingOperation { | |
150 PendingOperation(OperationType type, | |
151 const GURL& origin, | |
152 const std::string& identity_name, | |
153 const Identity& identity) | |
154 : type(type), | |
155 origin(origin), | |
156 identity_name(identity_name), | |
157 identity(identity) {} | |
158 | |
159 OperationType type; | |
160 GURL origin; | |
161 std::string identity_name; | |
162 Identity identity; | |
163 }; | |
164 typedef ScopedVector<PendingOperation> PendingOperationList; | |
165 | |
166 virtual ~SqlLiteStorage() {} | |
167 void OnDatabaseError(int error, sql::Statement* stmt); | |
168 void BatchOperation(OperationType type, | |
169 const GURL& origin, | |
170 const std::string& identity_name, | |
171 const Identity& identity); | |
172 void Commit(); | |
173 | |
174 base::TimeDelta validity_period_; | |
175 // The file path of the DB. Empty if temporary. | |
176 base::FilePath path_; | |
177 scoped_refptr<storage::SpecialStoragePolicy> special_storage_policy_; | |
178 std::unique_ptr<sql::Connection> db_; | |
179 // Batched DB operations pending to commit. | |
180 PendingOperationList pending_operations_; | |
181 | |
182 DISALLOW_COPY_AND_ASSIGN(SqlLiteStorage); | |
183 }; | |
184 | |
185 WebRTCIdentityStoreBackend::WebRTCIdentityStoreBackend( | |
186 const base::FilePath& path, | |
187 storage::SpecialStoragePolicy* policy, | |
188 base::TimeDelta validity_period) | |
189 : validity_period_(validity_period), | |
190 state_(NOT_STARTED), | |
191 sql_lite_storage_(new SqlLiteStorage(validity_period, path, policy)) { | |
192 } | |
193 | |
194 bool WebRTCIdentityStoreBackend::FindIdentity( | |
195 const GURL& origin, | |
196 const std::string& identity_name, | |
197 const std::string& common_name, | |
198 const FindIdentityCallback& callback) { | |
199 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
200 if (state_ == CLOSED) | |
201 return false; | |
202 | |
203 if (state_ != LOADED) { | |
204 // Queues the request to wait for the DB to load. | |
205 pending_find_requests_.push_back( | |
206 new PendingFindRequest(origin, identity_name, common_name, callback)); | |
207 if (state_ == LOADING) | |
208 return true; | |
209 | |
210 DCHECK_EQ(state_, NOT_STARTED); | |
211 | |
212 // Kick off loading the DB. | |
213 std::unique_ptr<IdentityMap> out_map(new IdentityMap()); | |
214 base::Closure task( | |
215 base::Bind(&SqlLiteStorage::Load, sql_lite_storage_, out_map.get())); | |
216 // |out_map| will be NULL after this call. | |
217 if (BrowserThread::PostTaskAndReply( | |
218 BrowserThread::DB, | |
219 FROM_HERE, | |
220 task, | |
221 base::Bind(&WebRTCIdentityStoreBackend::OnLoaded, | |
222 this, | |
223 base::Passed(&out_map)))) { | |
224 state_ = LOADING; | |
225 return true; | |
226 } | |
227 // If it fails to post task, falls back to ERR_FILE_NOT_FOUND. | |
228 } | |
229 | |
230 IdentityKey key(origin, identity_name); | |
231 IdentityMap::iterator iter = identities_.find(key); | |
232 if (iter != identities_.end() && iter->second.common_name == common_name) { | |
233 base::TimeDelta age = base::Time::Now() - base::Time::FromInternalValue( | |
234 iter->second.creation_time); | |
235 if (age < validity_period_) { | |
236 // Identity found. | |
237 return BrowserThread::PostTask(BrowserThread::IO, | |
238 FROM_HERE, | |
239 base::Bind(callback, | |
240 net::OK, | |
241 iter->second.certificate, | |
242 iter->second.private_key)); | |
243 } | |
244 // Removes the expired identity from the in-memory cache. The copy in the | |
245 // database will be removed on the next load. | |
246 identities_.erase(iter); | |
247 } | |
248 | |
249 return BrowserThread::PostTask( | |
250 BrowserThread::IO, | |
251 FROM_HERE, | |
252 base::Bind(callback, net::ERR_FILE_NOT_FOUND, "", "")); | |
253 } | |
254 | |
255 void WebRTCIdentityStoreBackend::AddIdentity(const GURL& origin, | |
256 const std::string& identity_name, | |
257 const std::string& common_name, | |
258 const std::string& certificate, | |
259 const std::string& private_key) { | |
260 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
261 if (state_ == CLOSED) | |
262 return; | |
263 | |
264 // If there is an existing identity for the same origin and identity_name, | |
265 // delete it. | |
266 IdentityKey key(origin, identity_name); | |
267 Identity identity(common_name, certificate, private_key); | |
268 | |
269 if (identities_.find(key) != identities_.end()) { | |
270 if (!BrowserThread::PostTask(BrowserThread::DB, | |
271 FROM_HERE, | |
272 base::Bind(&SqlLiteStorage::DeleteIdentity, | |
273 sql_lite_storage_, | |
274 origin, | |
275 identity_name, | |
276 identities_.find(key)->second))) | |
277 return; | |
278 } | |
279 identities_.insert(std::pair<IdentityKey, Identity>(key, identity)); | |
280 | |
281 BrowserThread::PostTask(BrowserThread::DB, | |
282 FROM_HERE, | |
283 base::Bind(&SqlLiteStorage::AddIdentity, | |
284 sql_lite_storage_, | |
285 origin, | |
286 identity_name, | |
287 identity)); | |
288 } | |
289 | |
290 void WebRTCIdentityStoreBackend::Close() { | |
291 if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) { | |
292 BrowserThread::PostTask( | |
293 BrowserThread::IO, | |
294 FROM_HERE, | |
295 base::Bind(&WebRTCIdentityStoreBackend::Close, this)); | |
296 return; | |
297 } | |
298 | |
299 if (state_ == CLOSED) | |
300 return; | |
301 | |
302 state_ = CLOSED; | |
303 BrowserThread::PostTask( | |
304 BrowserThread::DB, | |
305 FROM_HERE, | |
306 base::Bind(&SqlLiteStorage::Close, sql_lite_storage_)); | |
307 } | |
308 | |
309 void WebRTCIdentityStoreBackend::DeleteBetween(base::Time delete_begin, | |
310 base::Time delete_end, | |
311 const base::Closure& callback) { | |
312 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
313 if (state_ == CLOSED) | |
314 return; | |
315 | |
316 // Delete the in-memory cache. | |
317 IdentityMap::iterator it = identities_.begin(); | |
318 while (it != identities_.end()) { | |
319 if (it->second.creation_time >= delete_begin.ToInternalValue() && | |
320 it->second.creation_time <= delete_end.ToInternalValue()) { | |
321 identities_.erase(it++); | |
322 } else { | |
323 ++it; | |
324 } | |
325 } | |
326 BrowserThread::PostTaskAndReply(BrowserThread::DB, | |
327 FROM_HERE, | |
328 base::Bind(&SqlLiteStorage::DeleteBetween, | |
329 sql_lite_storage_, | |
330 delete_begin, | |
331 delete_end), | |
332 callback); | |
333 } | |
334 | |
335 void WebRTCIdentityStoreBackend::SetValidityPeriodForTesting( | |
336 base::TimeDelta validity_period) { | |
337 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
338 validity_period_ = validity_period; | |
339 BrowserThread::PostTask( | |
340 BrowserThread::DB, | |
341 FROM_HERE, | |
342 base::Bind(&SqlLiteStorage::SetValidityPeriodForTesting, | |
343 sql_lite_storage_, | |
344 validity_period)); | |
345 } | |
346 | |
347 WebRTCIdentityStoreBackend::~WebRTCIdentityStoreBackend() {} | |
348 | |
349 void WebRTCIdentityStoreBackend::OnLoaded( | |
350 std::unique_ptr<IdentityMap> out_map) { | |
351 DCHECK_CURRENTLY_ON(BrowserThread::IO); | |
352 | |
353 if (state_ != LOADING) | |
354 return; | |
355 | |
356 DVLOG(3) << "WebRTC identity store has loaded."; | |
357 | |
358 state_ = LOADED; | |
359 identities_.swap(*out_map); | |
360 | |
361 for (size_t i = 0; i < pending_find_requests_.size(); ++i) { | |
362 FindIdentity(pending_find_requests_[i]->origin, | |
363 pending_find_requests_[i]->identity_name, | |
364 pending_find_requests_[i]->common_name, | |
365 pending_find_requests_[i]->callback); | |
366 delete pending_find_requests_[i]; | |
367 } | |
368 pending_find_requests_.clear(); | |
369 } | |
370 | |
371 // | |
372 // Implementation of SqlLiteStorage. | |
373 // | |
374 | |
375 void WebRTCIdentityStoreBackend::SqlLiteStorage::Load(IdentityMap* out_map) { | |
376 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
377 DCHECK(!db_.get()); | |
378 | |
379 // Ensure the parent directory for storing certs is created before reading | |
380 // from it. | |
381 const base::FilePath dir = path_.DirName(); | |
382 if (!base::PathExists(dir) && !base::CreateDirectory(dir)) { | |
383 DVLOG(2) << "Unable to open DB file path."; | |
384 return; | |
385 } | |
386 | |
387 db_.reset(new sql::Connection()); | |
388 | |
389 db_->set_error_callback(base::Bind(&SqlLiteStorage::OnDatabaseError, this)); | |
390 | |
391 if (!db_->Open(path_)) { | |
392 DVLOG(2) << "Unable to open DB."; | |
393 db_.reset(); | |
394 return; | |
395 } | |
396 | |
397 if (!InitDB(db_.get())) { | |
398 DVLOG(2) << "Unable to init DB."; | |
399 db_.reset(); | |
400 return; | |
401 } | |
402 | |
403 db_->Preload(); | |
404 | |
405 // Delete expired identities. | |
406 DeleteBetween(base::Time(), base::Time::Now() - validity_period_); | |
407 | |
408 // Slurp all the identities into the out_map. | |
409 sql::Statement stmt(db_->GetUniqueStatement( | |
410 "SELECT origin, identity_name, common_name, " | |
411 "certificate, private_key, creation_time " | |
412 "FROM webrtc_identity_store")); | |
413 CHECK(stmt.is_valid()); | |
414 | |
415 while (stmt.Step()) { | |
416 IdentityKey key(GURL(stmt.ColumnString(0)), stmt.ColumnString(1)); | |
417 std::string common_name(stmt.ColumnString(2)); | |
418 std::string cert, private_key; | |
419 stmt.ColumnBlobAsString(3, &cert); | |
420 stmt.ColumnBlobAsString(4, &private_key); | |
421 int64_t creation_time = stmt.ColumnInt64(5); | |
422 std::pair<IdentityMap::iterator, bool> result = | |
423 out_map->insert(std::pair<IdentityKey, Identity>( | |
424 key, Identity(common_name, cert, private_key, creation_time))); | |
425 DCHECK(result.second); | |
426 } | |
427 } | |
428 | |
429 void WebRTCIdentityStoreBackend::SqlLiteStorage::Close() { | |
430 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
431 Commit(); | |
432 db_.reset(); | |
433 } | |
434 | |
435 void WebRTCIdentityStoreBackend::SqlLiteStorage::AddIdentity( | |
436 const GURL& origin, | |
437 const std::string& identity_name, | |
438 const Identity& identity) { | |
439 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
440 if (!db_.get()) | |
441 return; | |
442 | |
443 // Do not add for session only origins. | |
444 if (special_storage_policy_.get() && | |
445 !special_storage_policy_->IsStorageProtected(origin) && | |
446 special_storage_policy_->IsStorageSessionOnly(origin)) { | |
447 return; | |
448 } | |
449 BatchOperation(ADD_IDENTITY, origin, identity_name, identity); | |
450 } | |
451 | |
452 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteIdentity( | |
453 const GURL& origin, | |
454 const std::string& identity_name, | |
455 const Identity& identity) { | |
456 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
457 if (!db_.get()) | |
458 return; | |
459 BatchOperation(DELETE_IDENTITY, origin, identity_name, identity); | |
460 } | |
461 | |
462 void WebRTCIdentityStoreBackend::SqlLiteStorage::DeleteBetween( | |
463 base::Time delete_begin, | |
464 base::Time delete_end) { | |
465 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
466 if (!db_.get()) | |
467 return; | |
468 | |
469 // Commit pending operations first. | |
470 Commit(); | |
471 | |
472 sql::Statement del_stmt(db_->GetCachedStatement( | |
473 SQL_FROM_HERE, | |
474 "DELETE FROM webrtc_identity_store" | |
475 " WHERE creation_time >= ? AND creation_time <= ?")); | |
476 CHECK(del_stmt.is_valid()); | |
477 | |
478 del_stmt.BindInt64(0, delete_begin.ToInternalValue()); | |
479 del_stmt.BindInt64(1, delete_end.ToInternalValue()); | |
480 | |
481 sql::Transaction transaction(db_.get()); | |
482 if (!transaction.Begin()) { | |
483 DVLOG(2) << "Failed to begin the transaction."; | |
484 return; | |
485 } | |
486 | |
487 if (!del_stmt.Run()) { | |
488 DVLOG(2) << "Failed to run the delete statement."; | |
489 return; | |
490 } | |
491 | |
492 if (!transaction.Commit()) | |
493 DVLOG(2) << "Failed to commit the transaction."; | |
494 } | |
495 | |
496 void WebRTCIdentityStoreBackend::SqlLiteStorage::OnDatabaseError( | |
497 int error, | |
498 sql::Statement* stmt) { | |
499 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
500 | |
501 db_->RazeAndClose(); | |
502 // It's not safe to reset |db_| here. | |
503 } | |
504 | |
505 void WebRTCIdentityStoreBackend::SqlLiteStorage::BatchOperation( | |
506 OperationType type, | |
507 const GURL& origin, | |
508 const std::string& identity_name, | |
509 const Identity& identity) { | |
510 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
511 // Commit every 30 seconds. | |
512 static const base::TimeDelta kCommitInterval( | |
513 base::TimeDelta::FromSeconds(30)); | |
514 // Commit right away if we have more than 512 outstanding operations. | |
515 static const size_t kCommitAfterBatchSize = 512; | |
516 | |
517 // We do a full copy of the cert here, and hopefully just here. | |
518 std::unique_ptr<PendingOperation> operation( | |
519 new PendingOperation(type, origin, identity_name, identity)); | |
520 | |
521 pending_operations_.push_back(operation.release()); | |
522 | |
523 if (pending_operations_.size() == 1) { | |
524 // We've gotten our first entry for this batch, fire off the timer. | |
525 BrowserThread::PostDelayedTask(BrowserThread::DB, | |
526 FROM_HERE, | |
527 base::Bind(&SqlLiteStorage::Commit, this), | |
528 kCommitInterval); | |
529 } else if (pending_operations_.size() >= kCommitAfterBatchSize) { | |
530 // We've reached a big enough batch, fire off a commit now. | |
531 BrowserThread::PostTask(BrowserThread::DB, | |
532 FROM_HERE, | |
533 base::Bind(&SqlLiteStorage::Commit, this)); | |
534 } | |
535 } | |
536 | |
537 void WebRTCIdentityStoreBackend::SqlLiteStorage::Commit() { | |
538 DCHECK_CURRENTLY_ON(BrowserThread::DB); | |
539 // Maybe an old timer fired or we are already Close()'ed. | |
540 if (!db_.get() || pending_operations_.empty()) | |
541 return; | |
542 | |
543 sql::Statement add_stmt(db_->GetCachedStatement( | |
544 SQL_FROM_HERE, | |
545 "INSERT INTO webrtc_identity_store " | |
546 "(origin, identity_name, common_name, certificate," | |
547 " private_key, creation_time) VALUES" | |
548 " (?,?,?,?,?,?)")); | |
549 | |
550 CHECK(add_stmt.is_valid()); | |
551 | |
552 sql::Statement del_stmt(db_->GetCachedStatement( | |
553 SQL_FROM_HERE, | |
554 "DELETE FROM webrtc_identity_store WHERE origin=? AND identity_name=?")); | |
555 | |
556 CHECK(del_stmt.is_valid()); | |
557 | |
558 sql::Transaction transaction(db_.get()); | |
559 if (!transaction.Begin()) { | |
560 DVLOG(2) << "Failed to begin the transaction."; | |
561 return; | |
562 } | |
563 | |
564 // Swaps |pending_operations_| into a temporary list to make sure | |
565 // |pending_operations_| is always cleared in case of DB errors. | |
566 PendingOperationList pending_operations_copy; | |
567 pending_operations_.swap(pending_operations_copy); | |
568 | |
569 for (PendingOperationList::const_iterator it = | |
570 pending_operations_copy.begin(); | |
571 it != pending_operations_copy.end(); | |
572 ++it) { | |
573 switch ((*it)->type) { | |
574 case ADD_IDENTITY: { | |
575 add_stmt.Reset(true); | |
576 add_stmt.BindString(0, (*it)->origin.spec()); | |
577 add_stmt.BindString(1, (*it)->identity_name); | |
578 add_stmt.BindString(2, (*it)->identity.common_name); | |
579 const std::string& cert = (*it)->identity.certificate; | |
580 add_stmt.BindBlob(3, cert.data(), cert.size()); | |
581 const std::string& private_key = (*it)->identity.private_key; | |
582 add_stmt.BindBlob(4, private_key.data(), private_key.size()); | |
583 add_stmt.BindInt64(5, (*it)->identity.creation_time); | |
584 if (!add_stmt.Run()) { | |
585 DVLOG(2) << "Failed to add the identity to DB."; | |
586 return; | |
587 } | |
588 break; | |
589 } | |
590 case DELETE_IDENTITY: | |
591 del_stmt.Reset(true); | |
592 del_stmt.BindString(0, (*it)->origin.spec()); | |
593 del_stmt.BindString(1, (*it)->identity_name); | |
594 if (!del_stmt.Run()) { | |
595 DVLOG(2) << "Failed to delete the identity from DB."; | |
596 return; | |
597 } | |
598 break; | |
599 | |
600 default: | |
601 NOTREACHED(); | |
602 break; | |
603 } | |
604 } | |
605 | |
606 if (!transaction.Commit()) | |
607 DVLOG(2) << "Failed to commit the transaction."; | |
608 } | |
609 | |
610 } // namespace content | |
OLD | NEW |