diff mbox series

[09/13] channel_curl: implement put_file entry point

Message ID 20231120155459.45188-10-stefano.babic@swupdate.org
State Accepted
Headers show
Series Native Docker Support | expand

Commit Message

Stefano Babic Nov. 20, 2023, 3:54 p.m. UTC
Use put_file to upload a file from a stream. Data is read from a fd that
must be set by the caller into channel_data_t structure. Caller must set
the total amoun t of data that must be uploaded, else libcurl cannot
know.

Signed-off-by: Stefano Babic <stefano.babic@swupdate.org>
---
 corelib/channel_curl.c | 125 +++++++++++++++++++++++++++++++++++------
 include/channel_curl.h |   1 +
 2 files changed, 110 insertions(+), 16 deletions(-)
diff mbox series

Patch

diff --git a/corelib/channel_curl.c b/corelib/channel_curl.c
index 4a209bcd..27b31247 100644
--- a/corelib/channel_curl.c
+++ b/corelib/channel_curl.c
@@ -86,6 +86,7 @@  static channel_op_res_t channel_open(channel_t *this, void *cfg);
 static channel_op_res_t channel_get(channel_t *this, void *data);
 static channel_op_res_t channel_get_file(channel_t *this, void *data);
 static channel_op_res_t channel_put(channel_t *this, void *data);
+static channel_op_res_t channel_put_file(channel_t *this, void *data);
 channel_op_res_t channel_curl_init(void);
 channel_t *channel_new(void);
 
@@ -119,6 +120,7 @@  channel_t *channel_new(void)
 		newchan->get = &channel_get;
 		newchan->get_file = &channel_get_file;
 		newchan->put = &channel_put;
+		newchan->put_file = &channel_put_file;
 		newchan->get_redirect_url = &channel_get_redirect_url;
 	}
 
@@ -888,12 +890,11 @@  cleanup:
 	return result;
 }
 
-static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *data)
+static size_t read_callback(char *ptr, size_t size, size_t nmemb, void *data)
 {
 	channel_data_t *channel_data = (channel_data_t *)data;
-	ssize_t bytes;
+	ssize_t nbytes;
 	size_t n = 0;
-	int ret;
 
 	/*
 	 * Check if data is stored in a buffer or should be read
@@ -901,30 +902,34 @@  static size_t read_callback(void *ptr, size_t size, size_t nmemb, void *data)
 	 */
 	if (channel_data->request_body) {
 		/* Check data to be sent */
-		bytes = strlen(channel_data->request_body) - channel_data->offs;
+		nbytes = strlen(channel_data->request_body) - channel_data->offs;
 
-		if (!bytes)
+		if (!nbytes)
 			return 0;
 
-		n = min(bytes, size * nmemb);
+		n = min(nbytes, size * nmemb);
 
 		memcpy(ptr, &channel_data->request_body[channel_data->offs], n);
 		channel_data->offs += n;
 	} else {
-		bytes = nmemb * size;
-		ret = read(channel_data->read_fifo, ptr, bytes);
-		if (ret < 0) {
+		if (nmemb * size > channel_data->upload_filesize)
+			nbytes =  channel_data->upload_filesize;
+		else
+			nbytes = nmemb * size;
+
+		nbytes = read(channel_data->read_fifo, ptr, nbytes);
+		if (nbytes < 0) {
 			if (errno == EAGAIN) {
 				TRACE("READ EAGAIN");
-				bytes = 0;
+				nbytes = 0;
 			} else {
 				ERROR("Cannot read from FIFO");
 				return CURL_READFUNC_ABORT;
 			}
-		} else
-			bytes = ret;
+		}
 
-		n = bytes / nmemb;
+		n = nbytes / size;
+		channel_data->upload_filesize -= nbytes;
 	}
 
 	return n;
@@ -1018,9 +1023,8 @@  static CURLcode channel_set_read_callback(channel_curl_t *handle, channel_data_t
 {
 
 	return curl_easy_setopt(handle, CURLOPT_READFUNCTION, read_callback) ||
-		(channel_data->request_body ?
-			curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE,
-				(curl_off_t)strlen(channel_data->request_body)) : 0) ||
+		curl_easy_setopt(handle, CURLOPT_INFILESIZE_LARGE,
+				  channel_data->request_body ? (curl_off_t)strlen(channel_data->request_body) : (curl_off_t)channel_data->upload_filesize) ||
 		curl_easy_setopt(handle, CURLOPT_READDATA, channel_data);
 }
 
@@ -1136,6 +1140,95 @@  channel_op_res_t channel_put(channel_t *this, void *data)
 	}
 }
 
