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