libcurl_http_fetcher.cc revision 49fdf1889b965be25f929eeebc5b60cd40b90435
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 3// found in the LICENSE file. 4 5#include "glog/logging.h" 6#include "update_engine/libcurl_http_fetcher.h" 7 8// This is a concrete implementation of HttpFetcher that uses libcurl to do the 9// http work. 10 11namespace chromeos_update_engine { 12 13LibcurlHttpFetcher::~LibcurlHttpFetcher() { 14 CleanUp(); 15} 16 17// Begins the transfer, which must not have already been started. 18void LibcurlHttpFetcher::BeginTransfer(const std::string& url) { 19 CHECK(!transfer_in_progress_); 20 url_ = url; 21 curl_multi_handle_ = curl_multi_init(); 22 CHECK(curl_multi_handle_); 23 24 curl_handle_ = curl_easy_init(); 25 CHECK(curl_handle_); 26 27 if (post_data_set_) { 28 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POST, 1)); 29 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS, 30 &post_data_[0])); 31 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE, 32 post_data_.size())); 33 } 34 35 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this)); 36 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION, 37 StaticLibcurlWrite)); 38 CHECK_EQ(CURLE_OK, curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str())); 39 CHECK_EQ(CURLM_OK, curl_multi_add_handle(curl_multi_handle_, curl_handle_)); 40 transfer_in_progress_ = true; 41 CurlPerformOnce(); 42} 43 44void LibcurlHttpFetcher::TerminateTransfer() { 45 CleanUp(); 46} 47 48// TODO(adlr): detect network failures 49void LibcurlHttpFetcher::CurlPerformOnce() { 50 CHECK(transfer_in_progress_); 51 int running_handles = 0; 52 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM; 53 54 // libcurl may request that we immediately call curl_multi_perform after it 55 // returns, so we do. libcurl promises that curl_multi_perform will not block. 56 while (CURLM_CALL_MULTI_PERFORM == retcode) { 57 retcode = curl_multi_perform(curl_multi_handle_, &running_handles); 58 } 59 if (0 == running_handles) { 60 // we're done! 61 CleanUp(); 62 if (delegate_) 63 delegate_->TransferComplete(this, true); // success 64 } else { 65 // set up callback 66 SetupMainloopSources(); 67 } 68} 69 70size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) { 71 if (delegate_) 72 delegate_->ReceivedBytes(this, reinterpret_cast<char*>(ptr), size * nmemb); 73 return size * nmemb; 74} 75 76void LibcurlHttpFetcher::Pause() { 77 CHECK(curl_handle_); 78 CHECK(transfer_in_progress_); 79 CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_ALL)); 80} 81 82void LibcurlHttpFetcher::Unpause() { 83 CHECK(curl_handle_); 84 CHECK(transfer_in_progress_); 85 CHECK_EQ(CURLE_OK, curl_easy_pause(curl_handle_, CURLPAUSE_CONT)); 86} 87 88// This method sets up callbacks with the glib main loop. 89void LibcurlHttpFetcher::SetupMainloopSources() { 90 fd_set fd_read; 91 fd_set fd_write; 92 fd_set fd_exec; 93 94 FD_ZERO(&fd_read); 95 FD_ZERO(&fd_write); 96 FD_ZERO(&fd_exec); 97 98 int fd_max = 0; 99 100 // Ask libcurl for the set of file descriptors we should track on its 101 // behalf. 102 CHECK_EQ(CURLM_OK, curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write, 103 &fd_exec, &fd_max)); 104 105 // We should iterate through all file descriptors up to libcurl's fd_max or 106 // the highest one we're tracking, whichever is larger 107 if (!io_channels_.empty()) 108 fd_max = max(fd_max, io_channels_.rbegin()->first); 109 110 // For each fd, if we're not tracking it, track it. If we are tracking it, 111 // but libcurl doesn't care about it anymore, stop tracking it. 112 // After this loop, there should be exactly as many GIOChannel objects 113 // in io_channels_ as there are fds that we're tracking. 114 for (int i = 0; i <= fd_max; i++) { 115 if (!(FD_ISSET(i, &fd_read) || FD_ISSET(i, &fd_write) || 116 FD_ISSET(i, &fd_exec))) { 117 // if we have an outstanding io_channel, remove it 118 if (io_channels_.find(i) != io_channels_.end()) { 119 g_source_remove(io_channels_[i].second); 120 g_io_channel_unref(io_channels_[i].first); 121 io_channels_.erase(io_channels_.find(i)); 122 } 123 continue; 124 } 125 // If we are already tracking this fd, continue. 126 if (io_channels_.find(i) != io_channels_.end()) 127 continue; 128 129 // We must track a new fd 130 GIOChannel *io_channel = g_io_channel_unix_new(i); 131 guint tag = g_io_add_watch( 132 io_channel, 133 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI | 134 G_IO_ERR | G_IO_HUP), 135 &StaticFDCallback, 136 this); 137 io_channels_[i] = make_pair(io_channel, tag); 138 } 139 140 // Wet up a timeout callback for libcurl 141 long ms = 0; 142 CHECK_EQ(CURLM_OK, curl_multi_timeout(curl_multi_handle_, &ms)); 143 if (ms < 0) { 144 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html: 145 // if libcurl returns a -1 timeout here, it just means that libcurl 146 // currently has no stored timeout value. You must not wait too long 147 // (more than a few seconds perhaps) before you call 148 // curl_multi_perform() again. 149 ms = idle_ms_; 150 } 151 if (timeout_source_) { 152 g_source_destroy(timeout_source_); 153 timeout_source_ = NULL; 154 } 155 timeout_source_ = g_timeout_source_new(ms); 156 CHECK(timeout_source_); 157 g_source_set_callback(timeout_source_, StaticTimeoutCallback, this, 158 NULL); 159 g_source_attach(timeout_source_, NULL); 160} 161 162bool LibcurlHttpFetcher::FDCallback(GIOChannel *source, 163 GIOCondition condition) { 164 // Figure out which source it was; hopefully there aren't too many b/c 165 // this is a linear scan of our channels 166 bool found_in_set = false; 167 for (IOChannels::iterator it = io_channels_.begin(); 168 it != io_channels_.end(); ++it) { 169 if (it->second.first == source) { 170 // We will return false from this method, meaning that we shouldn't keep 171 // this g_io_channel around. So we remove it now from our collection of 172 // g_io_channels so that the other code in this class doens't mess with 173 // this (doomed) GIOChannel. 174 // TODO(adlr): optimize by seeing if we should reuse this GIOChannel 175 g_source_remove(it->second.second); 176 g_io_channel_unref(it->second.first); 177 io_channels_.erase(it); 178 found_in_set = true; 179 break; 180 } 181 } 182 CHECK(found_in_set); 183 CurlPerformOnce(); 184 return false; 185} 186 187bool LibcurlHttpFetcher::TimeoutCallback() { 188 // Since we will return false from this function, which tells glib to 189 // destroy the timeout callback, we must NULL it out here. This way, when 190 // setting up callback sources again, we won't try to delete this (doomed) 191 // timeout callback then. 192 // TODO(adlr): optimize by checking if we can keep this timeout callback. 193 timeout_source_ = NULL; 194 CurlPerformOnce(); 195 return false; 196} 197 198void LibcurlHttpFetcher::CleanUp() { 199 if (timeout_source_) { 200 g_source_destroy(timeout_source_); 201 timeout_source_ = NULL; 202 } 203 204 for (IOChannels::iterator it = io_channels_.begin(); 205 it != io_channels_.end(); ++it) { 206 g_source_remove(it->second.second); 207 g_io_channel_unref(it->second.first); 208 } 209 io_channels_.clear(); 210 211 if (curl_handle_) { 212 if (curl_multi_handle_) { 213 CHECK_EQ(CURLM_OK, 214 curl_multi_remove_handle(curl_multi_handle_, curl_handle_)); 215 } 216 curl_easy_cleanup(curl_handle_); 217 curl_handle_ = NULL; 218 } 219 if (curl_multi_handle_) { 220 CHECK_EQ(CURLM_OK, curl_multi_cleanup(curl_multi_handle_)); 221 curl_multi_handle_ = NULL; 222 } 223 transfer_in_progress_ = false; 224} 225 226} // namespace chromeos_update_engine 227