blob: 9ed0c64b90341eaeb17777e24bc84293da5e4b56 [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) {
rspangler@google.com49fdf182009-10-10 00:57:34 +000022 CHECK(!transfer_in_progress_);
23 url_ = url;
24 curl_multi_handle_ = curl_multi_init();
25 CHECK(curl_multi_handle_);
26
27 curl_handle_ = curl_easy_init();
28 CHECK(curl_handle_);
29
30 if (post_data_set_) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +000031 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POST, 1), CURLE_OK);
32 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDS,
33 &post_data_[0]),
34 CURLE_OK);
35 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_POSTFIELDSIZE,
36 post_data_.size()),
37 CURLE_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +000038 }
39
adlr@google.comc98a7ed2009-12-04 18:54:03 +000040 if (bytes_downloaded_ > 0) {
41 // Resume from where we left off
42 resume_offset_ = bytes_downloaded_;
43 CHECK_EQ(curl_easy_setopt(curl_handle_,
44 CURLOPT_RESUME_FROM_LARGE,
45 bytes_downloaded_), CURLE_OK);
46 }
47
48 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEDATA, this), CURLE_OK);
49 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_WRITEFUNCTION,
50 StaticLibcurlWrite), CURLE_OK);
51 CHECK_EQ(curl_easy_setopt(curl_handle_, CURLOPT_URL, url_.c_str()), CURLE_OK);
52 CHECK_EQ(curl_multi_add_handle(curl_multi_handle_, curl_handle_), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +000053 transfer_in_progress_ = true;
54 CurlPerformOnce();
55}
56
adlr@google.comc98a7ed2009-12-04 18:54:03 +000057// Begins the transfer, which must not have already been started.
58void LibcurlHttpFetcher::BeginTransfer(const std::string& url) {
59 transfer_size_ = -1;
60 bytes_downloaded_ = 0;
61 resume_offset_ = 0;
62 ResumeTransfer(url);
63}
64
rspangler@google.com49fdf182009-10-10 00:57:34 +000065void LibcurlHttpFetcher::TerminateTransfer() {
66 CleanUp();
67}
68
69// TODO(adlr): detect network failures
70void LibcurlHttpFetcher::CurlPerformOnce() {
71 CHECK(transfer_in_progress_);
72 int running_handles = 0;
73 CURLMcode retcode = CURLM_CALL_MULTI_PERFORM;
74
75 // 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.
77 while (CURLM_CALL_MULTI_PERFORM == retcode) {
78 retcode = curl_multi_perform(curl_multi_handle_, &running_handles);
79 }
80 if (0 == running_handles) {
81 // we're done!
82 CleanUp();
adlr@google.comc98a7ed2009-12-04 18:54:03 +000083
84 if ((transfer_size_ >= 0) && (bytes_downloaded_ < transfer_size_)) {
85 ResumeTransfer(url_);
86 } else {
87 if (delegate_) {
88 delegate_->TransferComplete(this, true); // success
89 }
90 }
rspangler@google.com49fdf182009-10-10 00:57:34 +000091 } else {
92 // set up callback
93 SetupMainloopSources();
94 }
95}
96
97size_t LibcurlHttpFetcher::LibcurlWrite(void *ptr, size_t size, size_t nmemb) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +000098 {
99 double transfer_size_double;
100 CHECK_EQ(curl_easy_getinfo(curl_handle_,
101 CURLINFO_CONTENT_LENGTH_DOWNLOAD,
102 &transfer_size_double), CURLE_OK);
103 off_t new_transfer_size = static_cast<off_t>(transfer_size_double);
104 if (new_transfer_size > 0) {
105 transfer_size_ = resume_offset_ + new_transfer_size;
106 }
107 }
108 bytes_downloaded_ += size * nmemb;
rspangler@google.com49fdf182009-10-10 00:57:34 +0000109 if (delegate_)
110 delegate_->ReceivedBytes(this, reinterpret_cast<char*>(ptr), size * nmemb);
111 return size * nmemb;
112}
113
114void LibcurlHttpFetcher::Pause() {
115 CHECK(curl_handle_);
116 CHECK(transfer_in_progress_);
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000117 CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_ALL), CURLE_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000118}
119
120void LibcurlHttpFetcher::Unpause() {
121 CHECK(curl_handle_);
122 CHECK(transfer_in_progress_);
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000123 CHECK_EQ(curl_easy_pause(curl_handle_, CURLPAUSE_CONT), CURLE_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000124}
125
126// This method sets up callbacks with the glib main loop.
127void LibcurlHttpFetcher::SetupMainloopSources() {
128 fd_set fd_read;
129 fd_set fd_write;
130 fd_set fd_exec;
131
132 FD_ZERO(&fd_read);
133 FD_ZERO(&fd_write);
134 FD_ZERO(&fd_exec);
135
136 int fd_max = 0;
137
138 // Ask libcurl for the set of file descriptors we should track on its
139 // behalf.
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000140 CHECK_EQ(curl_multi_fdset(curl_multi_handle_, &fd_read, &fd_write,
141 &fd_exec, &fd_max), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000142
143 // We should iterate through all file descriptors up to libcurl's fd_max or
144 // the highest one we're tracking, whichever is larger
145 if (!io_channels_.empty())
146 fd_max = max(fd_max, io_channels_.rbegin()->first);
147
148 // For each fd, if we're not tracking it, track it. If we are tracking it,
149 // but libcurl doesn't care about it anymore, stop tracking it.
150 // After this loop, there should be exactly as many GIOChannel objects
151 // in io_channels_ as there are fds that we're tracking.
152 for (int i = 0; i <= fd_max; i++) {
153 if (!(FD_ISSET(i, &fd_read) || FD_ISSET(i, &fd_write) ||
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000154 FD_ISSET(i, &fd_exec))) {
rspangler@google.com49fdf182009-10-10 00:57:34 +0000155 // if we have an outstanding io_channel, remove it
156 if (io_channels_.find(i) != io_channels_.end()) {
157 g_source_remove(io_channels_[i].second);
158 g_io_channel_unref(io_channels_[i].first);
159 io_channels_.erase(io_channels_.find(i));
160 }
161 continue;
162 }
163 // If we are already tracking this fd, continue.
164 if (io_channels_.find(i) != io_channels_.end())
165 continue;
166
167 // We must track a new fd
168 GIOChannel *io_channel = g_io_channel_unix_new(i);
169 guint tag = g_io_add_watch(
170 io_channel,
171 static_cast<GIOCondition>(G_IO_IN | G_IO_OUT | G_IO_PRI |
172 G_IO_ERR | G_IO_HUP),
173 &StaticFDCallback,
174 this);
175 io_channels_[i] = make_pair(io_channel, tag);
176 }
177
178 // Wet up a timeout callback for libcurl
179 long ms = 0;
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000180 CHECK_EQ(curl_multi_timeout(curl_multi_handle_, &ms), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000181 if (ms < 0) {
182 // From http://curl.haxx.se/libcurl/c/curl_multi_timeout.html:
183 // 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
185 // (more than a few seconds perhaps) before you call
186 // curl_multi_perform() again.
187 ms = idle_ms_;
188 }
189 if (timeout_source_) {
190 g_source_destroy(timeout_source_);
191 timeout_source_ = NULL;
192 }
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}
199
200bool LibcurlHttpFetcher::FDCallback(GIOChannel *source,
201 GIOCondition condition) {
202 // Figure out which source it was; hopefully there aren't too many b/c
203 // this is a linear scan of our channels
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 }
220 CHECK(found_in_set);
221 CurlPerformOnce();
222 return false;
223}
224
225bool LibcurlHttpFetcher::TimeoutCallback() {
226 // 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
228 // setting up callback sources again, we won't try to delete this (doomed)
229 // timeout callback then.
230 // TODO(adlr): optimize by checking if we can keep this timeout callback.
231 timeout_source_ = NULL;
232 CurlPerformOnce();
233 return false;
234}
235
236void LibcurlHttpFetcher::CleanUp() {
237 if (timeout_source_) {
238 g_source_destroy(timeout_source_);
239 timeout_source_ = NULL;
240 }
241
242 for (IOChannels::iterator it = io_channels_.begin();
243 it != io_channels_.end(); ++it) {
244 g_source_remove(it->second.second);
245 g_io_channel_unref(it->second.first);
246 }
247 io_channels_.clear();
248
249 if (curl_handle_) {
250 if (curl_multi_handle_) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000251 CHECK_EQ(curl_multi_remove_handle(curl_multi_handle_, curl_handle_),
252 CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000253 }
254 curl_easy_cleanup(curl_handle_);
255 curl_handle_ = NULL;
256 }
257 if (curl_multi_handle_) {
adlr@google.comc98a7ed2009-12-04 18:54:03 +0000258 CHECK_EQ(curl_multi_cleanup(curl_multi_handle_), CURLM_OK);
rspangler@google.com49fdf182009-10-10 00:57:34 +0000259 curl_multi_handle_ = NULL;
260 }
261 transfer_in_progress_ = false;
262}
263
264} // namespace chromeos_update_engine