static size_t header_cb (void *ptr, size_t size, size_t nmemb, void *opaque) { struct curl_handle *h = opaque; size_t realsize = size * nmemb; size_t len; const char *accept_line = "Accept-Ranges: bytes"; const char *line = ptr; if (realsize >= strlen (accept_line) && strncmp (line, accept_line, strlen (accept_line)) == 0) h->accept_range = 1; /* Useful to print the server headers when debugging. However we * must strip off trailing \r?\n from each line. */ len = realsize; if (len > 0 && line[len-1] == '\n') len--; if (len > 0 && line[len-1] == '\r') len--; if (len > 0) nbdkit_debug ("S: %.*s", (int) len, line); return realsize; }
/* Convert a list of extents into NBD_REPLY_TYPE_BLOCK_STATUS blocks. * The rules here are very complicated. Read the spec carefully! */ static struct block_descriptor * extents_to_block_descriptors (struct nbdkit_extents *extents, uint16_t flags, uint32_t count, uint64_t offset, size_t *nr_blocks) { const bool req_one = flags & NBD_CMD_FLAG_REQ_ONE; const size_t nr_extents = nbdkit_extents_count (extents); size_t i; struct block_descriptor *blocks; /* This is checked in server/plugins.c. */ assert (nr_extents >= 1); /* We may send fewer than nr_extents blocks, but never more. */ blocks = calloc (req_one ? 1 : nr_extents, sizeof (struct block_descriptor)); if (blocks == NULL) { nbdkit_error ("calloc: %m"); return NULL; } if (req_one) { const struct nbdkit_extent e = nbdkit_get_extent (extents, 0); /* Checked as a side effect of how the extent list is created. */ assert (e.length > 0); *nr_blocks = 1; /* Must not exceed count of the original request. */ blocks[0].length = MIN (e.length, (uint64_t) count); blocks[0].status_flags = e.type & 3; } else { uint64_t pos = offset; *nr_blocks = 0; for (i = 0; i < nr_extents; ++i) { const struct nbdkit_extent e = nbdkit_get_extent (extents, i); uint64_t length; if (i == 0) assert (e.offset == offset); /* Must not exceed UINT32_MAX. */ blocks[i].length = length = MIN (e.length, UINT32_MAX); blocks[i].status_flags = e.type & 3; (*nr_blocks)++; pos += length; if (pos > offset + count) /* this must be the last block */ break; /* If we reach here then we must have consumed this whole * extent. This is currently true because the server only sends * 32 bit requests, but if we move to 64 bit requests we will * need to revisit this code so it can split extents into * multiple blocks. XXX */ assert (e.length <= length); } } #if 0 for (i = 0; i < *nr_blocks; ++i) nbdkit_debug ("block status: sending block %" PRIu32 " type %" PRIu32, blocks[i].length, blocks[i].status_flags); #endif /* Convert to big endian for the protocol. */ for (i = 0; i < *nr_blocks; ++i) { blocks[i].length = htobe32 (blocks[i].length); blocks[i].status_flags = htobe32 (blocks[i].status_flags); } return blocks; }
/* Create the per-connection handle. */ static void * curl_open (int readonly) { struct curl_handle *h; CURLcode r; double d; h = calloc (1, sizeof *h); if (h == NULL) { nbdkit_error ("calloc: %m"); return NULL; } h->c = curl_easy_init (); if (h->c == NULL) { nbdkit_error ("curl_easy_init: failed: %m"); goto err; } nbdkit_debug ("opened libcurl easy handle"); curl_easy_setopt (h->c, CURLOPT_ERRORBUFFER, h->errbuf); r = curl_easy_setopt (h->c, CURLOPT_URL, url); if (r != CURLE_OK) { display_curl_error (h, r, "curl_easy_setopt: CURLOPT_URL [%s]", url); goto err; } nbdkit_debug ("set libcurl URL: %s", url); /* Other possible settings, which could be specified on the command line: CURLOPT_PROXY */ curl_easy_setopt (h->c, CURLOPT_AUTOREFERER, 1); curl_easy_setopt (h->c, CURLOPT_FOLLOWLOCATION, 1); curl_easy_setopt (h->c, CURLOPT_FAILONERROR, 1); if (timeout > 0) curl_easy_setopt (h->c, CURLOPT_TIMEOUT, timeout); if (sslverify == 0) curl_easy_setopt (h->c, CURLOPT_SSL_VERIFYPEER, sslverify); if (user) curl_easy_setopt (h->c, CURLOPT_USERNAME, user); if (password) curl_easy_setopt (h->c, CURLOPT_USERPWD, password); /* Get the file size and also whether the remote HTTP server * supports byte ranges. */ h->accept_range = 0; curl_easy_setopt (h->c, CURLOPT_NOBODY, 1); /* No Body, not nobody! */ curl_easy_setopt (h->c, CURLOPT_HEADERFUNCTION, header_cb); curl_easy_setopt (h->c, CURLOPT_HEADERDATA, h); r = curl_easy_perform (h->c); if (r != CURLE_OK) { display_curl_error (h, r, "problem doing HEAD request to fetch size of URL [%s]", url); goto err; } r = curl_easy_getinfo (h->c, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); if (r != CURLE_OK) { display_curl_error (h, r, "could not get length of remote file [%s]", url); goto err; } if (d == -1) { nbdkit_error ("could not get length of remote file [%s], is the URL correct?", url); goto err; } h->exportsize = (size_t) d; nbdkit_debug ("content length: %" PRIi64, h->exportsize); if (strncasecmp (url, "http://", strlen ("http://")) == 0 || strncasecmp (url, "https://", strlen ("https://")) == 0) { if (!h->accept_range) { nbdkit_error ("server does not support 'range' (byte range) requests"); goto err; } nbdkit_debug ("accept range supported (for HTTP/HTTPS)"); } /* Get set up for reading and writing. */ curl_easy_setopt (h->c, CURLOPT_HEADERFUNCTION, NULL); curl_easy_setopt (h->c, CURLOPT_HEADERDATA, NULL); curl_easy_setopt (h->c, CURLOPT_WRITEFUNCTION, write_cb); curl_easy_setopt (h->c, CURLOPT_WRITEDATA, h); if (!readonly) { curl_easy_setopt (h->c, CURLOPT_READFUNCTION, read_cb); curl_easy_setopt (h->c, CURLOPT_READDATA, h); } nbdkit_debug ("returning new handle %p", h); return h; err: if (h->c) curl_easy_cleanup (h->c); free (h); return NULL; }
/* Create the per-connection handle. */ static void * split_open (int readonly) { struct handle *h; int flags; size_t i; uint64_t offset; struct stat statbuf; h = malloc (sizeof *h); if (h == NULL) { nbdkit_error ("malloc: %m"); return NULL; } h->files = malloc (nr_files * sizeof (struct file)); if (h->files == NULL) { nbdkit_error ("malloc: %m"); free (h); return NULL; } for (i = 0; i < nr_files; ++i) h->files[i].fd = -1; /* Open the files. */ flags = O_CLOEXEC|O_NOCTTY; if (readonly) flags |= O_RDONLY; else flags |= O_RDWR; for (i = 0; i < nr_files; ++i) { h->files[i].fd = open (filenames[i], flags); if (h->files[i].fd == -1) { nbdkit_error ("open: %s: %m", filenames[i]); goto err; } } offset = 0; for (i = 0; i < nr_files; ++i) { h->files[i].offset = offset; if (fstat (h->files[i].fd, &statbuf) == -1) { nbdkit_error ("stat: %s: %m", filenames[i]); goto err; } h->files[i].size = statbuf.st_size; offset += statbuf.st_size; nbdkit_debug ("file[%zu]=%s: offset=%" PRIu64 ", size=%" PRIu64, i, filenames[i], h->files[i].offset, h->files[i].size); } h->size = offset; nbdkit_debug ("total size=%" PRIu64, h->size); return h; err: for (i = 0; i < nr_files; ++i) { if (h->files[i].fd >= 0) close (h->files[i].fd); } free (h->files); free (h); return NULL; }