blob: ab4869464e3c7fd3771c0ccbed63f25384b76acf [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
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