Respond with QuotaExceededError when IndexedDB has no disk space on open.
[chromium-blink-merge.git] / content / browser / download / base_file.cc
blob90ab485c3615168e91c4d01e5ce42cf6ac40dd18
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.
5 #include "content/browser/download/base_file.h"
7 #include "base/bind.h"
8 #include "base/file_util.h"
9 #include "base/format_macros.h"
10 #include "base/logging.h"
11 #include "base/pickle.h"
12 #include "base/strings/stringprintf.h"
13 #include "base/threading/thread_restrictions.h"
14 #include "content/browser/download/download_interrupt_reasons_impl.h"
15 #include "content/browser/download/download_net_log_parameters.h"
16 #include "content/browser/download/download_stats.h"
17 #include "content/public/browser/browser_thread.h"
18 #include "content/public/browser/content_browser_client.h"
19 #include "crypto/secure_hash.h"
20 #include "net/base/file_stream.h"
21 #include "net/base/net_errors.h"
23 namespace content {
25 // This will initialize the entire array to zero.
26 const unsigned char BaseFile::kEmptySha256Hash[] = { 0 };
28 BaseFile::BaseFile(const base::FilePath& full_path,
29 const GURL& source_url,
30 const GURL& referrer_url,
31 int64 received_bytes,
32 bool calculate_hash,
33 const std::string& hash_state_bytes,
34 scoped_ptr<net::FileStream> file_stream,
35 const net::BoundNetLog& bound_net_log)
36 : full_path_(full_path),
37 source_url_(source_url),
38 referrer_url_(referrer_url),
39 file_stream_(file_stream.Pass()),
40 bytes_so_far_(received_bytes),
41 start_tick_(base::TimeTicks::Now()),
42 calculate_hash_(calculate_hash),
43 detached_(false),
44 bound_net_log_(bound_net_log) {
45 memcpy(sha256_hash_, kEmptySha256Hash, kSha256HashLen);
46 if (calculate_hash_) {
47 secure_hash_.reset(crypto::SecureHash::Create(crypto::SecureHash::SHA256));
48 if ((bytes_so_far_ > 0) && // Not starting at the beginning.
49 (!IsEmptyHash(hash_state_bytes))) {
50 Pickle hash_state(hash_state_bytes.c_str(), hash_state_bytes.size());
51 PickleIterator data_iterator(hash_state);
52 secure_hash_->Deserialize(&data_iterator);
57 BaseFile::~BaseFile() {
58 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
59 if (detached_)
60 Close();
61 else
62 Cancel(); // Will delete the file.
65 DownloadInterruptReason BaseFile::Initialize(
66 const base::FilePath& default_directory) {
67 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
68 DCHECK(!detached_);
70 if (file_stream_) {
71 file_stream_->SetBoundNetLogSource(bound_net_log_);
72 file_stream_->EnableErrorStatistics();
75 if (full_path_.empty()) {
76 base::FilePath initial_directory(default_directory);
77 base::FilePath temp_file;
78 if (initial_directory.empty()) {
79 initial_directory =
80 GetContentClient()->browser()->GetDefaultDownloadDirectory();
82 // |initial_directory| can still be empty if ContentBrowserClient returned
83 // an empty path for the downloads directory.
84 if ((initial_directory.empty() ||
85 !file_util::CreateTemporaryFileInDir(initial_directory, &temp_file)) &&
86 !file_util::CreateTemporaryFile(&temp_file)) {
87 return LogInterruptReason("Unable to create", 0,
88 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
90 full_path_ = temp_file;
93 return Open();
96 DownloadInterruptReason BaseFile::AppendDataToFile(const char* data,
97 size_t data_len) {
98 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
99 DCHECK(!detached_);
101 // NOTE(benwells): The above DCHECK won't be present in release builds,
102 // so we log any occurences to see how common this error is in the wild.
103 if (detached_)
104 RecordDownloadCount(APPEND_TO_DETACHED_FILE_COUNT);
106 if (!file_stream_)
107 return LogInterruptReason("No file stream on append", 0,
108 DOWNLOAD_INTERRUPT_REASON_FILE_FAILED);
110 // TODO(phajdan.jr): get rid of this check.
111 if (data_len == 0)
112 return DOWNLOAD_INTERRUPT_REASON_NONE;
114 // The Write call below is not guaranteed to write all the data.
115 size_t write_count = 0;
116 size_t len = data_len;
117 const char* current_data = data;
118 while (len > 0) {
119 write_count++;
120 int write_result =
121 file_stream_->WriteSync(current_data, len);
122 DCHECK_NE(0, write_result);
124 // Check for errors.
125 if (static_cast<size_t>(write_result) != data_len) {
126 // We should never get ERR_IO_PENDING, as the Write above is synchronous.
127 DCHECK_NE(net::ERR_IO_PENDING, write_result);
129 // Report errors on file writes.
130 if (write_result < 0)
131 return LogNetError("Write", static_cast<net::Error>(write_result));
134 // Update status.
135 size_t write_size = static_cast<size_t>(write_result);
136 DCHECK_LE(write_size, len);
137 len -= write_size;
138 current_data += write_size;
139 bytes_so_far_ += write_size;
142 RecordDownloadWriteSize(data_len);
143 RecordDownloadWriteLoopCount(write_count);
145 if (calculate_hash_)
146 secure_hash_->Update(data, data_len);
148 return DOWNLOAD_INTERRUPT_REASON_NONE;
151 DownloadInterruptReason BaseFile::Rename(const base::FilePath& new_path) {
152 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
153 DownloadInterruptReason rename_result = DOWNLOAD_INTERRUPT_REASON_NONE;
155 // If the new path is same as the old one, there is no need to perform the
156 // following renaming logic.
157 if (new_path == full_path_)
158 return DOWNLOAD_INTERRUPT_REASON_NONE;
160 // Save the information whether the download is in progress because
161 // it will be overwritten by closing the file.
162 bool was_in_progress = in_progress();
164 bound_net_log_.BeginEvent(
165 net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED,
166 base::Bind(&FileRenamedNetLogCallback, &full_path_, &new_path));
167 Close();
168 file_util::CreateDirectory(new_path.DirName());
170 // A simple rename wouldn't work here since we want the file to have
171 // permissions / security descriptors that makes sense in the new directory.
172 rename_result = MoveFileAndAdjustPermissions(new_path);
174 if (rename_result == DOWNLOAD_INTERRUPT_REASON_NONE) {
175 full_path_ = new_path;
176 // Re-open the file if we were still using it.
177 if (was_in_progress)
178 rename_result = Open();
181 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_RENAMED);
182 return rename_result;
185 void BaseFile::Detach() {
186 detached_ = true;
187 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DETACHED);
190 void BaseFile::Cancel() {
191 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
192 DCHECK(!detached_);
194 bound_net_log_.AddEvent(net::NetLog::TYPE_CANCELLED);
196 Close();
198 if (!full_path_.empty()) {
199 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_DELETED);
201 base::DeleteFile(full_path_, false);
205 void BaseFile::Finish() {
206 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
208 if (calculate_hash_)
209 secure_hash_->Finish(sha256_hash_, kSha256HashLen);
211 Close();
214 void BaseFile::SetClientGuid(const std::string& guid) {
215 client_guid_ = guid;
218 // OS_WIN, OS_MACOSX and OS_LINUX have specialized implementations.
219 #if !defined(OS_WIN) && !defined(OS_MACOSX) && !defined(OS_LINUX)
220 DownloadInterruptReason BaseFile::AnnotateWithSourceInformation() {
221 return DOWNLOAD_INTERRUPT_REASON_NONE;
223 #endif
225 bool BaseFile::GetHash(std::string* hash) {
226 DCHECK(!detached_);
227 hash->assign(reinterpret_cast<const char*>(sha256_hash_),
228 sizeof(sha256_hash_));
229 return (calculate_hash_ && !in_progress());
232 std::string BaseFile::GetHashState() {
233 if (!calculate_hash_)
234 return std::string();
236 Pickle hash_state;
237 if (!secure_hash_->Serialize(&hash_state))
238 return std::string();
240 return std::string(reinterpret_cast<const char*>(hash_state.data()),
241 hash_state.size());
244 // static
245 bool BaseFile::IsEmptyHash(const std::string& hash) {
246 return (hash.size() == kSha256HashLen &&
247 0 == memcmp(hash.data(), kEmptySha256Hash, sizeof(kSha256HashLen)));
250 std::string BaseFile::DebugString() const {
251 return base::StringPrintf("{ source_url_ = \"%s\""
252 " full_path_ = \"%" PRFilePath "\""
253 " bytes_so_far_ = %" PRId64
254 " detached_ = %c }",
255 source_url_.spec().c_str(),
256 full_path_.value().c_str(),
257 bytes_so_far_,
258 detached_ ? 'T' : 'F');
261 void BaseFile::CreateFileStream() {
262 file_stream_.reset(new net::FileStream(bound_net_log_.net_log()));
263 file_stream_->SetBoundNetLogSource(bound_net_log_);
266 DownloadInterruptReason BaseFile::Open() {
267 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
268 DCHECK(!detached_);
269 DCHECK(!full_path_.empty());
271 bound_net_log_.BeginEvent(
272 net::NetLog::TYPE_DOWNLOAD_FILE_OPENED,
273 base::Bind(&FileOpenedNetLogCallback, &full_path_, bytes_so_far_));
275 // Create a new file stream if it is not provided.
276 if (!file_stream_) {
277 CreateFileStream();
278 file_stream_->EnableErrorStatistics();
279 int open_result = file_stream_->OpenSync(
280 full_path_,
281 base::PLATFORM_FILE_OPEN_ALWAYS | base::PLATFORM_FILE_WRITE);
282 if (open_result != net::OK) {
283 ClearStream();
284 return LogNetError("Open", static_cast<net::Error>(open_result));
287 // We may be re-opening the file after rename. Always make sure we're
288 // writing at the end of the file.
289 int64 seek_result = file_stream_->SeekSync(net::FROM_END, 0);
290 if (seek_result < 0) {
291 ClearStream();
292 return LogNetError("Seek", static_cast<net::Error>(seek_result));
294 } else {
295 file_stream_->SetBoundNetLogSource(bound_net_log_);
298 int64 file_size = file_stream_->SeekSync(net::FROM_END, 0);
299 if (file_size > bytes_so_far_) {
300 // The file is larger than we expected.
301 // This is OK, as long as we don't use the extra.
302 // Truncate the file.
303 int64 truncate_result = file_stream_->Truncate(bytes_so_far_);
304 if (truncate_result < 0)
305 return LogNetError("Truncate", static_cast<net::Error>(truncate_result));
307 // If if wasn't an error, it should have truncated to the size
308 // specified.
309 DCHECK_EQ(bytes_so_far_, truncate_result);
310 } else if (file_size < bytes_so_far_) {
311 // The file is shorter than we expected. Our hashes won't be valid.
312 return LogInterruptReason("Unable to seek to last written point", 0,
313 DOWNLOAD_INTERRUPT_REASON_FILE_TOO_SHORT);
316 return DOWNLOAD_INTERRUPT_REASON_NONE;
319 void BaseFile::Close() {
320 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::FILE));
322 bound_net_log_.AddEvent(net::NetLog::TYPE_DOWNLOAD_FILE_CLOSED);
324 if (file_stream_) {
325 #if defined(OS_CHROMEOS)
326 // Currently we don't really care about the return value, since if it fails
327 // theres not much we can do. But we might in the future.
328 file_stream_->FlushSync();
329 #endif
330 ClearStream();
334 void BaseFile::ClearStream() {
335 // This should only be called when we have a stream.
336 DCHECK(file_stream_.get() != NULL);
337 file_stream_.reset();
338 bound_net_log_.EndEvent(net::NetLog::TYPE_DOWNLOAD_FILE_OPENED);
341 DownloadInterruptReason BaseFile::LogNetError(
342 const char* operation,
343 net::Error error) {
344 bound_net_log_.AddEvent(
345 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
346 base::Bind(&FileErrorNetLogCallback, operation, error));
347 return ConvertNetErrorToInterruptReason(error, DOWNLOAD_INTERRUPT_FROM_DISK);
350 DownloadInterruptReason BaseFile::LogSystemError(
351 const char* operation,
352 int os_error) {
353 // There's no direct conversion from a system error to an interrupt reason.
354 net::Error net_error = net::MapSystemError(os_error);
355 return LogInterruptReason(
356 operation, os_error,
357 ConvertNetErrorToInterruptReason(
358 net_error, DOWNLOAD_INTERRUPT_FROM_DISK));
361 DownloadInterruptReason BaseFile::LogInterruptReason(
362 const char* operation,
363 int os_error,
364 DownloadInterruptReason reason) {
365 bound_net_log_.AddEvent(
366 net::NetLog::TYPE_DOWNLOAD_FILE_ERROR,
367 base::Bind(&FileInterruptedNetLogCallback, operation, os_error, reason));
368 return reason;
371 } // namespace content