| OLD | NEW |
| 1 // Copyright (c) 2009 The Chromium OS Authors. All rights reserved. | 1 // Copyright (c) 2009 The Chromium OS Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include "update_engine/libcurl_http_fetcher.h" | 5 #include "update_engine/libcurl_http_fetcher.h" |
| 6 #include <algorithm> | 6 #include <algorithm> |
| 7 #include "chromeos/obsolete_logging.h" | 7 #include "chromeos/obsolete_logging.h" |
| 8 | 8 |
| 9 using std::max; | 9 using std::max; |
| 10 using std::make_pair; | 10 using std::make_pair; |
| 11 | 11 |
| 12 // This is a concrete implementation of HttpFetcher that uses libcurl to do the | 12 // This is a concrete implementation of HttpFetcher that uses libcurl to do the |
| 13 // http work. | 13 // http work. |
| 14 | 14 |
| 15 namespace chromeos_update_engine { | 15 namespace chromeos_update_engine { |
| 16 | 16 |
| 17 LibcurlHttpFetcher::~LibcurlHttpFetcher() { | 17 LibcurlHttpFetcher::~LibcurlHttpFetcher() { |
| 18 CleanUp(); | 18 CleanUp(); |
| 19 } | 19 } |
| 20 | 20 |
| 21 void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) { | 21 void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) { |
| 22 LOG(INFO) << "Starting/Resuming transfer"; |
| 22 CHECK(!transfer_in_progress_); | 23 CHECK(!transfer_in_progress_); |
| 23 url_ = url; | 24 url_ = url; |
| 24 curl_multi_handle_ = curl_multi_init(); | 25 curl_multi_handle_ = curl_multi_init(); |
| 25 CHECK(curl_multi_handle_); | 26 CHECK(curl_multi_handle_); |
| 26 | 27 |
| 27 curl_handle_ = curl_easy_init(); | 28 curl_handle_ = curl_easy_init(); |
| 28 CHECK(curl_handle_); | 29 CHECK(curl_handle_); |
| 29 | 30 |
| 30 if (post_data_set_) { | 31 if (post_data_set_) { |
| 31 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK); | 32 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK); |
| (...skipping 10 matching lines...) Expand all Loading... |
| 42 resume_offset_ = bytes_downloaded_; | 43 resume_offset_ = bytes_downloaded_; |
| 43 CHECK_EQ(curl_easy_setopt(curl_handle_, | 44 CHECK_EQ(curl_easy_setopt(curl_handle_, |
| 44 CURLOPT_RESUME_FROM_LARGE, | 45 CURLOPT_RESUME_FROM_LARGE, |
| 45 bytes_downloaded_), CURLE_OK); | 46 bytes_downloaded_), CURLE_OK); |
| 46 } | 47 } |
| 47 | 48 |
| 48 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK); | 49 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK); |
| 49 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, | 50 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, |
| 50 StaticLibcurlWrite), CURLE_OK); | 51 StaticLibcurlWrite), CURLE_OK); |
| 51 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK); | 52 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK); |
| 53 |
| 54 // If the connection drops under 10 bytes/sec for 90 seconds, reconnect. |
| 55 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_LIMIT, 10), |
| 56 CURLE_OK); |
| 57 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_LOW_SPEED_TIME, 90), |
| 58 CURLE_OK); |
| 59 |
| 52 CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK); | 60 CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK); |
| 53 transfer_in_progress_ = true; | 61 transfer_in_progress_ = true; |
| 54 CurlPerformOnce(); | |
| 55 } | 62 } |
| 56 | 63 |
| 57 // Begins the transfer, which must not have already been started. | 64 // Begins the transfer, which must not have already been started. |
| 58 void LibcurlHttpFetcher::BeginTransfer(const std::string& url) { | 65 void LibcurlHttpFetcher::BeginTransfer(const std::string& url) { |
| 59 transfer_size_ = -1; | 66 transfer_size_ = -1; |
| 60 bytes_downloaded_ = 0; | 67 bytes_downloaded_ = 0; |
| 61 resume_offset_ = 0; | 68 resume_offset_ = 0; |
| 62 ResumeTransfer(url); | 69 do { |
| 70 ResumeTransfer(url); |
| 71 } while (CurlPerformOnce()); |
| 63 } | 72 } |
| 64 | 73 |
| 65 void LibcurlHttpFetcher::TerminateTransfer() { | 74 void LibcurlHttpFetcher::TerminateTransfer() { |
| 66 CleanUp(); | 75 CleanUp(); |
| 67 } | 76 } |
| 68 | 77 |
| 69 // TODO(adlr): detect network failures | 78 bool LibcurlHttpFetcher::CurlPerformOnce() { |
| 70 void LibcurlHttpFetcher::CurlPerformOnce() { | |
| 71 CHECK(transfer_in_progress_); | 79 CHECK(transfer_in_progress_); |
| 72 int running_handles = 0; | 80 int running_handles = 0; |
| 73 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM; | 81 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM; |
| 74 | 82 |
| 75 // libcurl may request that we immediately call curl_multi_perform after it | 83 // libcurl may request that we immediately call curl_multi_perform after it |
| 76 // returns, so we do. libcurl promises that curl_multi_perform will not block. | 84 // returns, so we do. libcurl promises that curl_multi_perform will not block. |
| 77 while (CURLM_CALL_MULTI_PERFORM == retcode) { | 85 while (CURLM_CALL_MULTI_PERFORM == retcode) { |
| 78 retcode = curl_multi_perform(curl_multi_handle_, &running_handles); | 86 retcode = curl_multi_perform(curl_multi_handle_, &running_handles); |
| 79 } | 87 } |
| 80 if (0 == running_handles) { | 88 if (0 == running_handles) { |
| 81 // we're done! | 89 // we're done! |
| 82 CleanUp(); | 90 CleanUp(); |
| 83 | 91 |
| 84 if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) { | 92 if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) { |
| 85 ResumeTransfer(url_); | 93 // Need to restart transfer |
| 94 return true; |
| 86 } else { | 95 } else { |
| 87 if (delegate_) { | 96 if (delegate_) { |
| 88 delegate_->TransferComplete(this, true); // success | 97 delegate_->TransferComplete(this, true); // success |
| 89 } | 98 } |
| 90 } | 99 } |
| 91 } else { | 100 } else { |
| 92 // set up callback | 101 // set up callback |
| 93 SetupMainloopSources(); | 102 SetupMainloopSources(); |
| 94 } | 103 } |
| 104 return false; |
| 95 } | 105 } |
| 96 | 106 |
| 97 size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) { | 107 size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) { |
| 98 { | 108 { |
| 99 double transfer_size_double; | 109 double transfer_size_double; |
| 100 CHECK_EQ(curl_easy_getinfo(curl_handle_, | 110 CHECK_EQ(curl_easy_getinfo(curl_handle_, |
| 101 CURLINFO_CONTENT_LENGTH_DOWNLOAD, | 111 CURLINFO_CONTENT_LENGTH_DOWNLOAD, |
| 102 &transfer_size_double), CURLE_OK); | 112 &transfer_size_double), CURLE_OK); |
| 103 off_t new_transfer_size = static_cast<off_t>(transfer_size_double); | 113 off_t new_transfer_size = static_cast<off_t>(transfer_size_double); |
| 104 if (new_transfer_size > 0) { | 114 if (new_transfer_size > 0) { |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 156 if (io_channels_.find(i) != io_channels_.end()) { | 166 if (io_channels_.find(i) != io_channels_.end()) { |
| 157 g_source_remove(io_channels_[i].second); | 167 g_source_remove(io_channels_[i].second); |
| 158 g_io_channel_unref(io_channels_[i].first); | 168 g_io_channel_unref(io_channels_[i].first); |
| 159 io_channels_.erase(io_channels_.find(i)); | 169 io_channels_.erase(io_channels_.find(i)); |
| 160 } | 170 } |
| 161 continue; | 171 continue; |
| 162 } | 172 } |
| 163 // If we are already tracking this fd, continue. | 173 // If we are already tracking this fd, continue. |
| 164 if (io_channels_.find(i) != io_channels_.end()) | 174 if (io_channels_.find(i) != io_channels_.end()) |
| 165 continue; | 175 continue; |
| 166 | |
| 167 // We must track a new fd | 176 // We must track a new fd |
| 168 GIOChannel *io_channel = g_io_channel_unix_new(i); | 177 GIOChannel *io_channel = g_io_channel_unix_new(i); |
| 169 guint tag = g_io_add_watch( | 178 guint tag = g_io_add_watch( |
| 170 io_channel, | 179 io_channel, |
| 171 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI | | 180 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI | |
| 172 G_IO_ERR | G_IO_HUP), | 181 G_IO_ERR | G_IO_HUP), |
| 173 &StaticFDCallback, | 182 &StaticFDCallback, |
| 174 this); | 183 this); |
| 175 io_channels_[i] = make_pair(io_channel, tag); | 184 io_channels_[i] = make_pair(io_channel, tag); |
| 185 static int io_counter = 0; |
| 186 io_counter++; |
| 187 if (io_counter % 50 == 0) { |
| 188 LOG(INFO) << "io_counter = " << io_counter; |
| 189 } |
| 176 } | 190 } |
| 177 | 191 |
| 178 // Wet up a timeout callback for libcurl | 192 // Wet up a timeout callback for libcurl |
| 179 long ms = 0; | 193 long ms = 0; |
| 180 CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK); | 194 CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK); |
| 181 if (ms < 0) { | 195 if (ms < 0) { |
| 182 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html: | 196 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html: |
| 183 // if libcurl returns a -1 timeout here, it just means that libcurl | 197 // if libcurl returns a -1 timeout here, it just means that libcurl |
| 184 // currently has no stored timeout value. You must not wait too long | 198 // currently has no stored timeout value. You must not wait too long |
| 185 // (more than a few seconds perhaps) before you call | 199 // (more than a few seconds perhaps) before you call |
| 186 // curl_multi_perform() again. | 200 // curl_multi_perform() again. |
| 187 ms = idle_ms_; | 201 ms = idle_ms_; |
| 188 } | 202 } |
| 189 if (timeout_source_) { | 203 if (!timeout_source_) { |
| 190 g_source_destroy(timeout_source_); | 204 LOG(INFO) << "setting up timeout source:" << ms; |
| 191 timeout_source_ = NULL; | 205 timeout_source_ = g_timeout_source_new(1000); |
| 206 CHECK(timeout_source_); |
| 207 g_source_set_callback(timeout_source_, StaticTimeoutCallback, this, |
| 208 NULL); |
| 209 g_source_attach(timeout_source_, NULL); |
| 210 static int counter = 0; |
| 211 counter++; |
| 212 if (counter % 50 == 0) { |
| 213 LOG(INFO) << "counter = " << counter; |
| 214 } |
| 192 } | 215 } |
| 193 timeout_source_ = g_timeout_source_new(ms); | |
| 194 CHECK(timeout_source_); | |
| 195 g_source_set_callback(timeout_source_, StaticTimeoutCallback, this, | |
| 196 NULL); | |
| 197 g_source_attach(timeout_source_, NULL); | |
| 198 } | 216 } |
| 199 | 217 |
| 200 bool LibcurlHttpFetcher::FDCallback(GIOChannel *source, | 218 bool LibcurlHttpFetcher::FDCallback(GIOChannel *source, |
| 201 GIOCondition condition) { | 219 GIOCondition condition) { |
| 202 // Figure out which source it was; hopefully there aren't too many b/c | 220 while (CurlPerformOnce()) { |
| 203 // this is a linear scan of our channels | 221 ResumeTransfer(url_); |
| 204 bool found_in_set = false; | |
| 205 for (IOChannels::iterator it = io_channels_.begin(); | |
| 206 it != io_channels_.end(); ++it) { | |
| 207 if (it->second.first == source) { | |
| 208 // We will return false from this method, meaning that we shouldn't keep | |
| 209 // this g_io_channel around. So we remove it now from our collection of | |
| 210 // g_io_channels so that the other code in this class doens't mess with | |
| 211 // this (doomed) GIOChannel. | |
| 212 // TODO(adlr): optimize by seeing if we should reuse this GIOChannel | |
| 213 g_source_remove(it->second.second); | |
| 214 g_io_channel_unref(it->second.first); | |
| 215 io_channels_.erase(it); | |
| 216 found_in_set = true; | |
| 217 break; | |
| 218 } | |
| 219 } | 222 } |
| 220 CHECK(found_in_set); | 223 // We handle removing of this source elsewhere, so we always return true. |
| 221 CurlPerformOnce(); | 224 // The docs say, "the function should return FALSE if the event source |
| 222 return false; | 225 // should be removed." |
| 226 // http://www.gtk.org/api/2.6/glib/glib-IO-Channels.html#GIOFunc |
| 227 return true; |
| 223 } | 228 } |
| 224 | 229 |
| 225 bool LibcurlHttpFetcher::TimeoutCallback() { | 230 gboolean LibcurlHttpFetcher::TimeoutCallback() { |
| 231 if (!transfer_in_progress_) |
| 232 return TRUE; |
| 226 // Since we will return false from this function, which tells glib to | 233 // Since we will return false from this function, which tells glib to |
| 227 // destroy the timeout callback, we must NULL it out here. This way, when | 234 // destroy the timeout callback, we must NULL it out here. This way, when |
| 228 // setting up callback sources again, we won't try to delete this (doomed) | 235 // setting up callback sources again, we won't try to delete this (doomed) |
| 229 // timeout callback then. | 236 // timeout callback then. |
| 230 // TODO(adlr): optimize by checking if we can keep this timeout callback. | 237 // TODO(adlr): optimize by checking if we can keep this timeout callback. |
| 231 timeout_source_ = NULL; | 238 //timeout_source_ = NULL; |
| 232 CurlPerformOnce(); | 239 while (CurlPerformOnce()) { |
| 233 return false; | 240 ResumeTransfer(url_); |
| 241 } |
| 242 return TRUE; |
| 234 } | 243 } |
| 235 | 244 |
| 236 void LibcurlHttpFetcher::CleanUp() { | 245 void LibcurlHttpFetcher::CleanUp() { |
| 237 if (timeout_source_) { | 246 if (timeout_source_) { |
| 238 g_source_destroy(timeout_source_); | 247 g_source_destroy(timeout_source_); |
| 239 timeout_source_ = NULL; | 248 timeout_source_ = NULL; |
| 240 } | 249 } |
| 241 | 250 |
| 242 for (IOChannels::iterator it = io_channels_.begin(); | 251 for (IOChannels::iterator it = io_channels_.begin(); |
| 243 it != io_channels_.end(); ++it) { | 252 it != io_channels_.end(); ++it) { |
| (...skipping 11 matching lines...) Expand all Loading... |
| 255 curl_handle_ = NULL; | 264 curl_handle_ = NULL; |
| 256 } | 265 } |
| 257 if (curl_multi_handle_) { | 266 if (curl_multi_handle_) { |
| 258 CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK); | 267 CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK); |
| 259 curl_multi_handle_ = NULL; | 268 curl_multi_handle_ = NULL; |
| 260 } | 269 } |
| 261 transfer_in_progress_ = false; | 270 transfer_in_progress_ = false; |
| 262 } | 271 } |
| 263 | 272 |
| 264 } // namespace chromeos_update_engine | 273 } // namespace chromeos_update_engine |
| OLD | NEW |