+channel_op_res_t channel_put_file(channel_t *this, void *data)
+{
+	CURLcode curl_result = CURLE_OK;
+	channel_curl_t *channel_curl = this->priv;
+	assert(data != NULL);
+	assert(channel_curl->handle != NULL);
+
+	channel_op_res_t result = CHANNEL_OK;
+	channel_data_t *channel_data = (channel_data_t *)data;
+	channel_data->offs = 0;
+	output_data_t outdata = {};
+	write_callback_t wrdata = { .this = this, .channel_data = channel_data, .outdata = &outdata };
+
+	if ((result = channel_set_content_type(this, channel_data)) !=
+	    CHANNEL_OK) {
+		ERROR("Set content-type option failed.");
+		goto cleanup_header;
+	}
+
+	if ((result = channel_set_options(this, channel_data)) != CHANNEL_OK) {
+		ERROR("Set channel option failed.");
+		goto cleanup_header;
+	}
+
+	if ((result = setup_reply_buffer(channel_curl->handle, &wrdata)) != CHANNEL_OK) {
+		goto cleanup_header;
+	}
+
+	if (!channel_data->method)
+		channel_data->method = CHANNEL_POST;
+
+	switch (channel_data->method)  {
+	case CHANNEL_PATCH:
+	case CHANNEL_POST:
+		if (channel_data->method == CHANNEL_PATCH)
+			curl_result |= curl_easy_setopt(channel_curl->handle, CURLOPT_CUSTOMREQUEST, "PATCH");
+		else
+			curl_result |= curl_easy_setopt(channel_curl->handle, CURLOPT_POST, 1L);
+
+		break;
+
+	case CHANNEL_PUT:
+		curl_result |= curl_easy_setopt(channel_curl->handle,
+						#if LIBCURL_VERSION_NUM >= 0x70C01
+						CURLOPT_UPLOAD,
+						#else
+						CURLOPT_PUT,
+						#endif
+						1L);
+		break;
+	}
+
+	curl_result |= channel_set_read_callback(channel_curl->handle, channel_data);
+	if (curl_result != CURLE_OK) {
+		result = CHANNEL_EINIT;
+		ERROR("Set %s channel method option failed.", method_desc[channel_data->method]);
+		goto cleanup_header;
+	}
+
+	CURLcode curlrc = curl_easy_perform(channel_curl->handle);
+	if (curlrc != CURLE_OK) {
+		ERROR("Channel %s operation failed (%d): '%s'", method_desc[channel_data->method], curlrc,
+		      curl_easy_strerror(curlrc));
+		result = channel_map_curl_error(curlrc);
+		goto cleanup_header;
+	}
+
+	channel_log_effective_url(this);
+
+	result = channel_map_http_code(this, &channel_data->http_response_code);
+
+	if (channel_data->nocheckanswer)
+		goto cleanup_header;
+
+	channel_log_reply(result, channel_data, &outdata);
+
+	if (result == CHANNEL_OK) {
+	    result = parse_reply(channel_data, &outdata);
+	}
+
+cleanup_header:
+	outdata.memory != NULL ? free(outdata.memory) : (void)0;
+	curl_easy_reset(channel_curl->handle);
+	curl_slist_free_all(channel_curl->header);
+	channel_curl->header = NULL;
+
+	return result;
+}
+
 channel_op_res_t channel_get_file(channel_t *this, void *data)
 {
 	channel_curl_t *channel_curl = this->priv;
diff --git a/include/channel_curl.h b/include/channel_curl.h
index b1fc65c2..b3b51e85 100644
--- a/include/channel_curl.h
+++ b/include/channel_curl.h
@@ -90,6 +90,7 @@  typedef struct {
 	struct dict *headers_to_send;
 	struct dict *received_headers;
 	unsigned int max_download_speed;
+	size_t	upload_filesize;
 	char *range; /* Range request for get_file in any */
 	void *user;
 } channel_data_t;