database_message_filter.cc revision a3f6a49ab37290eeeb8db0f41ec0f1cb74a68be7
1// Copyright (c) 2012 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/renderer_host/database_message_filter.h"
6
7#include <string>
8
9#include "base/bind.h"
10#include "base/platform_file.h"
11#include "base/strings/string_util.h"
12#include "base/strings/utf_string_conversions.h"
13#include "base/threading/thread.h"
14#include "content/common/database_messages.h"
15#include "content/public/browser/user_metrics.h"
16#include "content/public/common/result_codes.h"
17#include "third_party/sqlite/sqlite3.h"
18#include "webkit/browser/database/database_util.h"
19#include "webkit/browser/database/vfs_backend.h"
20#include "webkit/browser/quota/quota_manager.h"
21#include "webkit/common/database/database_identifier.h"
22
23#if defined(OS_POSIX)
24#include "base/file_descriptor_posix.h"
25#endif
26
27using quota::QuotaManager;
28using quota::QuotaManagerProxy;
29using quota::QuotaStatusCode;
30using webkit_database::DatabaseTracker;
31using webkit_database::DatabaseUtil;
32using webkit_database::VfsBackend;
33
34namespace content {
35namespace {
36
37const int kNumDeleteRetries = 2;
38const int kDelayDeleteRetryMs = 100;
39
40}  // namespace
41
42DatabaseMessageFilter::DatabaseMessageFilter(
43    webkit_database::DatabaseTracker* db_tracker)
44    : db_tracker_(db_tracker),
45      observer_added_(false) {
46  DCHECK(db_tracker_.get());
47}
48
49void DatabaseMessageFilter::OnChannelClosing() {
50  if (observer_added_) {
51    observer_added_ = false;
52    BrowserThread::PostTask(
53        BrowserThread::FILE, FROM_HERE,
54        base::Bind(&DatabaseMessageFilter::RemoveObserver, this));
55  }
56}
57
58void DatabaseMessageFilter::AddObserver() {
59  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
60  db_tracker_->AddObserver(this);
61}
62
63void DatabaseMessageFilter::RemoveObserver() {
64  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
65  db_tracker_->RemoveObserver(this);
66
67  // If the renderer process died without closing all databases,
68  // then we need to manually close those connections
69  db_tracker_->CloseDatabases(database_connections_);
70  database_connections_.RemoveAllConnections();
71}
72
73void DatabaseMessageFilter::OverrideThreadForMessage(
74    const IPC::Message& message,
75    BrowserThread::ID* thread) {
76  if (message.type() == DatabaseHostMsg_GetSpaceAvailable::ID)
77    *thread = BrowserThread::IO;
78  else if (IPC_MESSAGE_CLASS(message) == DatabaseMsgStart)
79    *thread = BrowserThread::FILE;
80
81  if (message.type() == DatabaseHostMsg_Opened::ID && !observer_added_) {
82    observer_added_ = true;
83    BrowserThread::PostTask(
84        BrowserThread::FILE, FROM_HERE,
85        base::Bind(&DatabaseMessageFilter::AddObserver, this));
86  }
87}
88
89bool DatabaseMessageFilter::OnMessageReceived(
90    const IPC::Message& message,
91    bool* message_was_ok) {
92  bool handled = true;
93  IPC_BEGIN_MESSAGE_MAP_EX(DatabaseMessageFilter, message, *message_was_ok)
94    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_OpenFile,
95                                    OnDatabaseOpenFile)
96    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_DeleteFile,
97                                    OnDatabaseDeleteFile)
98    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileAttributes,
99                                    OnDatabaseGetFileAttributes)
100    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetFileSize,
101                                    OnDatabaseGetFileSize)
102    IPC_MESSAGE_HANDLER_DELAY_REPLY(DatabaseHostMsg_GetSpaceAvailable,
103                                    OnDatabaseGetSpaceAvailable)
104    IPC_MESSAGE_HANDLER(DatabaseHostMsg_Opened, OnDatabaseOpened)
105    IPC_MESSAGE_HANDLER(DatabaseHostMsg_Modified, OnDatabaseModified)
106    IPC_MESSAGE_HANDLER(DatabaseHostMsg_Closed, OnDatabaseClosed)
107    IPC_MESSAGE_HANDLER(DatabaseHostMsg_HandleSqliteError, OnHandleSqliteError)
108    IPC_MESSAGE_UNHANDLED(handled = false)
109  IPC_END_MESSAGE_MAP_EX()
110  return handled;
111}
112
113DatabaseMessageFilter::~DatabaseMessageFilter() {
114}
115
116void DatabaseMessageFilter::OnDatabaseOpenFile(
117    const base::string16& vfs_file_name,
118    int desired_flags,
119    IPC::Message* reply_msg) {
120  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
121  base::PlatformFile file_handle = base::kInvalidPlatformFileValue;
122  std::string origin_identifier;
123  base::string16 database_name;
124
125  // When in incognito mode, we want to make sure that all DB files are
126  // removed when the incognito browser context goes away, so we add the
127  // SQLITE_OPEN_DELETEONCLOSE flag when opening all files, and keep
128  // open handles to them in the database tracker to make sure they're
129  // around for as long as needed.
130  if (vfs_file_name.empty()) {
131    VfsBackend::OpenTempFileInDirectory(db_tracker_->DatabaseDirectory(),
132                                        desired_flags, &file_handle);
133  } else if (DatabaseUtil::CrackVfsFileName(vfs_file_name, &origin_identifier,
134                                            &database_name, NULL) &&
135             !db_tracker_->IsDatabaseScheduledForDeletion(origin_identifier,
136                                                          database_name)) {
137    base::FilePath db_file = DatabaseUtil::GetFullFilePathForVfsFile(
138        db_tracker_.get(), vfs_file_name);
139    if (!db_file.empty()) {
140        if (db_tracker_->IsIncognitoProfile()) {
141          db_tracker_->GetIncognitoFileHandle(vfs_file_name, &file_handle);
142          if (file_handle == base::kInvalidPlatformFileValue) {
143            VfsBackend::OpenFile(db_file,
144                                 desired_flags | SQLITE_OPEN_DELETEONCLOSE,
145                                 &file_handle);
146            if (!(desired_flags & SQLITE_OPEN_DELETEONCLOSE))
147              db_tracker_->SaveIncognitoFileHandle(vfs_file_name, file_handle);
148          }
149        } else {
150          VfsBackend::OpenFile(db_file, desired_flags, &file_handle);
151        }
152      }
153  }
154
155  // Then we duplicate the file handle to make it useable in the renderer
156  // process. The original handle is closed, unless we saved it in the
157  // database tracker.
158  bool auto_close = !db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name);
159  IPC::PlatformFileForTransit target_handle =
160      IPC::GetFileHandleForProcess(file_handle, PeerHandle(), auto_close);
161
162  DatabaseHostMsg_OpenFile::WriteReplyParams(reply_msg, target_handle);
163  Send(reply_msg);
164}
165
166void DatabaseMessageFilter::OnDatabaseDeleteFile(
167    const base::string16& vfs_file_name,
168    const bool& sync_dir,
169    IPC::Message* reply_msg) {
170  DatabaseDeleteFile(vfs_file_name, sync_dir, reply_msg, kNumDeleteRetries);
171}
172
173void DatabaseMessageFilter::DatabaseDeleteFile(
174    const base::string16& vfs_file_name,
175    bool sync_dir,
176    IPC::Message* reply_msg,
177    int reschedule_count) {
178  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
179
180  // Return an error if the file name is invalid or if the file could not
181  // be deleted after kNumDeleteRetries attempts.
182  int error_code = SQLITE_IOERR_DELETE;
183  base::FilePath db_file =
184      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
185  if (!db_file.empty()) {
186    // In order to delete a journal file in incognito mode, we only need to
187    // close the open handle to it that's stored in the database tracker.
188    if (db_tracker_->IsIncognitoProfile()) {
189      const base::string16 wal_suffix(ASCIIToUTF16("-wal"));
190      base::string16 sqlite_suffix;
191
192      // WAL files can be deleted without having previously been opened.
193      if (!db_tracker_->HasSavedIncognitoFileHandle(vfs_file_name) &&
194          DatabaseUtil::CrackVfsFileName(vfs_file_name,
195                                         NULL, NULL, &sqlite_suffix) &&
196          sqlite_suffix == wal_suffix) {
197        error_code = SQLITE_OK;
198      } else if (db_tracker_->CloseIncognitoFileHandle(vfs_file_name)) {
199        error_code = SQLITE_OK;
200      }
201    } else {
202      error_code = VfsBackend::DeleteFile(db_file, sync_dir);
203    }
204
205    if ((error_code == SQLITE_IOERR_DELETE) && reschedule_count) {
206      // If the file could not be deleted, try again.
207      BrowserThread::PostDelayedTask(
208          BrowserThread::FILE, FROM_HERE,
209          base::Bind(&DatabaseMessageFilter::DatabaseDeleteFile, this,
210                     vfs_file_name, sync_dir, reply_msg, reschedule_count - 1),
211          base::TimeDelta::FromMilliseconds(kDelayDeleteRetryMs));
212      return;
213    }
214  }
215
216  DatabaseHostMsg_DeleteFile::WriteReplyParams(reply_msg, error_code);
217  Send(reply_msg);
218}
219
220void DatabaseMessageFilter::OnDatabaseGetFileAttributes(
221    const base::string16& vfs_file_name,
222    IPC::Message* reply_msg) {
223  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
224  int32 attributes = -1;
225  base::FilePath db_file =
226      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
227  if (!db_file.empty())
228    attributes = VfsBackend::GetFileAttributes(db_file);
229
230  DatabaseHostMsg_GetFileAttributes::WriteReplyParams(
231      reply_msg, attributes);
232  Send(reply_msg);
233}
234
235void DatabaseMessageFilter::OnDatabaseGetFileSize(
236    const base::string16& vfs_file_name, IPC::Message* reply_msg) {
237  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
238  int64 size = 0;
239  base::FilePath db_file =
240      DatabaseUtil::GetFullFilePathForVfsFile(db_tracker_.get(), vfs_file_name);
241  if (!db_file.empty())
242    size = VfsBackend::GetFileSize(db_file);
243
244  DatabaseHostMsg_GetFileSize::WriteReplyParams(reply_msg, size);
245  Send(reply_msg);
246}
247
248void DatabaseMessageFilter::OnDatabaseGetSpaceAvailable(
249    const std::string& origin_identifier, IPC::Message* reply_msg) {
250  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
251  DCHECK(db_tracker_->quota_manager_proxy());
252
253  QuotaManager* quota_manager =
254      db_tracker_->quota_manager_proxy()->quota_manager();
255  if (!quota_manager) {
256    NOTREACHED();  // The system is shutting down, messages are unexpected.
257    DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(
258        reply_msg, static_cast<int64>(0));
259    Send(reply_msg);
260    return;
261  }
262
263  quota_manager->GetUsageAndQuota(
264      webkit_database::GetOriginFromIdentifier(origin_identifier),
265      quota::kStorageTypeTemporary,
266      base::Bind(&DatabaseMessageFilter::OnDatabaseGetUsageAndQuota,
267                 this, reply_msg));
268}
269
270void DatabaseMessageFilter::OnDatabaseGetUsageAndQuota(
271    IPC::Message* reply_msg,
272    quota::QuotaStatusCode status,
273    int64 usage,
274    int64 quota) {
275  int64 available = 0;
276  if ((status == quota::kQuotaStatusOk) && (usage < quota))
277    available = quota - usage;
278  DatabaseHostMsg_GetSpaceAvailable::WriteReplyParams(reply_msg, available);
279  Send(reply_msg);
280}
281
282void DatabaseMessageFilter::OnDatabaseOpened(
283    const std::string& origin_identifier,
284    const base::string16& database_name,
285    const base::string16& description,
286    int64 estimated_size) {
287  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
288
289  if (!DatabaseUtil::IsValidOriginIdentifier(origin_identifier)) {
290    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
291    BadMessageReceived();
292    return;
293  }
294
295  int64 database_size = 0;
296  db_tracker_->DatabaseOpened(origin_identifier, database_name, description,
297                              estimated_size, &database_size);
298  database_connections_.AddConnection(origin_identifier, database_name);
299  Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name,
300                                  database_size));
301}
302
303void DatabaseMessageFilter::OnDatabaseModified(
304    const std::string& origin_identifier,
305    const base::string16& database_name) {
306  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
307  if (!database_connections_.IsDatabaseOpened(
308          origin_identifier, database_name)) {
309    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
310    BadMessageReceived();
311    return;
312  }
313
314  db_tracker_->DatabaseModified(origin_identifier, database_name);
315}
316
317void DatabaseMessageFilter::OnDatabaseClosed(
318    const std::string& origin_identifier,
319    const base::string16& database_name) {
320  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
321  if (!database_connections_.IsDatabaseOpened(
322          origin_identifier, database_name)) {
323    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
324    BadMessageReceived();
325    return;
326  }
327
328  database_connections_.RemoveConnection(origin_identifier, database_name);
329  db_tracker_->DatabaseClosed(origin_identifier, database_name);
330}
331
332void DatabaseMessageFilter::OnHandleSqliteError(
333    const std::string& origin_identifier,
334    const base::string16& database_name,
335    int error) {
336  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
337  if (!DatabaseUtil::IsValidOriginIdentifier(origin_identifier)) {
338    RecordAction(UserMetricsAction("BadMessageTerminate_DBMF"));
339    BadMessageReceived();
340    return;
341  }
342
343  db_tracker_->HandleSqliteError(origin_identifier, database_name, error);
344}
345
346void DatabaseMessageFilter::OnDatabaseSizeChanged(
347    const std::string& origin_identifier,
348    const base::string16& database_name,
349    int64 database_size) {
350  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
351  if (database_connections_.IsOriginUsed(origin_identifier)) {
352    Send(new DatabaseMsg_UpdateSize(origin_identifier, database_name,
353                                    database_size));
354  }
355}
356
357void DatabaseMessageFilter::OnDatabaseScheduledForDeletion(
358    const std::string& origin_identifier,
359    const base::string16& database_name) {
360  DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
361  Send(new DatabaseMsg_CloseImmediately(origin_identifier, database_name));
362}
363
364}  // namespace content
365