cloud-setup: merge branch 'th/cloud-setup-various'

https://gitlab.freedesktop.org/NetworkManager/NetworkManager/-/merge_requests/553
This commit is contained in:
Thomas Haller 2020-07-03 10:41:46 +02:00
commit 66e2d8c38a
No known key found for this signature in database
GPG key ID: 29C2366E4DFC5728
7 changed files with 286 additions and 162 deletions

View file

@ -6,6 +6,7 @@
#include "nm-glib-aux/nm-time-utils.h"
#include "nm-glib-aux/nm-logging-base.h"
#include "nm-glib-aux/nm-str-buf.h"
/*****************************************************************************/
@ -257,7 +258,6 @@ _poll_task_data_free (gpointer data)
static void
_poll_return (PollTaskData *poll_task_data,
gboolean success,
GError *error_take)
{
nm_clear_g_source_inst (&poll_task_data->source_next_poll);
@ -270,7 +270,7 @@ _poll_return (PollTaskData *poll_task_data,
if (error_take)
g_task_return_error (poll_task_data->task, g_steal_pointer (&error_take));
else
g_task_return_boolean (poll_task_data->task, success);
g_task_return_boolean (poll_task_data->task, TRUE);
g_object_unref (poll_task_data->task);
}
@ -301,7 +301,7 @@ _poll_done_cb (GObject *source,
if ( error
|| is_finished) {
_poll_return (poll_task_data, TRUE, g_steal_pointer (&error));
_poll_return (poll_task_data, g_steal_pointer (&error));
return;
}
@ -345,8 +345,8 @@ _poll_timeout_cb (gpointer user_data)
{
PollTaskData *poll_task_data = user_data;
_poll_return (poll_task_data, FALSE, nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN,
"timeout expired"));
_poll_return (poll_task_data, nm_utils_error_new (NM_UTILS_ERROR_UNKNOWN,
"timeout expired"));
return G_SOURCE_CONTINUE;
}
@ -356,17 +356,16 @@ _poll_cancelled_cb (GObject *object, gpointer user_data)
PollTaskData *poll_task_data = user_data;
GError *error = NULL;
_LOGD (">> poll cancelled");
nm_clear_g_signal_handler (g_task_get_cancellable (poll_task_data->task),
&poll_task_data->cancellable_id);
nm_utils_error_set_cancelled (&error, FALSE, NULL);
_poll_return (poll_task_data, FALSE, error);
_poll_return (poll_task_data, error);
}
/**
* nmcs_utils_poll:
* @poll_timeout_ms: if >= 0, then this is the overall timeout for how long we poll.
* When this timeout expires, the request completes with failure (but no error set).
* When this timeout expires, the request completes with failure (and error set).
* @ratelimit_timeout_ms: if > 0, we ratelimit the starts from one prope_start_fcn
* call to the next.
* @sleep_timeout_ms: if > 0, then we wait after a probe finished this timeout
@ -459,7 +458,8 @@ nmcs_utils_poll (int poll_timeout_ms,
* %FALSE will be returned.
* If the probe returned a failure, this returns %FALSE and the error
* provided by @probe_finish_fcn.
* If the request times out, this returns %FALSE without error set.
* If the request times out, this returns %FALSE with error set.
* Error is always set if (and only if) the function returns %FALSE.
*/
gboolean
nmcs_utils_poll_finish (GAsyncResult *result,
@ -565,15 +565,13 @@ nmcs_utils_uri_build_concat_v (const char *base,
const char **components,
gsize n_components)
{
GString *uri;
NMStrBuf strbuf = NM_STR_BUF_INIT (NM_UTILS_GET_NEXT_REALLOC_SIZE_104, FALSE);
nm_assert (base);
nm_assert (base[0]);
nm_assert (!NM_STR_HAS_SUFFIX (base, "/"));
uri = g_string_sized_new (100);
g_string_append (uri, base);
nm_str_buf_append (&strbuf, base);
if ( n_components > 0
&& components[0]
@ -583,18 +581,18 @@ nmcs_utils_uri_build_concat_v (const char *base,
*
* We only do that for the first component. */
} else
g_string_append_c (uri, '/');
nm_str_buf_append_c (&strbuf, '/');
while (n_components > 0) {
if (!components[0]) {
/* we allow NULL, to indicate nothing to append*/
/* we allow NULL, to indicate nothing to append */
} else
g_string_append (uri, components[0]);
nm_str_buf_append (&strbuf, components[0]);
components++;
n_components--;
}
return g_string_free (uri, FALSE);
return nm_str_buf_finalize (&strbuf, NULL);
}
/*****************************************************************************/

View file

@ -7,6 +7,7 @@
#include <curl/curl.h>
#include "nm-cloud-setup-utils.h"
#include "nm-glib-aux/nm-str-buf.h"
#define NM_CURL_DEBUG 0
@ -118,7 +119,7 @@ typedef struct {
CURLcode ehandle_result;
CURL *ehandle;
char *url;
GString *recv_data;
NMStrBuf recv_data;
struct curl_slist *headers;
gssize max_data;
gulong cancellable_id;
@ -144,8 +145,7 @@ _ehandle_free (EHandleData *edata)
g_object_unref (edata->task);
if (edata->recv_data)
g_string_free (edata->recv_data, TRUE);
nm_str_buf_destroy (&edata->recv_data);
if (edata->headers)
curl_slist_free_all (edata->headers);
g_free (edata->url);
@ -191,12 +191,15 @@ _ehandle_complete (EHandleData *edata,
_LOG2E (edata, "failed to get response code from curl easy handle");
_LOG2D (edata, "success getting %"G_GSIZE_FORMAT" bytes (response code %ld)",
edata->recv_data->len,
edata->recv_data.len,
response_code);
_LOG2T (edata, "received %"G_GSIZE_FORMAT" bytes: [[%s]]",
edata->recv_data->len,
nm_utils_buf_utf8safe_escape (edata->recv_data->str, edata->recv_data->len, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL, &str_tmp_1));
edata->recv_data.len,
nm_utils_buf_utf8safe_escape (nm_str_buf_get_str (&edata->recv_data),
edata->recv_data.len,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
&str_tmp_1));
_ehandle_free_ehandle (edata);
@ -205,7 +208,7 @@ _ehandle_complete (EHandleData *edata,
.response_code = response_code,
/* This ensures that response_data is always NUL terminated. This is an important guarantee
* that NMHttpClient makes. */
.response_data = g_string_free_to_bytes (g_steal_pointer (&edata->recv_data)),
.response_data = nm_str_buf_finalize_to_gbytes (&edata->recv_data),
};
g_task_return_pointer (edata->task, get_result, _get_result_free);
@ -225,14 +228,14 @@ _get_writefunction_cb (char *ptr, size_t size, size_t nmemb, void *user_data)
nmemb *= size;
if (edata->max_data >= 0) {
nm_assert (edata->recv_data->len <= edata->max_data);
nconsume = (((gsize) edata->max_data) - edata->recv_data->len);
nm_assert (edata->recv_data.len <= edata->max_data);
nconsume = (((gsize) edata->max_data) - edata->recv_data.len);
if (nconsume > nmemb)
nconsume = nmemb;
} else
nconsume = nmemb;
g_string_append_len (edata->recv_data, ptr, nconsume);
nm_str_buf_append_len (&edata->recv_data, ptr, nconsume);
return nconsume;
}
@ -283,7 +286,7 @@ nm_http_client_get (NMHttpClient *self,
edata = g_slice_new (EHandleData);
*edata = (EHandleData) {
.task = nm_g_task_new (self, cancellable, nm_http_client_get, callback, user_data),
.recv_data = g_string_sized_new (NM_MIN (max_data, 245)),
.recv_data = NM_STR_BUF_INIT (0, FALSE),
.max_data = max_data,
.url = g_strdup (url),
.headers = NULL,
@ -351,6 +354,21 @@ nm_http_client_get (NMHttpClient *self,
}
}
/**
* nm_http_client_get_finish:
* @self: the #NMHttpClient instance
* @result: the #GAsyncResult which to complete.
* @out_response_code: (allow-none) (out): the HTTP response code or -1 on other error.
* @out_response_data: (allow-none) (transfer full): the HTTP response data, if any.
* The GBytes buffer is guaranteed to have a trailing NUL character *after* the
* returned buffer size. That means, you can always trust that the buffer is NUL terminated
* and that there is one additional hidden byte after the data.
* Also, the returned buffer is allocated just for you. While GBytes is immutable, you are
* allowed to modify the buffer as it's not used by anybody else.
* @error: the error
*
* Returns: %TRUE on success or %FALSE with an error code.
*/
gboolean
nm_http_client_get_finish (NMHttpClient *self,
GAsyncResult *result,
@ -364,6 +382,9 @@ nm_http_client_get_finish (NMHttpClient *self,
g_return_val_if_fail (nm_g_task_is_valid (result, self, nm_http_client_get), FALSE);
get_result = g_task_propagate_pointer (G_TASK (result), error);
nm_assert ((!!get_result) == (!error));
if (!get_result) {
NM_SET_OUT (out_response_code, -1);
NM_SET_OUT (out_response_data, NULL);
@ -376,7 +397,6 @@ nm_http_client_get_finish (NMHttpClient *self,
NM_SET_OUT (out_response_data, g_steal_pointer (&get_result->response_data));
_get_result_free (get_result);
return TRUE;
}
@ -447,11 +467,14 @@ _poll_get_probe_finish_fcn (GObject *source,
&response_data,
&local_error);
if (!success) {
nm_assert ((!!success) == (!local_error));
if (local_error) {
if (nm_utils_error_is_cancelled (local_error)) {
g_propagate_error (error, g_steal_pointer (&local_error));
return TRUE;
}
/* any other error. Continue polling. */
return FALSE;
}
@ -468,8 +491,10 @@ _poll_get_probe_finish_fcn (GObject *source,
return TRUE;
}
if (!success)
if (!success) {
/* Not yet ready. Continue polling. */
return FALSE;
}
poll_get_data->response_code = response_code;
poll_get_data->response_data = g_steal_pointer (&response_data);
@ -487,10 +512,12 @@ _poll_get_done_cb (GObject *source,
success = nmcs_utils_poll_finish (result, NULL, &error);
nm_assert ((!!success) == (!error));
if (error)
g_task_return_error (poll_get_data->task, g_steal_pointer (&error));
else
g_task_return_boolean (poll_get_data->task, success);
g_task_return_boolean (poll_get_data->task, TRUE);
g_object_unref (poll_get_data->task);
}
@ -569,10 +596,11 @@ nm_http_client_poll_get_finish (NMHttpClient *self,
task = G_TASK (result);
success = g_task_propagate_boolean (task, &local_error);
if ( local_error
|| !success) {
if (local_error)
g_propagate_error (error, g_steal_pointer (&local_error));
nm_assert ((!!success) == (!local_error));
if (local_error) {
g_propagate_error (error, g_steal_pointer (&local_error));
NM_SET_OUT (out_response_code, -1);
NM_SET_OUT (out_response_data, NULL);
return FALSE;
@ -582,7 +610,6 @@ nm_http_client_poll_get_finish (NMHttpClient *self,
NM_SET_OUT (out_response_code, poll_get_data->response_code);
NM_SET_OUT (out_response_data, g_steal_pointer (&poll_get_data->response_data));
return TRUE;
}

View file

@ -90,13 +90,12 @@ _detect_get_meta_data_done_cb (GObject *source,
gs_unref_object GTask *task = user_data;
gs_free_error GError *get_error = NULL;
gs_free_error GError *error = NULL;
gboolean success;
success = nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source),
result,
NULL,
NULL,
&get_error);
nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source),
result,
NULL,
NULL,
&get_error);
if (nm_utils_error_is_cancelled (get_error)) {
g_task_return_error (task, g_steal_pointer (&get_error));
@ -112,14 +111,6 @@ _detect_get_meta_data_done_cb (GObject *source,
return;
}
if (!success) {
nm_utils_error_set (&error,
NM_UTILS_ERROR_UNKNOWN,
"failure to detect EC2 metadata");
g_task_return_error (task, g_steal_pointer (&error));
return;
}
g_task_return_boolean (task, TRUE);
}
@ -191,31 +182,28 @@ _get_config_fetch_done_cb (NMHttpClient *http_client,
gboolean is_local_ipv4)
{
GetConfigIfaceData *iface_data;
NMCSProviderGetConfigTaskData *get_config_data;
const char *hwaddr = NULL;
gs_unref_bytes GBytes *response_data = NULL;
gs_free_error GError *error = NULL;
gboolean success;
NMCSProviderGetConfigIfaceData *config_iface_data;
nm_utils_user_data_unpack (user_data, &iface_data, &hwaddr);
success = nm_http_client_poll_get_finish (http_client,
result,
NULL,
&response_data,
&error);
nm_http_client_poll_get_finish (http_client,
result,
NULL,
&response_data,
&error);
if (nm_utils_error_is_cancelled (error))
return;
get_config_data = iface_data->get_config_data;
config_iface_data = g_hash_table_lookup (get_config_data->result_dict, hwaddr);
if (success) {
if (!error) {
NMCSProviderGetConfigIfaceData *config_iface_data;
in_addr_t tmp_addr;
int tmp_prefix;
config_iface_data = g_hash_table_lookup (iface_data->get_config_data->result_dict, hwaddr);
if (is_local_ipv4) {
gs_free const char **s_addrs = NULL;
gsize i, len;
@ -436,7 +424,9 @@ _get_config_metadata_ready_check (long response_code,
GetConfigMetadataData *metadata_data = check_user_data;
gs_unref_hashtable GHashTable *response_parsed = NULL;
const guint8 *r_data;
const char *cur_line;
gsize r_len;
gsize cur_line_len;
GHashTableIter h_iter;
gboolean has_all;
const char *c_hwaddr;
@ -449,48 +439,33 @@ _get_config_metadata_ready_check (long response_code,
}
r_data = g_bytes_get_data (response_data, &r_len);
/* NMHttpClient guarantees that there is a trailing NUL after the data. */
nm_assert (r_data[r_len] == 0);
while (r_len > 0) {
const guint8 *p_eol;
const char *p_start;
gsize p_start_l;
gsize p_start_l_2;
char *hwaddr;
while (nm_utils_parse_next_line ((const char **) &r_data, &r_len, &cur_line, &cur_line_len)) {
GetConfigMetadataMac *mac_data;
char *hwaddr;
p_start = (const char *) r_data;
p_eol = memchr (r_data, '\n', r_len);
if (p_eol) {
p_start_l = (p_eol - r_data);
r_len -= p_start_l + 1;
r_data = &p_eol[1];
} else {
p_start_l = r_len;
r_data = &r_data[r_len];
r_len = 0;
}
if (p_start_l == 0)
if (cur_line_len == 0)
continue;
p_start_l_2 = p_start_l;
if (p_start[p_start_l_2 - 1] == '/') {
/* trim the trailing "/". */
p_start_l_2--;
}
/* Truncate the string. It's safe to do, because we own @response_data an it has an
* extra NUL character after the buffer. */
((char *) cur_line)[cur_line_len] = '\0';
hwaddr = nmcs_utils_hwaddr_normalize (p_start, p_start_l_2);
hwaddr = nmcs_utils_hwaddr_normalize (cur_line,
cur_line[cur_line_len - 1u] == '/'
? (gssize) (cur_line_len - 1u)
: -1);
if (!hwaddr)
continue;
if (!response_parsed)
response_parsed = g_hash_table_new_full (nm_str_hash, g_str_equal, g_free, g_free);
mac_data = g_malloc (sizeof (GetConfigMetadataData) + 1 + p_start_l);
mac_data = g_malloc (sizeof (GetConfigMetadataMac) + 1u + cur_line_len);
mac_data->iface_idx = iface_idx_counter++;
memcpy (mac_data->path, p_start, p_start_l);
mac_data->path[p_start_l] = '\0';
memcpy (mac_data->path, cur_line, cur_line_len + 1u);
g_hash_table_insert (response_parsed, hwaddr, mac_data);
}

View file

@ -46,13 +46,12 @@ _detect_get_meta_data_done_cb (GObject *source,
gs_unref_object GTask *task = user_data;
gs_free_error GError *get_error = NULL;
gs_free_error GError *error = NULL;
gboolean success;
success = nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source),
result,
NULL,
NULL,
&get_error);
nm_http_client_poll_get_finish (NM_HTTP_CLIENT (source),
result,
NULL,
NULL,
&get_error);
if (nm_utils_error_is_cancelled (get_error)) {
g_task_return_error (task, g_steal_pointer (&get_error));
@ -68,14 +67,6 @@ _detect_get_meta_data_done_cb (GObject *source,
return;
}
if (!success) {
nm_utils_error_set (&error,
NM_UTILS_ERROR_UNKNOWN,
"failure to detect GCP metadata");
g_task_return_error (task, g_steal_pointer (&error));
return;
}
g_task_return_boolean (task, TRUE);
}
@ -246,29 +237,29 @@ _get_config_ips_list_cb (GObject *source,
if (error)
goto fips_error;
response_str = g_bytes_get_data (response, &response_len);
/* NMHttpClient guarantees that there is a trailing NUL after the data. */
nm_assert (response_str[response_len] == 0);
uri_arr = g_ptr_array_new_with_free_func (g_free);
response_str = g_bytes_get_data (response, &response_len);
while (nm_utils_parse_next_line (&response_str,
&response_len,
&line,
&line_len)) {
nm_auto_free_gstring GString *gstr = NULL;
gint64 fip_index;
gstr = g_string_new_len (line, line_len);
fip_index = _nm_utils_ascii_str_to_int64 (gstr->str, 10, 0, G_MAXINT64, -1);
/* Truncate the string. It's safe to do, because we own @response_data an it has an
* extra NUL character after the buffer. */
((char *) line)[line_len] = '\0';
if (fip_index < 0) {
fip_index = _nm_utils_ascii_str_to_int64 (line, 10, 0, G_MAXINT64, -1);
if (fip_index < 0)
continue;
}
g_string_printf (gstr,
"%"G_GSSIZE_FORMAT"/forwarded-ips/%"G_GINT64_FORMAT,
iface_data->iface_idx,
fip_index);
g_ptr_array_add (uri_arr, g_string_free (g_steal_pointer (&gstr), FALSE));
g_ptr_array_add (uri_arr,
g_strdup_printf ("%"G_GSSIZE_FORMAT"/forwarded-ips/%"G_GINT64_FORMAT,
iface_data->iface_idx,
fip_index));
}
iface_data->n_fips_pending = uri_arr->len;
@ -321,7 +312,7 @@ _get_config_iface_cb (GObject *source,
gs_free_error GError *error = NULL;
gs_free const char *hwaddr = NULL;
gs_free const char *uri = NULL;
gs_free char *str = NULL;
char sbuf[100];
GCPData *gcp_data;
gcp_data = iface_data->gcp_data;
@ -350,11 +341,10 @@ _get_config_iface_cb (GObject *source,
iface_data->iface_idx,
hwaddr);
str = g_strdup_printf ("%"G_GSSIZE_FORMAT"/forwarded-ips/",
iface_data->iface_idx);
nm_sprintf_buf (sbuf, "%"G_GSSIZE_FORMAT"/forwarded-ips/", iface_data->iface_idx);
nm_http_client_poll_get (NM_HTTP_CLIENT (source),
(uri = _gcp_uri_interfaces (str)),
(uri = _gcp_uri_interfaces (sbuf)),
HTTP_TIMEOUT_MS,
HTTP_REQ_MAX_DATA,
HTTP_POLL_TIMEOUT_MS,
@ -379,13 +369,10 @@ _get_net_ifaces_list_cb (GObject *source,
gpointer user_data)
{
gs_unref_ptrarray GPtrArray *ifaces_arr = NULL;
nm_auto_free_gstring GString *gstr = NULL;
gs_unref_bytes GBytes *response = NULL;
gs_free_error GError *error = NULL;
GCPData *gcp_data = user_data;
const char *response_str;
const char *token_start;
const char *token_end;
gsize response_len;
const char *line;
gsize line_len;
@ -403,8 +390,10 @@ _get_net_ifaces_list_cb (GObject *source,
}
response_str = g_bytes_get_data (response, &response_len);
/* NMHttpClient guarantees that there is a trailing NUL after the data. */
nm_assert (response_str[response_len] == 0);
ifaces_arr = g_ptr_array_new ();
gstr = g_string_new (NULL);
while (nm_utils_parse_next_line (&response_str,
&response_len,
@ -413,16 +402,16 @@ _get_net_ifaces_list_cb (GObject *source,
GCPIfaceData *iface_data;
gssize iface_idx;
token_start = line;
token_end = memchr (token_start, '/', line_len);
if (!token_end)
if (line_len == 0)
continue;
g_string_truncate (gstr, 0);
g_string_append_len (gstr, token_start, token_end - token_start);
iface_idx = _nm_utils_ascii_str_to_int64 (gstr->str, 10, 0, G_MAXSSIZE, -1);
/* Truncate the string. It's safe to do, because we own @response_data an it has an
* extra NUL character after the buffer. */
((char *) line)[line_len] = '\0';
if (line[line_len - 1] == '/')
((char *) line)[--line_len] = '\0';
iface_idx = _nm_utils_ascii_str_to_int64 (line, 10, 0, G_MAXSSIZE, -1);
if (iface_idx < 0)
continue;
@ -442,14 +431,15 @@ _get_net_ifaces_list_cb (GObject *source,
for (i = 0; i < ifaces_arr->len; ++i) {
GCPIfaceData *data = ifaces_arr->pdata[i];
gs_free const char *uri = NULL;
char sbuf[100];
_LOGD ("GCP interface[%"G_GSSIZE_FORMAT"]: retrieving configuration",
data->iface_idx);
g_string_printf (gstr, "%"G_GSSIZE_FORMAT"/mac", data->iface_idx);
nm_sprintf_buf (sbuf, "%"G_GSSIZE_FORMAT"/mac", data->iface_idx);
nm_http_client_poll_get (NM_HTTP_CLIENT (source),
(uri = _gcp_uri_interfaces (gstr->str)),
(uri = _gcp_uri_interfaces (sbuf)),
HTTP_TIMEOUT_MS,
HTTP_REQ_MAX_DATA,
HTTP_POLL_TIMEOUT_MS,
@ -484,7 +474,6 @@ get_config (NMCSProvider *provider,
.n_ifaces_pending = 0,
.error = NULL,
.success = FALSE,
};
nm_http_client_poll_get (nmcs_provider_get_http_client (provider),

View file

@ -1080,39 +1080,53 @@ nm_utils_parse_next_line (const char **inout_ptr,
const char **out_line,
gsize *out_line_len)
{
gboolean eol_is_carriage_return;
const char *line_start;
const char *line_end;
gsize line_len;
g_return_val_if_fail (inout_ptr, FALSE);
g_return_val_if_fail (inout_len, FALSE);
g_return_val_if_fail (out_line, FALSE);
nm_assert (inout_ptr);
nm_assert (inout_len);
nm_assert (*inout_len == 0 || *inout_ptr);
nm_assert (out_line);
nm_assert (out_line_len);
if (*inout_len <= 0)
goto error;
if (G_UNLIKELY (*inout_len == 0))
return FALSE;
line_start = *inout_ptr;
line_end = memchr (line_start, '\n', *inout_len);
if (!line_end)
line_end = memchr (line_start, '\0', *inout_len);
if (!line_end) {
line_end = line_start + *inout_len;
NM_SET_OUT (inout_len, 0);
} else
NM_SET_OUT (inout_len, *inout_len - (line_end - line_start) - 1);
NM_SET_OUT (out_line, line_start);
NM_SET_OUT (out_line_len, (gsize) (line_end - line_start));
eol_is_carriage_return = FALSE;
for (line_len = 0; ; line_len++) {
if (line_len >= *inout_len) {
/* if we consumed the entire line, we place the pointer at
* one character after the end. */
*inout_ptr = &line_start[line_len];
*inout_len = 0;
goto done;
}
switch (line_start[line_len]) {
case '\r':
eol_is_carriage_return = TRUE;
/* fall-through*/
case '\0':
case '\n':
*inout_ptr = &line_start[line_len + 1];
*inout_len = *inout_len - line_len - 1u;
if ( eol_is_carriage_return
&& *inout_len > 0
&& (*inout_ptr)[0] == '\n') {
/* also consume "\r\n" as one. */
(*inout_len)--;
(*inout_ptr)++;
}
goto done;
}
}
if (*inout_len > 0)
NM_SET_OUT (inout_ptr, line_end + 1);
else
NM_SET_OUT (inout_ptr, NULL);
done:
*out_line = line_start;
*out_line_len = line_len;
return TRUE;
error:
NM_SET_OUT (out_line, NULL);
NM_SET_OUT (out_line_len, 0);
return FALSE;
}
/*****************************************************************************/

View file

@ -429,6 +429,23 @@ nm_str_buf_finalize (NMStrBuf *strbuf,
return g_steal_pointer (&strbuf->_priv_str);
}
static inline GBytes *
nm_str_buf_finalize_to_gbytes (NMStrBuf *strbuf)
{
char *s;
gsize l;
/* this always returns a non-NULL, newly allocated GBytes instance.
* The data buffer always has an additional NUL character after
* the data, and the data is allocated with malloc.
*
* That means, the caller who takes ownership of the GBytes can
* safely modify the content of the buffer (including the additional
* NUL sentinel). */
s = nm_str_buf_finalize (strbuf, &l);
return g_bytes_new_take (s ?: g_new0 (char, 1), l);
}
/**
* nm_str_buf_destroy:
* @strbuf: an initialized #NMStrBuf

View file

@ -773,6 +773,109 @@ test_nm_str_buf (void)
/*****************************************************************************/
static void
test_nm_utils_parse_next_line (void)
{
const char *data;
const char *data0;
gsize data_len;
const char *line_start;
gsize line_len;
int i_run;
gsize j, k;
data = NULL;
data_len = 0;
g_assert (!nm_utils_parse_next_line (&data, &data_len, &line_start, &line_len));
for (i_run = 0; i_run < 1000; i_run++) {
gs_unref_ptrarray GPtrArray *strv = g_ptr_array_new_with_free_func (g_free);
gs_unref_ptrarray GPtrArray *strv2 = g_ptr_array_new_with_free_func (g_free);
gsize strv_len = nmtst_get_rand_word_length (NULL);
nm_auto_str_buf NMStrBuf strbuf = NM_STR_BUF_INIT (0, nmtst_get_rand_bool ());
/* create a list of random words. */
for (j = 0; j < strv_len; j++) {
gsize w_len = nmtst_get_rand_word_length (NULL);
NMStrBuf w_buf = NM_STR_BUF_INIT (nmtst_get_rand_uint32 () % (w_len + 1), nmtst_get_rand_bool ());
for (k = 0; k < w_len; k++)
nm_str_buf_append_c (&w_buf, '0' + (k % 10));
nm_str_buf_maybe_expand (&w_buf, 1, TRUE);
g_ptr_array_add (strv, nm_str_buf_finalize (&w_buf, NULL));
}
/* join the list of random words with (random) line delimiters
* ("\0", "\n", "\r" or EOF). */
for (j = 0; j < strv_len; j++) {
nm_str_buf_append (&strbuf, strv->pdata[j]);
again:
switch (nmtst_get_rand_uint32 () % 5) {
case 0:
nm_str_buf_append_c (&strbuf, '\0');
break;
case 1:
if ( strbuf.len > 0
&& (nm_str_buf_get_str_unsafe (&strbuf))[strbuf.len - 1] == '\r') {
/* the previous line was empty and terminated by "\r". We
* must not join with "\n". Retry. */
goto again;
}
nm_str_buf_append_c (&strbuf, '\n');
break;
case 2:
nm_str_buf_append_c (&strbuf, '\r');
break;
case 3:
nm_str_buf_append (&strbuf, "\r\n");
break;
case 4:
/* the last word randomly is delimited or not, but not if the last
* word is "". */
if (j + 1 < strv_len) {
/* it's not the last word. Retry. */
goto again;
}
g_assert (j == strv_len - 1);
if (((const char *) strv->pdata[j])[0] == '\0') {
/* if the last word was "", we need a delimiter (to parse it back).
* Retry. */
goto again;
}
/* The final delimiter gets omitted. It's EOF. */
break;
}
}
data0 = nm_str_buf_get_str_unsafe (&strbuf);
if ( !data0
&& nmtst_get_rand_bool ()) {
nm_str_buf_maybe_expand (&strbuf, 1, TRUE);
data0 = nm_str_buf_get_str_unsafe (&strbuf);
g_assert (data0);
}
data_len = strbuf.len;
g_assert ((data_len > 0 && data0) || data_len == 0);
data = data0;
while (nm_utils_parse_next_line (&data, &data_len, &line_start, &line_len)) {
g_assert (line_start);
g_assert (line_start >= data0);
g_assert (line_start < &data0[strbuf.len]);
g_assert (!memchr (line_start, '\0', line_len));
g_ptr_array_add (strv2, g_strndup (line_start, line_len));
}
g_assert (data_len == 0);
if (data0)
g_assert (data == &data0[strbuf.len]);
else
g_assert (!data);
g_assert (_nm_utils_strv_cmp_n ((const char *const*) strv->pdata, strv->len, (const char *const*) strv2->pdata, strv2->len) == 0);
}
}
/*****************************************************************************/
NMTST_DEFINE ();
int main (int argc, char **argv)
@ -794,6 +897,7 @@ int main (int argc, char **argv)
g_test_add_func ("/general/test_string_table_lookup", test_string_table_lookup);
g_test_add_func ("/general/test_nm_utils_get_next_realloc_size", test_nm_utils_get_next_realloc_size);
g_test_add_func ("/general/test_nm_str_buf", test_nm_str_buf);
g_test_add_func ("/general/test_nm_utils_parse_next_line", test_nm_utils_parse_next_line);
return g_test_run ();
}