get_file.c revision 299742
1/* 2 * get_file.c : entry point for update RA functions for ra_serf 3 * 4 * ==================================================================== 5 * Licensed to the Apache Software Foundation (ASF) under one 6 * or more contributor license agreements. See the NOTICE file 7 * distributed with this work for additional information 8 * regarding copyright ownership. The ASF licenses this file 9 * to you under the Apache License, Version 2.0 (the 10 * "License"); you may not use this file except in compliance 11 * with the License. You may obtain a copy of the License at 12 * 13 * http://www.apache.org/licenses/LICENSE-2.0 14 * 15 * Unless required by applicable law or agreed to in writing, 16 * software distributed under the License is distributed on an 17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18 * KIND, either express or implied. See the License for the 19 * specific language governing permissions and limitations 20 * under the License. 21 * ==================================================================== 22 */ 23 24 25 26#define APR_WANT_STRFUNC 27#include <apr_version.h> 28#include <apr_want.h> 29 30#include <apr_uri.h> 31 32#include <serf.h> 33 34#include "svn_private_config.h" 35#include "svn_hash.h" 36#include "svn_pools.h" 37#include "svn_ra.h" 38#include "svn_delta.h" 39#include "svn_path.h" 40#include "svn_props.h" 41 42#include "private/svn_dep_compat.h" 43#include "private/svn_string_private.h" 44 45#include "ra_serf.h" 46#include "../libsvn_ra/ra_loader.h" 47 48 49 50 51/* 52 * This structure represents a single request to GET (fetch) a file with 53 * its associated Serf session/connection. 54 */ 55typedef struct stream_ctx_t { 56 57 /* The handler representing this particular fetch. */ 58 svn_ra_serf__handler_t *handler; 59 60 /* Have we read our response headers yet? */ 61 svn_boolean_t read_headers; 62 63 svn_boolean_t using_compression; 64 65 /* This flag is set when our response is aborted before we reach the 66 * end and we decide to requeue this request. 67 */ 68 svn_boolean_t aborted_read; 69 apr_off_t aborted_read_size; 70 71 /* This is the amount of data that we have read so far. */ 72 apr_off_t read_size; 73 74 /* If we're writing this file to a stream, this will be non-NULL. */ 75 svn_stream_t *result_stream; 76 77} stream_ctx_t; 78 79 80 81/** Routines called when we are fetching a file */ 82 83static svn_error_t * 84headers_fetch(serf_bucket_t *headers, 85 void *baton, 86 apr_pool_t *pool /* request pool */, 87 apr_pool_t *scratch_pool) 88{ 89 stream_ctx_t *fetch_ctx = baton; 90 91 if (fetch_ctx->using_compression) 92 { 93 serf_bucket_headers_setn(headers, "Accept-Encoding", "gzip"); 94 } 95 96 return SVN_NO_ERROR; 97} 98 99static svn_error_t * 100cancel_fetch(serf_request_t *request, 101 serf_bucket_t *response, 102 int status_code, 103 void *baton) 104{ 105 stream_ctx_t *fetch_ctx = baton; 106 107 /* Uh-oh. Our connection died on us. 108 * 109 * The core ra_serf layer will requeue our request - we just need to note 110 * that we got cut off in the middle of our song. 111 */ 112 if (!response) 113 { 114 /* If we already started the fetch and opened the file handle, we need 115 * to hold subsequent read() ops until we get back to where we were 116 * before the close and we can then resume the textdelta() calls. 117 */ 118 if (fetch_ctx->read_headers) 119 { 120 if (!fetch_ctx->aborted_read && fetch_ctx->read_size) 121 { 122 fetch_ctx->aborted_read = TRUE; 123 fetch_ctx->aborted_read_size = fetch_ctx->read_size; 124 } 125 fetch_ctx->read_size = 0; 126 } 127 128 return SVN_NO_ERROR; 129 } 130 131 /* We have no idea what went wrong. */ 132 SVN_ERR_MALFUNCTION(); 133} 134 135 136/* Helper svn_ra_serf__get_file(). Attempts to fetch file contents 137 * using SESSION->wc_callbacks->get_wc_contents() if sha1 property is 138 * present in PROPS. 139 * 140 * Sets *FOUND_P to TRUE if file contents was successfuly fetched. 141 * 142 * Performs all temporary allocations in POOL. 143 */ 144static svn_error_t * 145try_get_wc_contents(svn_boolean_t *found_p, 146 svn_ra_serf__session_t *session, 147 const char *sha1_checksum_prop, 148 svn_stream_t *dst_stream, 149 apr_pool_t *pool) 150{ 151 svn_checksum_t *checksum; 152 svn_stream_t *wc_stream; 153 svn_error_t *err; 154 155 /* No contents found by default. */ 156 *found_p = FALSE; 157 158 if (!session->wc_callbacks->get_wc_contents 159 || sha1_checksum_prop == NULL) 160 { 161 /* Nothing to do. */ 162 return SVN_NO_ERROR; 163 } 164 165 SVN_ERR(svn_checksum_parse_hex(&checksum, svn_checksum_sha1, 166 sha1_checksum_prop, pool)); 167 168 err = session->wc_callbacks->get_wc_contents( 169 session->wc_callback_baton, &wc_stream, checksum, pool); 170 171 if (err) 172 { 173 svn_error_clear(err); 174 175 /* Ignore errors for now. */ 176 return SVN_NO_ERROR; 177 } 178 179 if (wc_stream) 180 { 181 SVN_ERR(svn_stream_copy3(wc_stream, 182 svn_stream_disown(dst_stream, pool), 183 NULL, NULL, pool)); 184 *found_p = TRUE; 185 } 186 187 return SVN_NO_ERROR; 188} 189 190/* ----------------------------------------------------------------------- 191 svn_ra_get_file() specific */ 192 193/* Implements svn_ra_serf__response_handler_t */ 194static svn_error_t * 195handle_stream(serf_request_t *request, 196 serf_bucket_t *response, 197 void *handler_baton, 198 apr_pool_t *pool) 199{ 200 stream_ctx_t *fetch_ctx = handler_baton; 201 apr_status_t status; 202 203 if (fetch_ctx->handler->sline.code != 200) 204 return svn_error_trace(svn_ra_serf__unexpected_status(fetch_ctx->handler)); 205 206 while (1) 207 { 208 const char *data; 209 apr_size_t len; 210 211 status = serf_bucket_read(response, 8000, &data, &len); 212 if (SERF_BUCKET_READ_ERROR(status)) 213 { 214 return svn_ra_serf__wrap_err(status, NULL); 215 } 216 217 fetch_ctx->read_size += len; 218 219 if (fetch_ctx->aborted_read) 220 { 221 apr_off_t skip; 222 223 /* We haven't caught up to where we were before. */ 224 if (fetch_ctx->read_size < fetch_ctx->aborted_read_size) 225 { 226 /* Eek. What did the file shrink or something? */ 227 if (APR_STATUS_IS_EOF(status)) 228 { 229 SVN_ERR_MALFUNCTION(); 230 } 231 232 /* Skip on to the next iteration of this loop. */ 233 if (APR_STATUS_IS_EAGAIN(status)) 234 { 235 return svn_ra_serf__wrap_err(status, NULL); 236 } 237 continue; 238 } 239 240 /* Woo-hoo. We're back. */ 241 fetch_ctx->aborted_read = FALSE; 242 243 /* Increment data and len by the difference. */ 244 skip = len - (fetch_ctx->read_size - fetch_ctx->aborted_read_size); 245 data += skip; 246 len -= (apr_size_t)skip; 247 } 248 249 if (len) 250 { 251 apr_size_t written_len; 252 253 written_len = len; 254 255 SVN_ERR(svn_stream_write(fetch_ctx->result_stream, data, 256 &written_len)); 257 } 258 259 if (status) 260 { 261 return svn_ra_serf__wrap_err(status, NULL); 262 } 263 } 264 /* not reached */ 265} 266 267/* Baton for get_file_prop_cb */ 268struct file_prop_baton_t 269{ 270 apr_pool_t *result_pool; 271 svn_node_kind_t kind; 272 apr_hash_t *props; 273 const char *sha1_checksum; 274}; 275 276/* Implements svn_ra_serf__prop_func_t for svn_ra_serf__get_file */ 277static svn_error_t * 278get_file_prop_cb(void *baton, 279 const char *path, 280 const char *ns, 281 const char *name, 282 const svn_string_t *value, 283 apr_pool_t *scratch_pool) 284{ 285 struct file_prop_baton_t *fb = baton; 286 const char *svn_name; 287 288 if (strcmp(ns, "DAV:") == 0 && strcmp(name, "resourcetype") == 0) 289 { 290 const char *val = value->data; 291 292 if (strcmp(val, "collection") == 0) 293 fb->kind = svn_node_dir; 294 else 295 fb->kind = svn_node_file; 296 297 return SVN_NO_ERROR; 298 } 299 else if (strcmp(ns, SVN_DAV_PROP_NS_DAV) == 0 300 && strcmp(name, "sha1-checksum") == 0) 301 { 302 fb->sha1_checksum = apr_pstrdup(fb->result_pool, value->data); 303 } 304 305 if (!fb->props) 306 return SVN_NO_ERROR; 307 308 svn_name = svn_ra_serf__svnname_from_wirename(ns, name, fb->result_pool); 309 if (svn_name) 310 { 311 svn_hash_sets(fb->props, svn_name, 312 svn_string_dup(value, fb->result_pool)); 313 } 314 return SVN_NO_ERROR; 315} 316 317svn_error_t * 318svn_ra_serf__get_file(svn_ra_session_t *ra_session, 319 const char *path, 320 svn_revnum_t revision, 321 svn_stream_t *stream, 322 svn_revnum_t *fetched_rev, 323 apr_hash_t **props, 324 apr_pool_t *pool) 325{ 326 svn_ra_serf__session_t *session = ra_session->priv; 327 const char *fetch_url; 328 const svn_ra_serf__dav_props_t *which_props; 329 svn_ra_serf__handler_t *propfind_handler; 330 struct file_prop_baton_t fb; 331 332 /* Fetch properties. */ 333 334 fetch_url = svn_path_url_add_component2(session->session_url.path, path, pool); 335 336 /* The simple case is if we want HEAD - then a GET on the fetch_url is fine. 337 * 338 * Otherwise, we need to get the baseline version for this particular 339 * revision and then fetch that file. 340 */ 341 if (SVN_IS_VALID_REVNUM(revision) || fetched_rev) 342 { 343 SVN_ERR(svn_ra_serf__get_stable_url(&fetch_url, fetched_rev, 344 session, 345 fetch_url, revision, 346 pool, pool)); 347 revision = SVN_INVALID_REVNUM; 348 } 349 /* REVISION is always SVN_INVALID_REVNUM */ 350 SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(revision)); 351 352 if (props) 353 which_props = all_props; 354 else if (stream && session->wc_callbacks->get_wc_contents) 355 which_props = type_and_checksum_props; 356 else 357 which_props = check_path_props; 358 359 fb.result_pool = pool; 360 fb.props = props ? apr_hash_make(pool) : NULL; 361 fb.kind = svn_node_unknown; 362 fb.sha1_checksum = NULL; 363 364 SVN_ERR(svn_ra_serf__create_propfind_handler(&propfind_handler, session, 365 fetch_url, SVN_INVALID_REVNUM, 366 "0", which_props, 367 get_file_prop_cb, &fb, 368 pool)); 369 370 SVN_ERR(svn_ra_serf__context_run_one(propfind_handler, pool)); 371 372 /* Verify that resource type is not collection. */ 373 if (fb.kind != svn_node_file) 374 { 375 return svn_error_create(SVN_ERR_FS_NOT_FILE, NULL, 376 _("Can't get text contents of a directory")); 377 } 378 379 if (props) 380 *props = fb.props; 381 382 if (stream) 383 { 384 svn_boolean_t found; 385 SVN_ERR(try_get_wc_contents(&found, session, fb.sha1_checksum, stream, pool)); 386 387 /* No contents found in the WC, let's fetch from server. */ 388 if (!found) 389 { 390 stream_ctx_t *stream_ctx; 391 svn_ra_serf__handler_t *handler; 392 393 /* Create the fetch context. */ 394 stream_ctx = apr_pcalloc(pool, sizeof(*stream_ctx)); 395 stream_ctx->result_stream = stream; 396 stream_ctx->using_compression = session->using_compression; 397 398 handler = svn_ra_serf__create_handler(session, pool); 399 400 handler->method = "GET"; 401 handler->path = fetch_url; 402 403 handler->custom_accept_encoding = TRUE; 404 handler->no_dav_headers = TRUE; 405 406 handler->header_delegate = headers_fetch; 407 handler->header_delegate_baton = stream_ctx; 408 409 handler->response_handler = handle_stream; 410 handler->response_baton = stream_ctx; 411 412 handler->response_error = cancel_fetch; 413 handler->response_error_baton = stream_ctx; 414 415 stream_ctx->handler = handler; 416 417 SVN_ERR(svn_ra_serf__context_run_one(handler, pool)); 418 419 if (handler->sline.code != 200) 420 return svn_error_trace(svn_ra_serf__unexpected_status(handler)); 421 } 422 } 423 424 return SVN_NO_ERROR; 425} 426