blob: 7a032fe460fb18cd54f49be99c71c1a65917c6ae [file] [log] [blame]
rspangler@google.com49fdf182009-10-10 00:57:34 +00001// 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
rspangler@google.com49fdf182009-10-10 00:57:34 +00005#include "update_engine/libcurl_http_fetcher.h"
adlr@google.comc98a7ed2009-12-04 18:54:03 +00006#include <algorithm>
7#include "chromeos/obsolete_logging.h"
8
9using std::max;
10using std::make_pair;
rspangler@google.com49fdf182009-10-10 00:57:34 +000011
12// This is a concrete implementation of HttpFetcher that uses libcurl to do the
13// http work.
14
15namespace chromeos_update_engine {
16
17LibcurlHttpFetcher::~LibcurlHttpFetcher() {
18 CleanUp();
19}
20
adlr@google.comc98a7ed2009-12-04 18:54:03 +000021void LibcurlHttpFetcher::ResumeTransfer(const std::string& url) {
Andrew de los Reyes3270f742010-07-15 22:28:14 -070022 LOG(INFO) << "Starting/Resuming transfer";
rspangler@google.com49fdf182009-10-10 00:57:34 +000023 CHECK(!transfer_in_progress_);
24 url_ = url;
25 curl_multi_handle_ = curl_multi_init();
26 CHECK(curl_multi_handle_);
27
28 curl_handle_ = curl_easy_init();
29 CHECK(curl_handle_);
30
31 if (post_data_set_) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +000032 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
33 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
34 &post_data_[0]),
35 CURLE_OK);
36 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
37 post_data_.size()),
38 CURLE_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +000039 }
40
adlr@google.comc98a7ed2009-12-04 18:54:03 +000041 if (bytes_downloaded_ > 0) {
42 // Resume from where we left off
43 resume_offset_ = bytes_downloaded_;
44 CHECK_EQ(curl_easy_setopt(curl_handle_,
45 CURLOPT_RESUME_FROM_LARGE,
46 bytes_downloaded_), CURLE_OK);
47 }
48
49 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
50 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
51 StaticLibcurlWrite), CURLE_OK);
52 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK);
Andrew de los Reyes3270f742010-07-15 22:28:14 -070053
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
adlr@google.comc98a7ed2009-12-04 18:54:03 +000060 CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +000061 transfer_in_progress_ = true;
rspangler@google.com49fdf182009-10-10 00:57:34 +000062}
63
adlr@google.comc98a7ed2009-12-04 18:54:03 +000064// Begins the transfer, which must not have already been started.
65void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
66 transfer_size_ = -1;
67 bytes_downloaded_ = 0;
68 resume_offset_ = 0;
Andrew de los Reyes3270f742010-07-15 22:28:14 -070069 do {
70 ResumeTransfer(url);
71 } while (CurlPerformOnce());
adlr@google.comc98a7ed2009-12-04 18:54:03 +000072}
73
rspangler@google.com49fdf182009-10-10 00:57:34 +000074void LibcurlHttpFetcher::TerminateTransfer() {
75 CleanUp();
76}
77
Andrew de los Reyes3270f742010-07-15 22:28:14 -070078bool LibcurlHttpFetcher::CurlPerformOnce() {
rspangler@google.com49fdf182009-10-10 00:57:34 +000079 CHECK(transfer_in_progress_);
80 int running_handles = 0;
81 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
82
83 // libcurl may request that we immediately call curl_multi_perform after it
84 // returns, so we do. libcurl promises that curl_multi_perform will not block.
85 while (CURLM_CALL_MULTI_PERFORM == retcode) {
86 retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
87 }
88 if (0 == running_handles) {
89 // we're done!
90 CleanUp();
adlr@google.comc98a7ed2009-12-04 18:54:03 +000091
92 if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
Andrew de los Reyes3270f742010-07-15 22:28:14 -070093 // Need to restart transfer
94 return true;
adlr@google.comc98a7ed2009-12-04 18:54:03 +000095 } else {
96 if (delegate_) {
97 delegate_->TransferComplete(this, true); // success
98 }
99 }
rspangler@google.com49fdf182009-10-10 00:57:34 +0000100 } else {
101 // set up callback
102 SetupMainloopSources();
103 }
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700104 return false;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000105}
106
107size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000108 {
109 double transfer_size_double;
110 CHECK_EQ(curl_easy_getinfo(curl_handle_,
111 CURLINFO_CONTENT_LENGTH_DOWNLOAD,
112 &transfer_size_double), CURLE_OK);
113 off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
114 if (new_transfer_size > 0) {
115 transfer_size_ = resume_offset_ + new_transfer_size;
116 }
117 }
118 bytes_downloaded_ += size * nmemb;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000119 if (delegate_)
120 delegate_->ReceivedBytes(this, reinterpret_cast<char*>(ptr), size * nmemb);
121 return size * nmemb;
122}
123
124void LibcurlHttpFetcher::Pause() {
125 CHECK(curl_handle_);
126 CHECK(transfer_in_progress_);
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000127 CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000128}
129
130void LibcurlHttpFetcher::Unpause() {
131 CHECK(curl_handle_);
132 CHECK(transfer_in_progress_);
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000133 CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000134}
135
136// This method sets up callbacks with the glib main loop.
137void LibcurlHttpFetcher::SetupMainloopSources() {
138 fd_set fd_read;
139 fd_set fd_write;
140 fd_set fd_exec;
141
142 FD_ZERO(&fd_read);
143 FD_ZERO(&fd_write);
144 FD_ZERO(&fd_exec);
145
146 int fd_max = 0;
147
148 // Ask libcurl for the set of file descriptors we should track on its
149 // behalf.
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000150 CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
151 &fd_exec, &fd_max), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000152
153 // We should iterate through all file descriptors up to libcurl's fd_max or
154 // the highest one we're tracking, whichever is larger
155 if (!io_channels_.empty())
156 fd_max = max(fd_max, io_channels_.rbegin()->first);
157
158 // For each fd, if we're not tracking it, track it. If we are tracking it,
159 // but libcurl doesn't care about it anymore, stop tracking it.
160 // After this loop, there should be exactly as many GIOChannel objects
161 // in io_channels_ as there are fds that we're tracking.
162 for (int i = 0; i <= fd_max; i++) {
163 if (!(FD_ISSET(i, &fd_read) || FD_ISSET(i, &fd_write) ||
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000164 FD_ISSET(i, &fd_exec))) {
rspangler@google.com49fdf182009-10-10 00:57:34 +0000165 // if we have an outstanding io_channel, remove it
166 if (io_channels_.find(i) != io_channels_.end()) {
167 g_source_remove(io_channels_[i].second);
168 g_io_channel_unref(io_channels_[i].first);
169 io_channels_.erase(io_channels_.find(i));
170 }
171 continue;
172 }
173 // If we are already tracking this fd, continue.
174 if (io_channels_.find(i) != io_channels_.end())
175 continue;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000176 // We must track a new fd
177 GIOChannel *io_channel = g_io_channel_unix_new(i);
178 guint tag = g_io_add_watch(
179 io_channel,
180 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI |
181 G_IO_ERR | G_IO_HUP),
182 &StaticFDCallback,
183 this);
184 io_channels_[i] = make_pair(io_channel, tag);
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700185 static int io_counter = 0;
186 io_counter++;
187 if (io_counter % 50 == 0) {
188 LOG(INFO) << "io_counter = " << io_counter;
189 }
rspangler@google.com49fdf182009-10-10 00:57:34 +0000190 }
191
192 // Wet up a timeout callback for libcurl
193 long ms = 0;
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000194 CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000195 if (ms < 0) {
196 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
197 // if libcurl returns a -1 timeout here, it just means that libcurl
198 // currently has no stored timeout value. You must not wait too long
199 // (more than a few seconds perhaps) before you call
200 // curl_multi_perform() again.
201 ms = idle_ms_;
202 }
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700203 if (!timeout_source_) {
204 LOG(INFO) << "setting up timeout source:" << ms;
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 }
rspangler@google.com49fdf182009-10-10 00:57:34 +0000215 }
rspangler@google.com49fdf182009-10-10 00:57:34 +0000216}
217
218bool LibcurlHttpFetcher::FDCallback(GIOChannel *source,
219 GIOCondition condition) {
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700220 while (CurlPerformOnce()) {
221 ResumeTransfer(url_);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000222 }
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700223 // We handle removing of this source elsewhere, so we always return true.
224 // The docs say, "the function should return FALSE if the event source
225 // should be removed."
226 // http://www.gtk.org/api/2.6/glib/glib-IO-Channels.html#GIOFunc
227 return true;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000228}
229
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700230gboolean LibcurlHttpFetcher::TimeoutCallback() {
231 if (!transfer_in_progress_)
232 return TRUE;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000233 // Since we will return false from this function, which tells glib to
234 // destroy the timeout callback, we must NULL it out here. This way, when
235 // setting up callback sources again, we won't try to delete this (doomed)
236 // timeout callback then.
237 // TODO(adlr): optimize by checking if we can keep this timeout callback.
Andrew de los Reyes3270f742010-07-15 22:28:14 -0700238 //timeout_source_ = NULL;
239 while (CurlPerformOnce()) {
240 ResumeTransfer(url_);
241 }
242 return TRUE;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000243}
244
245void LibcurlHttpFetcher::CleanUp() {
246 if (timeout_source_) {
247 g_source_destroy(timeout_source_);
248 timeout_source_ = NULL;
249 }
250
251 for (IOChannels::iterator it = io_channels_.begin();
252 it != io_channels_.end(); ++it) {
253 g_source_remove(it->second.second);
254 g_io_channel_unref(it->second.first);
255 }
256 io_channels_.clear();
257
258 if (curl_handle_) {
259 if (curl_multi_handle_) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000260 CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
261 CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000262 }
263 curl_easy_cleanup(curl_handle_);
264 curl_handle_ = NULL;
265 }
266 if (curl_multi_handle_) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000267 CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000268 curl_multi_handle_ = NULL;
269 }
270 transfer_in_progress_ = false;
271}
272
273} // namespace chromeos_update_engine