report: add ability to upload reports over http (#41440)
This commit is contained in:
@@ -45,6 +45,7 @@ wrap=(
|
||||
/usr/lib/systemd/tests/testdata/TEST-74-AUX-UTILS.units/proxy-echo.py
|
||||
/usr/libexec/polkit-1/polkitd
|
||||
/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-imds.py
|
||||
/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py
|
||||
agetty
|
||||
btrfs
|
||||
capsh
|
||||
|
||||
+53
-23
@@ -7,10 +7,6 @@
|
||||
#include "iovec-wrapper.h"
|
||||
#include "string-util.h"
|
||||
|
||||
struct iovec_wrapper *iovw_new(void) {
|
||||
return new0(struct iovec_wrapper, 1);
|
||||
}
|
||||
|
||||
void iovw_done(struct iovec_wrapper *iovw) {
|
||||
assert(iovw);
|
||||
|
||||
@@ -27,22 +23,6 @@ void iovw_done_free(struct iovec_wrapper *iovw) {
|
||||
iovw_done(iovw);
|
||||
}
|
||||
|
||||
struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw) {
|
||||
if (!iovw)
|
||||
return NULL;
|
||||
|
||||
iovw_done_free(iovw);
|
||||
return mfree(iovw);
|
||||
}
|
||||
|
||||
struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw) {
|
||||
if (!iovw)
|
||||
return NULL;
|
||||
|
||||
iovw_done(iovw);
|
||||
return mfree(iovw);
|
||||
}
|
||||
|
||||
int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
|
||||
assert(iovw);
|
||||
|
||||
@@ -61,6 +41,28 @@ int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len) {
|
||||
if (len == 0)
|
||||
return 0;
|
||||
|
||||
void *c = memdup(data, len);
|
||||
if (!c)
|
||||
return -ENOMEM;
|
||||
|
||||
return iovw_put(iovw, c, len);
|
||||
}
|
||||
|
||||
int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) {
|
||||
/* Move data into iovw or free on error */
|
||||
int r;
|
||||
|
||||
r = iovw_put(iovw, data, len);
|
||||
if (r < 0)
|
||||
free(data);
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
int iovw_put_string_field_full(struct iovec_wrapper *iovw, bool replace, const char *field, const char *value) {
|
||||
_cleanup_free_ char *x = NULL;
|
||||
int r;
|
||||
@@ -118,13 +120,12 @@ void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new) {
|
||||
}
|
||||
|
||||
size_t iovw_size(const struct iovec_wrapper *iovw) {
|
||||
if (!iovw)
|
||||
return 0;
|
||||
assert(iovw);
|
||||
|
||||
return iovec_total_size(iovw->iovec, iovw->count);
|
||||
}
|
||||
|
||||
int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source) {
|
||||
int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source) {
|
||||
size_t original_count;
|
||||
int r;
|
||||
|
||||
@@ -160,3 +161,32 @@ rollback:
|
||||
target->count = original_count;
|
||||
return r;
|
||||
}
|
||||
|
||||
char* iovw_to_cstring(const struct iovec_wrapper *iovw) {
|
||||
size_t size;
|
||||
char *p, *ans;
|
||||
|
||||
assert(iovw);
|
||||
|
||||
/* Squish a series of iovecs into a C string. Embedded NULs are not allowed.
|
||||
* The caller is expected to filter them out when populating the data. */
|
||||
|
||||
size = iovw_size(iovw);
|
||||
if (size == SIZE_MAX)
|
||||
return NULL; /* Prevent theoretical overflow */
|
||||
size ++;
|
||||
|
||||
p = ans = new(char, size);
|
||||
if (!ans)
|
||||
return NULL;
|
||||
|
||||
FOREACH_ARRAY(iovec, iovw->iovec, iovw->count) {
|
||||
assert(!memchr(iovec->iov_base, 0, iovec->iov_len));
|
||||
|
||||
p = mempcpy(p, iovec->iov_base, iovec->iov_len);
|
||||
}
|
||||
|
||||
*p = '\0';
|
||||
|
||||
return ans;
|
||||
}
|
||||
|
||||
@@ -8,26 +8,12 @@ struct iovec_wrapper {
|
||||
size_t count;
|
||||
};
|
||||
|
||||
struct iovec_wrapper *iovw_new(void);
|
||||
struct iovec_wrapper *iovw_free(struct iovec_wrapper *iovw);
|
||||
struct iovec_wrapper *iovw_free_free(struct iovec_wrapper *iovw);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(struct iovec_wrapper*, iovw_free_free);
|
||||
|
||||
void iovw_done_free(struct iovec_wrapper *iovw);
|
||||
void iovw_done(struct iovec_wrapper *iovw);
|
||||
|
||||
int iovw_put(struct iovec_wrapper *iovw, void *data, size_t len);
|
||||
static inline int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len) {
|
||||
/* Move data into iovw or free on error */
|
||||
int r;
|
||||
|
||||
r = iovw_put(iovw, data, len);
|
||||
if (r < 0)
|
||||
free(data);
|
||||
|
||||
return r;
|
||||
}
|
||||
int iovw_append(struct iovec_wrapper *iovw, const void *data, size_t len);
|
||||
int iovw_consume(struct iovec_wrapper *iovw, void *data, size_t len);
|
||||
|
||||
static inline bool iovw_isempty(const struct iovec_wrapper *iovw) {
|
||||
return !iovw || iovw->count == 0;
|
||||
@@ -46,4 +32,5 @@ int iovw_put_string_fieldf_full(struct iovec_wrapper *iovw, bool replace, const
|
||||
int iovw_put_string_field_free(struct iovec_wrapper *iovw, const char *field, char *value);
|
||||
void iovw_rebase(struct iovec_wrapper *iovw, void *old, void *new);
|
||||
size_t iovw_size(const struct iovec_wrapper *iovw);
|
||||
int iovw_append(struct iovec_wrapper *target, const struct iovec_wrapper *source);
|
||||
int iovw_append_iovw(struct iovec_wrapper *target, const struct iovec_wrapper *source);
|
||||
char* iovw_to_cstring(const struct iovec_wrapper *iovw);
|
||||
|
||||
@@ -50,7 +50,7 @@ int coredump_backtrace(int argc, char *argv[]) {
|
||||
} else {
|
||||
/* The imported iovecs are not supposed to be freed by us so let's copy and merge them at the
|
||||
* end of the array. */
|
||||
r = iovw_append(&context.iovw, &importer.iovw);
|
||||
r = iovw_append_iovw(&context.iovw, &importer.iovw);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
|
||||
+7
-18
@@ -18,6 +18,7 @@
|
||||
#include "chase.h"
|
||||
#include "copy.h"
|
||||
#include "creds-util.h"
|
||||
#include "curl-util.h"
|
||||
#include "device-private.h"
|
||||
#include "dns-rr.h"
|
||||
#include "errno-util.h"
|
||||
@@ -53,8 +54,6 @@
|
||||
#include "web-util.h"
|
||||
#include "xattr-util.h"
|
||||
|
||||
#include "../import/curl-util.h"
|
||||
|
||||
/* This implements a client to the AWS' and Azure's "Instance Metadata Service", as well as GCP's "VM
|
||||
* Metadata", i.e.:
|
||||
*
|
||||
@@ -1113,20 +1112,14 @@ static int context_acquire_data(Context *c) {
|
||||
if (!token_header)
|
||||
return context_log_oom(c);
|
||||
|
||||
struct curl_slist *n = curl_slist_append(c->request_header_data, token_header);
|
||||
if (!n)
|
||||
return context_log_oom(c);
|
||||
|
||||
c->request_header_data = n;
|
||||
r = curl_append_to_header(&c->request_header_data, STRV_MAKE(token_header));
|
||||
if (r < 0)
|
||||
return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m");
|
||||
}
|
||||
|
||||
STRV_FOREACH(i, arg_extra_header) {
|
||||
struct curl_slist *n = curl_slist_append(c->request_header_data, *i);
|
||||
if (!n)
|
||||
return context_log_oom(c);
|
||||
|
||||
c->request_header_data = n;
|
||||
}
|
||||
r = curl_append_to_header(&c->request_header_data, arg_extra_header);
|
||||
if (r < 0)
|
||||
return context_log_errno(c, LOG_ERR, r, "Failed to create curl header: %m");
|
||||
|
||||
if (c->request_header_data)
|
||||
if (curl_easy_setopt(c->curl_data, CURLOPT_HTTPHEADER, c->request_header_data) != CURLE_OK)
|
||||
@@ -2250,10 +2243,6 @@ static bool http_header_name_valid(const char *a) {
|
||||
return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && !strchr(a, ':');
|
||||
}
|
||||
|
||||
static bool http_header_valid(const char *a) {
|
||||
return a && ascii_is_valid(a) && !string_has_cc(a, /* ok= */ NULL) && strchr(a, ':');
|
||||
}
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
int r;
|
||||
|
||||
|
||||
@@ -11,8 +11,9 @@ executables += [
|
||||
'sources' : files(
|
||||
'imdsd.c',
|
||||
'imds-util.c'
|
||||
) + import_curl_util_c,
|
||||
'dependencies' : [ libcurl ],
|
||||
),
|
||||
'link_with' : [libcurlutil_static, libshared],
|
||||
'dependencies' : [libcurl],
|
||||
},
|
||||
libexec_template + {
|
||||
'name' : 'systemd-imds',
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
import_curl_util_c = files('curl-util.c')
|
||||
|
||||
if conf.get('ENABLE_IMPORTD') != 1
|
||||
subdir_done()
|
||||
endif
|
||||
@@ -35,8 +33,9 @@ executables += [
|
||||
'pull-oci.c',
|
||||
'pull-raw.c',
|
||||
'pull-tar.c',
|
||||
) + import_curl_util_c,
|
||||
),
|
||||
'objects' : ['systemd-importd'],
|
||||
'link_with' : [libcurlutil_static, libshared],
|
||||
'dependencies' : common_deps + [
|
||||
libopenssl,
|
||||
],
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "alloc-util.h"
|
||||
#include "build.h"
|
||||
#include "conf-parser.h"
|
||||
#include "curl-util.h"
|
||||
#include "daemon-util.h"
|
||||
#include "env-file.h"
|
||||
#include "extract-word.h"
|
||||
@@ -81,20 +82,6 @@ static void close_fd_input(Uploader *u);
|
||||
|
||||
#define STATE_FILE "/var/lib/systemd/journal-upload/state"
|
||||
|
||||
#define easy_setopt(curl, opt, value, level, cmd) \
|
||||
do { \
|
||||
code = curl_easy_setopt(curl, opt, value); \
|
||||
if (code) { \
|
||||
log_full(level, \
|
||||
"curl_easy_setopt " #opt " failed: %s", \
|
||||
curl_easy_strerror(code)); \
|
||||
cmd; \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL);
|
||||
|
||||
static size_t output_callback(char *buf,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
@@ -194,8 +181,6 @@ int start_upload(Uploader *u,
|
||||
size_t nmemb,
|
||||
void *userdata),
|
||||
void *data) {
|
||||
CURLcode code;
|
||||
|
||||
assert(u);
|
||||
assert(input_callback);
|
||||
|
||||
@@ -262,64 +247,63 @@ int start_upload(Uploader *u,
|
||||
"Call to curl_easy_init failed.");
|
||||
|
||||
/* If configured, set a timeout for the curl operation. */
|
||||
if (arg_network_timeout_usec != USEC_INFINITY)
|
||||
easy_setopt(curl, CURLOPT_TIMEOUT,
|
||||
(long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC),
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (arg_network_timeout_usec != USEC_INFINITY &&
|
||||
!easy_setopt(curl, LOG_ERR, CURLOPT_TIMEOUT,
|
||||
(long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC)))
|
||||
return -EXFULL;
|
||||
|
||||
/* tell it to POST to the URL */
|
||||
easy_setopt(curl, CURLOPT_POST, 1L,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_POST, 1L))
|
||||
return -EXFULL;
|
||||
|
||||
easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_ERRORBUFFER, u->error))
|
||||
return -EXFULL;
|
||||
|
||||
/* set where to write to */
|
||||
easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEFUNCTION, output_callback))
|
||||
return -EXFULL;
|
||||
|
||||
easy_setopt(curl, CURLOPT_WRITEDATA, data,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEDATA, data))
|
||||
return -EXFULL;
|
||||
|
||||
/* set where to read from */
|
||||
easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_READFUNCTION, input_callback))
|
||||
return -EXFULL;
|
||||
|
||||
easy_setopt(curl, CURLOPT_READDATA, data,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_READDATA, data))
|
||||
return -EXFULL;
|
||||
|
||||
/* use our special own mime type and chunked transfer */
|
||||
easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_HTTPHEADER, u->header))
|
||||
return -EXFULL;
|
||||
|
||||
if (DEBUG_LOGGING)
|
||||
/* enable verbose for easier tracing */
|
||||
easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
|
||||
(void) easy_setopt(curl, LOG_WARNING, CURLOPT_VERBOSE, 1L);
|
||||
|
||||
easy_setopt(curl, CURLOPT_USERAGENT,
|
||||
"systemd-journal-upload " GIT_VERSION,
|
||||
LOG_WARNING, );
|
||||
(void) easy_setopt(curl, LOG_WARNING,
|
||||
CURLOPT_USERAGENT, "systemd-journal-upload " GIT_VERSION);
|
||||
|
||||
if (!streq_ptr(arg_key, "-") && (arg_key || startswith(u->url, "https://"))) {
|
||||
easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
|
||||
LOG_ERR, return -EXFULL);
|
||||
easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE))
|
||||
return -EXFULL;
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE))
|
||||
return -EXFULL;
|
||||
}
|
||||
|
||||
if (STRPTR_IN_SET(arg_trust, "-", "all")) {
|
||||
log_info("Server certificate verification disabled.");
|
||||
easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L,
|
||||
LOG_ERR, return -EUCLEAN);
|
||||
easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L,
|
||||
LOG_ERR, return -EUCLEAN);
|
||||
} else if (arg_trust || startswith(u->url, "https://"))
|
||||
easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
|
||||
LOG_ERR, return -EXFULL);
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYPEER, 0L))
|
||||
return -EUCLEAN;
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYHOST, 0L))
|
||||
return -EUCLEAN;
|
||||
} else if (arg_trust || startswith(u->url, "https://")) {
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE))
|
||||
return -EXFULL;
|
||||
}
|
||||
|
||||
if (arg_key || arg_trust)
|
||||
easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
|
||||
LOG_WARNING, );
|
||||
if (startswith(u->url, "https://"))
|
||||
(void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
|
||||
|
||||
u->easy = TAKE_PTR(curl);
|
||||
} else {
|
||||
@@ -330,11 +314,8 @@ int start_upload(Uploader *u,
|
||||
}
|
||||
|
||||
/* upload to this place */
|
||||
code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
|
||||
if (code)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EXFULL),
|
||||
"curl_easy_setopt CURLOPT_URL failed: %s",
|
||||
curl_easy_strerror(code));
|
||||
if (!easy_setopt(u->easy, LOG_ERR, CURLOPT_URL, u->url))
|
||||
return -EXFULL;
|
||||
|
||||
u->uploading = true;
|
||||
|
||||
@@ -567,15 +548,15 @@ static int update_content_encoding_header(Uploader *u, const CompressionConfig *
|
||||
break;
|
||||
}
|
||||
|
||||
if (update_header) {
|
||||
CURLcode code;
|
||||
easy_setopt(u->easy, CURLOPT_HTTPHEADER, u->header, LOG_WARNING, return -EXFULL);
|
||||
}
|
||||
if (update_header &&
|
||||
!easy_setopt(u->easy, LOG_WARNING, CURLOPT_HTTPHEADER, u->header))
|
||||
return -EXFULL;
|
||||
|
||||
u->compression = cc;
|
||||
|
||||
if (cc)
|
||||
log_debug("Using compression algorithm %s with compression level %i.", compression_to_string(cc->algorithm), cc->level);
|
||||
log_debug("Using compression algorithm %s with compression level %i.",
|
||||
compression_to_string(cc->algorithm), cc->level);
|
||||
else
|
||||
log_debug("Disabled compression algorithm.");
|
||||
return 0;
|
||||
@@ -644,12 +625,13 @@ static int perform_upload(Uploader *u) {
|
||||
code = curl_easy_perform(u->easy);
|
||||
if (code) {
|
||||
if (u->error[0])
|
||||
log_error("Upload to %s failed: %.*s",
|
||||
u->url, (int) sizeof(u->error), u->error);
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Upload to %s failed: %.*s",
|
||||
u->url, (int) sizeof(u->error), u->error);
|
||||
else
|
||||
log_error("Upload to %s failed: %s",
|
||||
u->url, curl_easy_strerror(code));
|
||||
return -EIO;
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Upload to %s failed: %s",
|
||||
u->url, curl_easy_strerror(code));
|
||||
}
|
||||
|
||||
code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
|
||||
|
||||
@@ -4,7 +4,12 @@ executables += [
|
||||
libexec_template + {
|
||||
'name' : 'systemd-report',
|
||||
'public' : true,
|
||||
'sources' : files('report.c'),
|
||||
'sources' : files(
|
||||
'report.c',
|
||||
'report-upload.c',
|
||||
),
|
||||
'link_with' : [libcurlutil_static, libshared],
|
||||
'dependencies' : [libcurl],
|
||||
},
|
||||
|
||||
libexec_template + {
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include "sd-json.h"
|
||||
|
||||
#include "log.h"
|
||||
#include "report.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "utf8.h"
|
||||
#include "version.h"
|
||||
|
||||
#if HAVE_LIBCURL
|
||||
#include "curl-util.h"
|
||||
#include <curl/easy.h> /* Sadly this fails if ordered first. */
|
||||
|
||||
#define SERVER_ANSWER_MAX (1*1024*1024u)
|
||||
|
||||
static size_t output_callback(char *buf,
|
||||
size_t size,
|
||||
size_t nmemb,
|
||||
void *userp) {
|
||||
|
||||
Context *context = ASSERT_PTR(userp);
|
||||
int r;
|
||||
|
||||
assert(size == 1); /* The docs say that this is always true. */
|
||||
|
||||
log_debug("Got an answer from the server (%zu bytes)", nmemb);
|
||||
|
||||
if (nmemb != 0) {
|
||||
size_t new_size = size_add(iovw_size(&context->upload_answer), nmemb);
|
||||
|
||||
if (new_size > SERVER_ANSWER_MAX) {
|
||||
log_warning("Server answer too long (%zu > %u), refusing.", new_size, SERVER_ANSWER_MAX);
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (memchr(buf, 0, nmemb)) {
|
||||
log_warning("Server answer contains an embedded NUL, refusing.");
|
||||
return 0;
|
||||
}
|
||||
|
||||
r = iovw_append(&context->upload_answer, buf, nmemb);
|
||||
if (r < 0) {
|
||||
log_warning("Failed to store server answer (%zu bytes): out of memory", nmemb);
|
||||
return 0; /* Returning < nmemb signals failure */
|
||||
}
|
||||
}
|
||||
|
||||
return nmemb;
|
||||
}
|
||||
|
||||
static int build_json_report(Context *context, sd_json_variant **ret) {
|
||||
/* Convert the variant array to a JSON report. */
|
||||
|
||||
assert(context);
|
||||
assert(ret);
|
||||
|
||||
usec_t ts = now(CLOCK_REALTIME);
|
||||
int r;
|
||||
|
||||
const char *ident;
|
||||
if (IN_SET(context->action, ACTION_LIST_METRICS, ACTION_DESCRIBE_METRICS))
|
||||
ident = "metrics";
|
||||
else if (IN_SET(context->action, ACTION_LIST_FACTS, ACTION_DESCRIBE_FACTS))
|
||||
ident = "facts";
|
||||
else
|
||||
assert_not_reached();
|
||||
|
||||
r = sd_json_buildo(ret,
|
||||
SD_JSON_BUILD_PAIR("timestamp",
|
||||
SD_JSON_BUILD_STRING(FORMAT_TIMESTAMP_STYLE(ts, TIMESTAMP_UTC))),
|
||||
SD_JSON_BUILD_PAIR(ident,
|
||||
SD_JSON_BUILD_VARIANT_ARRAY(context->metrics, context->n_metrics)));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to build JSON data: %m");
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
int upload_collected(Context *context) {
|
||||
#if HAVE_LIBCURL
|
||||
_cleanup_(curl_slist_free_allp) struct curl_slist *header = NULL;
|
||||
char error[CURL_ERROR_SIZE] = {};
|
||||
_cleanup_free_ char *json = NULL;
|
||||
int r;
|
||||
|
||||
{
|
||||
/* Convert our variant array to a JSON report.
|
||||
* We won't need the JSON structure again, so free it quickly. */
|
||||
|
||||
_cleanup_(sd_json_variant_unrefp) sd_json_variant *vl = NULL;
|
||||
r = build_json_report(context, &vl);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = sd_json_variant_format(vl, /* flags= */ 0, &json);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to format JSON data: %m");
|
||||
}
|
||||
|
||||
r = curl_append_to_header(&header,
|
||||
STRV_MAKE("Content-Type: application/json",
|
||||
"Accept: application/json"));
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create curl header: %m");
|
||||
|
||||
r = curl_append_to_header(&header, arg_extra_headers);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to create curl header: %m");
|
||||
|
||||
_cleanup_(curl_easy_cleanupp) CURL *curl = curl_easy_init();
|
||||
if (!curl)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(ENOSR),
|
||||
"Call to curl_easy_init failed.");
|
||||
|
||||
/* If configured, set a timeout for the curl operation. */
|
||||
if (arg_network_timeout_usec != USEC_INFINITY &&
|
||||
!easy_setopt(curl, LOG_ERR, CURLOPT_TIMEOUT,
|
||||
(long) DIV_ROUND_UP(arg_network_timeout_usec, USEC_PER_SEC)))
|
||||
return -EXFULL;
|
||||
|
||||
/* Tell it to POST to the URL */
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_POST, 1L))
|
||||
return -EXFULL;
|
||||
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_ERRORBUFFER, error))
|
||||
return -EXFULL;
|
||||
|
||||
/* Where to write to */
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEFUNCTION, output_callback))
|
||||
return -EXFULL;
|
||||
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_WRITEDATA, context))
|
||||
return -EXFULL;
|
||||
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_HTTPHEADER, header))
|
||||
return -EXFULL;
|
||||
|
||||
if (DEBUG_LOGGING)
|
||||
/* enable verbose for easier tracing */
|
||||
(void) easy_setopt(curl, LOG_WARNING, CURLOPT_VERBOSE, 1L);
|
||||
|
||||
(void) easy_setopt(curl, LOG_WARNING,
|
||||
CURLOPT_USERAGENT, "systemd-report " GIT_VERSION);
|
||||
|
||||
if (!streq_ptr(arg_key, "-") && (arg_key || startswith(arg_url, "https://"))) {
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLKEY, arg_key ?: REPORT_PRIV_KEY_FILE))
|
||||
return -EXFULL;
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSLCERT, arg_cert ?: REPORT_CERT_FILE))
|
||||
return -EXFULL;
|
||||
}
|
||||
|
||||
if (STRPTR_IN_SET(arg_trust, "-", "all")) {
|
||||
log_info("Server certificate verification disabled.");
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYPEER, 0L))
|
||||
return -EUCLEAN;
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_SSL_VERIFYHOST, 0L))
|
||||
return -EUCLEAN;
|
||||
} else if (arg_trust || startswith(arg_url, "https://")) {
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_CAINFO, arg_trust ?: REPORT_TRUST_FILE))
|
||||
return -EXFULL;
|
||||
}
|
||||
|
||||
if (startswith(arg_url, "https://"))
|
||||
(void) easy_setopt(curl, LOG_WARNING, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1_2);
|
||||
|
||||
/* Upload to this place */
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_URL, arg_url))
|
||||
return -EXFULL;
|
||||
|
||||
if (!easy_setopt(curl, LOG_ERR, CURLOPT_POSTFIELDS, json))
|
||||
return -EXFULL;
|
||||
|
||||
CURLcode code = curl_easy_perform(curl);
|
||||
if (code != CURLE_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Upload to %s failed: %s", arg_url,
|
||||
empty_to_null(&error[0]) ?: curl_easy_strerror(code));
|
||||
|
||||
long status;
|
||||
code = curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status);
|
||||
if (code != CURLE_OK)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN),
|
||||
"Failed to retrieve response code: %s",
|
||||
curl_easy_strerror(code));
|
||||
|
||||
_cleanup_free_ char *ans = iovw_to_cstring(&context->upload_answer);
|
||||
if (!ans)
|
||||
return log_oom();
|
||||
|
||||
if (!utf8_is_valid(ans))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN),
|
||||
"Upload to %s failed with code %ld and an invalid UTF-8 answer.",
|
||||
arg_url, status);
|
||||
|
||||
if (status >= 300)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Upload to %s failed with code %ld: %s",
|
||||
arg_url, status, strna(ans));
|
||||
if (status < 200)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EIO),
|
||||
"Upload to %s finished with unexpected code %ld: %s",
|
||||
arg_url, status, strna(ans));
|
||||
log_info("Upload to %s finished successfully with code %ld: %s",
|
||||
arg_url, status, strna(ans));
|
||||
return 0;
|
||||
#else
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP),
|
||||
"Compiled without libcurl.");
|
||||
#endif
|
||||
}
|
||||
+136
-109
@@ -1,7 +1,5 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <getopt.h>
|
||||
|
||||
#include "sd-event.h"
|
||||
#include "sd-varlink.h"
|
||||
|
||||
@@ -13,10 +11,12 @@
|
||||
#include "format-table.h"
|
||||
#include "log.h"
|
||||
#include "main-func.h"
|
||||
#include "options.h"
|
||||
#include "parse-argument.h"
|
||||
#include "path-lookup.h"
|
||||
#include "pretty-print.h"
|
||||
#include "recurse-dir.h"
|
||||
#include "report.h"
|
||||
#include "runtime-scope.h"
|
||||
#include "set.h"
|
||||
#include "sort-util.h"
|
||||
@@ -26,6 +26,7 @@
|
||||
#include "time-util.h"
|
||||
#include "varlink-idl-util.h"
|
||||
#include "verbs.h"
|
||||
#include "web-util.h"
|
||||
|
||||
#define METRICS_OR_FACTS_MAX 4096U
|
||||
#define METRICS_OR_FACTS_LINKS_MAX 128U
|
||||
@@ -36,27 +37,19 @@ static bool arg_legend = true;
|
||||
static RuntimeScope arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF|SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
|
||||
static char **arg_matches = NULL;
|
||||
char *arg_url = NULL;
|
||||
char *arg_key = NULL;
|
||||
char *arg_cert = NULL;
|
||||
char *arg_trust = NULL;
|
||||
char **arg_extra_headers = NULL;
|
||||
usec_t arg_network_timeout_usec = TIMEOUT_USEC;
|
||||
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_matches, strv_freep);
|
||||
|
||||
typedef enum Action {
|
||||
ACTION_LIST_METRICS,
|
||||
ACTION_DESCRIBE_METRICS,
|
||||
ACTION_LIST_FACTS,
|
||||
ACTION_DESCRIBE_FACTS,
|
||||
_ACTION_MAX,
|
||||
_ACTION_INVALID = -EINVAL,
|
||||
} Action;
|
||||
|
||||
/* The structure for collected "metrics" or "facts". The fields
|
||||
* are prefixed with just "metrics" for brevity. */
|
||||
typedef struct Context {
|
||||
Action action;
|
||||
sd_event *event;
|
||||
Set *link_infos;
|
||||
sd_json_variant **metrics; /* Collected metrics or facts for sorting */
|
||||
size_t n_metrics, n_skipped_metrics, n_invalid_metrics;
|
||||
} Context;
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_url, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_key, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_cert, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_trust, freep);
|
||||
STATIC_DESTRUCTOR_REGISTER(arg_extra_headers, strv_freep);
|
||||
|
||||
typedef struct LinkInfo {
|
||||
Context *context;
|
||||
@@ -77,9 +70,12 @@ static void context_done(Context *context) {
|
||||
if (!context)
|
||||
return;
|
||||
|
||||
set_free(context->link_infos);
|
||||
context->event = sd_event_unref(context->event);
|
||||
context->link_infos = set_free(context->link_infos);
|
||||
sd_json_variant_unref_many(context->metrics, context->n_metrics);
|
||||
sd_event_unref(context->event);
|
||||
context->metrics = NULL;
|
||||
context->n_metrics = 0;
|
||||
iovw_done_free(&context->upload_answer);
|
||||
}
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC(LinkInfo*, link_info_free);
|
||||
@@ -570,6 +566,7 @@ static int output_collected(Context *context) {
|
||||
}
|
||||
|
||||
_cleanup_(table_unrefp) Table *table = NULL;
|
||||
|
||||
switch(context->action) {
|
||||
|
||||
case ACTION_LIST_METRICS:
|
||||
@@ -708,6 +705,10 @@ static int readdir_sources(char **ret_directory, DirectoryEntries **ret) {
|
||||
return m > 0;
|
||||
}
|
||||
|
||||
VERB_FULL(verb_metrics, "metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_METRICS,
|
||||
"Acquire list of metrics and their values");
|
||||
VERB_FULL(verb_metrics, "describe-metrics", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_METRICS,
|
||||
"Describe available metrics");
|
||||
static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
Action action = data;
|
||||
int r;
|
||||
@@ -768,7 +769,10 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run event loop: %m");
|
||||
|
||||
r = output_collected(&context);
|
||||
if (arg_url)
|
||||
r = upload_collected(&context);
|
||||
else
|
||||
r = output_collected(&context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -788,6 +792,10 @@ static int verb_metrics(int argc, char *argv[], uintptr_t data, void *userdata)
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB_FULL(verb_facts, "facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_LIST_FACTS,
|
||||
"Acquire list of facts and their values");
|
||||
VERB_FULL(verb_facts, "describe-facts", "[MATCH…]", VERB_ANY, VERB_ANY, 0, ACTION_DESCRIBE_FACTS,
|
||||
"Describe available facts");
|
||||
static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
Action action = data;
|
||||
int r;
|
||||
@@ -848,7 +856,10 @@ static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to run event loop: %m");
|
||||
|
||||
r = output_collected(&context);
|
||||
if (arg_url)
|
||||
r = upload_collected(&context);
|
||||
else
|
||||
r = output_collected(&context);
|
||||
if (r < 0)
|
||||
return r;
|
||||
}
|
||||
@@ -868,6 +879,7 @@ static int verb_facts(int argc, char *argv[], uintptr_t data, void *userdata) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
VERB_NOARG(verb_list_sources, "list-sources", "Show list of known metrics sources");
|
||||
static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
int r;
|
||||
|
||||
@@ -925,143 +937,158 @@ static int verb_list_sources(int argc, char *argv[], uintptr_t _data, void *user
|
||||
|
||||
static int help(void) {
|
||||
_cleanup_free_ char *link = NULL;
|
||||
_cleanup_(table_unrefp) Table *verbs = NULL, *options = NULL;
|
||||
int r;
|
||||
|
||||
r = terminal_urlify_man("systemd-report", "1", &link);
|
||||
if (r < 0)
|
||||
return log_oom();
|
||||
|
||||
printf("%1$s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%5$sAcquire metrics and facts from local sources.%6$s\n"
|
||||
"\n%3$sCommands:%4$s\n"
|
||||
" metrics [MATCH...] Acquire list of metrics and their values\n"
|
||||
" describe-metrics [MATCH...]\n"
|
||||
" Describe available metrics\n"
|
||||
" facts [MATCH...] Acquire list of facts and their values\n"
|
||||
" describe-facts [MATCH...]\n"
|
||||
" Describe available facts\n"
|
||||
" list-sources Show list of known metrics sources\n"
|
||||
"\n%3$sOptions:%4$s\n"
|
||||
" -h --help Show this help\n"
|
||||
" --version Show package version\n"
|
||||
" --no-pager Do not pipe output into a pager\n"
|
||||
" --no-legend Do not show the headers and footers\n"
|
||||
" --user Connect to user service manager\n"
|
||||
" --system Connect to system service manager (default)\n"
|
||||
" --json=pretty|short\n"
|
||||
" Configure JSON output\n"
|
||||
" -j Equivalent to --json=pretty (on TTY) or --json=short\n"
|
||||
" (otherwise)\n"
|
||||
"\nSee the %2$s for details.\n",
|
||||
program_invocation_short_name,
|
||||
link,
|
||||
ansi_underline(),
|
||||
ansi_normal(),
|
||||
ansi_highlight(),
|
||||
ansi_normal());
|
||||
r = verbs_get_help_table(&verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
r = option_parser_get_help_table(&options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
(void) table_sync_column_widths(0, options, verbs);
|
||||
|
||||
printf("%s [OPTIONS...] COMMAND ...\n"
|
||||
"\n%sAcquire metrics and facts from local sources.%s\n"
|
||||
"\n%sCommands:%s\n",
|
||||
program_invocation_short_name,
|
||||
ansi_highlight(),
|
||||
ansi_normal(),
|
||||
ansi_underline(),
|
||||
ansi_normal());
|
||||
r = table_print_or_warn(verbs);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\n%sOptions:%s\n",
|
||||
ansi_underline(),
|
||||
ansi_normal());
|
||||
r = table_print_or_warn(options);
|
||||
if (r < 0)
|
||||
return r;
|
||||
|
||||
printf("\nSee the %s for details.\n", link);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int verb_help(int argc, char *argv[], uintptr_t _data, void *userdata) {
|
||||
return help();
|
||||
}
|
||||
VERB_COMMON_HELP_HIDDEN(help);
|
||||
|
||||
static int parse_argv(int argc, char *argv[]) {
|
||||
enum {
|
||||
ARG_VERSION = 0x100,
|
||||
ARG_NO_PAGER,
|
||||
ARG_NO_LEGEND,
|
||||
ARG_USER,
|
||||
ARG_SYSTEM,
|
||||
ARG_JSON,
|
||||
};
|
||||
|
||||
static const struct option options[] = {
|
||||
{ "help", no_argument, NULL, 'h' },
|
||||
{ "version", no_argument, NULL, ARG_VERSION },
|
||||
{ "no-pager", no_argument, NULL, ARG_NO_PAGER },
|
||||
{ "no-legend", no_argument, NULL, ARG_NO_LEGEND },
|
||||
{ "user", no_argument, NULL, ARG_USER },
|
||||
{ "system", no_argument, NULL, ARG_SYSTEM },
|
||||
{ "json", required_argument, NULL, ARG_JSON },
|
||||
{}
|
||||
};
|
||||
|
||||
int c, r;
|
||||
static int parse_argv(int argc, char *argv[], char ***ret_args) {
|
||||
int r;
|
||||
|
||||
assert(argc >= 0);
|
||||
assert(argv);
|
||||
|
||||
while ((c = getopt_long(argc, argv, "hj", options, NULL)) >= 0)
|
||||
OptionParser state = { argc, argv };
|
||||
const char *arg;
|
||||
|
||||
FOREACH_OPTION(&state, c, &arg, /* on_error= */ return c)
|
||||
switch (c) {
|
||||
case 'h':
|
||||
OPTION_COMMON_HELP:
|
||||
return help();
|
||||
|
||||
case ARG_VERSION:
|
||||
OPTION_COMMON_VERSION:
|
||||
return version();
|
||||
|
||||
case ARG_NO_PAGER:
|
||||
OPTION_COMMON_NO_PAGER:
|
||||
arg_pager_flags |= PAGER_DISABLE;
|
||||
break;
|
||||
|
||||
case ARG_NO_LEGEND:
|
||||
OPTION_COMMON_NO_LEGEND:
|
||||
arg_legend = false;
|
||||
break;
|
||||
|
||||
case ARG_USER:
|
||||
OPTION_LONG("user", NULL, "Connect to user service manager"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_USER;
|
||||
break;
|
||||
|
||||
case ARG_SYSTEM:
|
||||
OPTION_LONG("system", NULL, "Connect to system service manager (default)"):
|
||||
arg_runtime_scope = RUNTIME_SCOPE_SYSTEM;
|
||||
break;
|
||||
|
||||
case ARG_JSON:
|
||||
r = parse_json_argument(optarg, &arg_json_format_flags);
|
||||
OPTION_COMMON_JSON:
|
||||
r = parse_json_argument(arg, &arg_json_format_flags);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
break;
|
||||
|
||||
case 'j':
|
||||
OPTION_COMMON_LOWERCASE_J:
|
||||
arg_json_format_flags = SD_JSON_FORMAT_PRETTY_AUTO|SD_JSON_FORMAT_COLOR_AUTO;
|
||||
break;
|
||||
|
||||
case '?':
|
||||
return -EINVAL;
|
||||
OPTION_LONG("url", "URL",
|
||||
"Upload to this address"):
|
||||
r = free_and_strdup_warn(&arg_url, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
default:
|
||||
assert_not_reached();
|
||||
OPTION_LONG("key", "FILENAME",
|
||||
"Specify key in PEM format (default: \"" REPORT_PRIV_KEY_FILE "\")"):
|
||||
r = free_and_strdup_warn(&arg_key, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("cert", "FILENAME",
|
||||
"Specify certificate in PEM format (default: \"" REPORT_CERT_FILE "\")"):
|
||||
r = free_and_strdup_warn(&arg_cert, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("trust", "FILENAME|all",
|
||||
"Specify CA certificate or disable checking (default: \"" REPORT_TRUST_FILE "\")"):
|
||||
r = free_and_strdup_warn(&arg_trust, arg);
|
||||
if (r < 0)
|
||||
return r;
|
||||
break;
|
||||
|
||||
OPTION_LONG("network-timeout", "SEC", "Specify timeout for network upload operation"):
|
||||
r = parse_sec(arg, &arg_network_timeout_usec);
|
||||
if (r < 0)
|
||||
return log_error_errno(r, "Failed to parse --network-timeout value: %s", arg);
|
||||
break;
|
||||
|
||||
OPTION_LONG("extra-header", "NAME: VALUE",
|
||||
"Inject additional header into the upload request"):
|
||||
if (isempty(arg)) {
|
||||
arg_extra_headers = strv_free(arg_extra_headers);
|
||||
break;
|
||||
}
|
||||
|
||||
if (!http_header_valid(arg))
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid HTTP header: %s", arg);
|
||||
|
||||
if (strv_extend(&arg_extra_headers, arg) < 0)
|
||||
return log_oom();
|
||||
break;
|
||||
}
|
||||
|
||||
if ((arg_url || arg_key || arg_cert || arg_trust || arg_extra_headers) && !HAVE_LIBCURL)
|
||||
return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Compiled without libcurl.");
|
||||
|
||||
*ret_args = option_parser_get_args(&state);
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int report_main(int argc, char *argv[]) {
|
||||
static const Verb verbs[] = {
|
||||
{ "help", VERB_ANY, 1, 0, verb_help },
|
||||
{ "metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_LIST_METRICS },
|
||||
{ "describe-metrics", VERB_ANY, VERB_ANY, 0, verb_metrics, ACTION_DESCRIBE_METRICS },
|
||||
{ "facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_LIST_FACTS },
|
||||
{ "describe-facts", VERB_ANY, VERB_ANY, 0, verb_facts, ACTION_DESCRIBE_FACTS },
|
||||
{ "list-sources", VERB_ANY, 1, 0, verb_list_sources },
|
||||
{}
|
||||
};
|
||||
|
||||
return dispatch_verb(argc, argv, verbs, NULL);
|
||||
}
|
||||
|
||||
static int run(int argc, char *argv[]) {
|
||||
char **args = NULL;
|
||||
int r;
|
||||
|
||||
log_setup();
|
||||
|
||||
r = parse_argv(argc, argv);
|
||||
r = parse_argv(argc, argv, &args);
|
||||
if (r <= 0)
|
||||
return r;
|
||||
|
||||
return report_main(argc, argv);
|
||||
return dispatch_verb_with_args(args, NULL);
|
||||
}
|
||||
|
||||
DEFINE_MAIN_FUNCTION(run);
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
#pragma once
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
#include "iovec-wrapper.h"
|
||||
|
||||
#define REPORT_PRIV_KEY_FILE CERTIFICATE_ROOT "/private/systemd-report.pem"
|
||||
#define REPORT_CERT_FILE CERTIFICATE_ROOT "/certs/systemd-report.pem"
|
||||
#define REPORT_TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
|
||||
|
||||
extern char *arg_url, *arg_key, *arg_cert, *arg_trust;
|
||||
extern char **arg_extra_headers;
|
||||
extern usec_t arg_network_timeout_usec;
|
||||
|
||||
typedef enum Action {
|
||||
ACTION_LIST_METRICS,
|
||||
ACTION_DESCRIBE_METRICS,
|
||||
ACTION_LIST_FACTS,
|
||||
ACTION_DESCRIBE_FACTS,
|
||||
_ACTION_MAX,
|
||||
_ACTION_INVALID = -EINVAL,
|
||||
} Action;
|
||||
|
||||
/* The structure for collected "metrics" or "facts". The fields
|
||||
* are prefixed with just "metrics" for brevity. */
|
||||
typedef struct Context {
|
||||
Action action;
|
||||
sd_event *event;
|
||||
Set *link_infos;
|
||||
sd_json_variant **metrics; /* Collected metrics or facts for sorting */
|
||||
size_t n_metrics, n_skipped_metrics, n_invalid_metrics;
|
||||
struct iovec_wrapper upload_answer;
|
||||
} Context;
|
||||
|
||||
int upload_collected(Context *context);
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "hashmap.h"
|
||||
#include "log.h"
|
||||
#include "string-util.h"
|
||||
#include "strv.h"
|
||||
#include "time-util.h"
|
||||
#include "version.h"
|
||||
|
||||
@@ -407,3 +408,19 @@ int curl_parse_http_time(const char *t, usec_t *ret) {
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int curl_append_to_header(struct curl_slist **list, char **headers) {
|
||||
/* This function leaves 'list' modified on partial failure.
|
||||
* Input/output param list may point to NULL. */
|
||||
|
||||
assert(list);
|
||||
|
||||
STRV_FOREACH(h, headers) {
|
||||
struct curl_slist *l = curl_slist_append(*list, *h);
|
||||
if (!l)
|
||||
return -ENOMEM;
|
||||
*list = l;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
@@ -5,6 +5,19 @@
|
||||
|
||||
#include "shared-forward.h"
|
||||
|
||||
#define easy_setopt(curl, log_level, opt, value) ({ \
|
||||
CURLcode code = curl_easy_setopt(ASSERT_PTR(curl), opt, value); \
|
||||
if (code) \
|
||||
log_full(log_level, \
|
||||
"curl_easy_setopt %s failed: %s", \
|
||||
#opt, curl_easy_strerror(code)); \
|
||||
code == CURLE_OK; \
|
||||
})
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURLM*, curl_multi_cleanup, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL);
|
||||
|
||||
typedef struct CurlGlue CurlGlue;
|
||||
|
||||
typedef struct CurlGlue {
|
||||
@@ -30,7 +43,4 @@ void curl_glue_remove_and_free(CurlGlue *g, CURL *c);
|
||||
struct curl_slist *curl_slist_new(const char *first, ...) _sentinel_;
|
||||
int curl_header_strdup(const void *contents, size_t sz, const char *field, char **value);
|
||||
int curl_parse_http_time(const char *t, usec_t *ret);
|
||||
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL*, curl_easy_cleanup, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURLM*, curl_multi_cleanup, NULL);
|
||||
DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist*, curl_slist_free_all, NULL);
|
||||
int curl_append_to_header(struct curl_slist **list, char **headers);
|
||||
+1
-1
@@ -123,7 +123,7 @@ static int fact_build_send(FactFamilyContext *context, const char *object, sd_js
|
||||
return sd_varlink_replybo(context->link,
|
||||
SD_JSON_BUILD_PAIR_STRING("name", context->fact_family->name),
|
||||
JSON_BUILD_PAIR_STRING_NON_EMPTY("object", object),
|
||||
SD_JSON_BUILD_PAIR("value", SD_JSON_BUILD_VARIANT(value)));
|
||||
SD_JSON_BUILD_PAIR_VARIANT("value", value));
|
||||
}
|
||||
|
||||
int fact_build_send_string(FactFamilyContext *context, const char *object, const char *value) {
|
||||
|
||||
@@ -459,3 +459,18 @@ libshared_fdisk = static_library(
|
||||
userspace],
|
||||
c_args : ['-fvisibility=default'],
|
||||
build_by_default : false)
|
||||
|
||||
# A small shared file that is linked into a few places.
|
||||
# It is not part of libshared because this code needs libcurl and
|
||||
# we don't want to link libshared to libcurl.
|
||||
if conf.get('HAVE_LIBCURL') == 1
|
||||
libcurlutil_static = static_library(
|
||||
'curl-util',
|
||||
'curl-util.c',
|
||||
implicit_include_directories : false,
|
||||
dependencies : [userspace, libcurl],
|
||||
include_directories : includes,
|
||||
build_by_default : false)
|
||||
else
|
||||
libcurlutil_static = []
|
||||
endif
|
||||
|
||||
+20
-13
@@ -5,19 +5,6 @@
|
||||
#include "utf8.h"
|
||||
#include "web-util.h"
|
||||
|
||||
bool http_etag_is_valid(const char *etag) {
|
||||
if (isempty(etag))
|
||||
return false;
|
||||
|
||||
if (!endswith(etag, "\""))
|
||||
return false;
|
||||
|
||||
if (!STARTSWITH_SET(etag, "\"", "W/\""))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool http_url_is_valid(const char *url) {
|
||||
const char *p;
|
||||
|
||||
@@ -62,3 +49,23 @@ bool documentation_url_is_valid(const char *url) {
|
||||
|
||||
return ascii_is_valid(p);
|
||||
}
|
||||
|
||||
bool http_header_valid(const char *header) {
|
||||
return header &&
|
||||
ascii_is_valid(header) &&
|
||||
!string_has_cc(header, /* ok= */ NULL) &&
|
||||
strchr(header, ':');
|
||||
}
|
||||
|
||||
bool http_etag_is_valid(const char *etag) {
|
||||
if (isempty(etag))
|
||||
return false;
|
||||
|
||||
if (!endswith(etag, "\""))
|
||||
return false;
|
||||
|
||||
if (!STARTSWITH_SET(etag, "\"", "W/\""))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
bool http_url_is_valid(const char *url) _pure_;
|
||||
bool file_url_is_valid(const char *url) _pure_;
|
||||
|
||||
bool documentation_url_is_valid(const char *url) _pure_;
|
||||
|
||||
bool http_etag_is_valid(const char *etag);
|
||||
bool http_header_valid(const char *header) _pure_;
|
||||
bool http_etag_is_valid(const char *etag) _pure_;
|
||||
|
||||
@@ -130,6 +130,7 @@ simple_tests += files(
|
||||
'test-install-root.c',
|
||||
'test-io-util.c',
|
||||
'test-iovec-util.c',
|
||||
'test-iovec-wrapper.c',
|
||||
'test-journal-importer.c',
|
||||
'test-kbd-util.c',
|
||||
'test-label.c',
|
||||
|
||||
@@ -0,0 +1,245 @@
|
||||
/* SPDX-License-Identifier: LGPL-2.1-or-later */
|
||||
|
||||
#include <sys/uio.h>
|
||||
|
||||
#include "alloc-util.h"
|
||||
#include "iovec-wrapper.h"
|
||||
#include "tests.h"
|
||||
|
||||
TEST(iovw_put) {
|
||||
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
|
||||
|
||||
/* Zero-length insertions are no-ops and do not touch the data pointer */
|
||||
ASSERT_OK_ZERO(iovw_put(&iovw, NULL, 0));
|
||||
ASSERT_OK_ZERO(iovw_put(&iovw, (char*) "foo", 0));
|
||||
ASSERT_EQ(iovw.count, 0U);
|
||||
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3));
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "barbar", 6));
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "q", 1));
|
||||
ASSERT_EQ(iovw.count, 3U);
|
||||
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, 3U);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "foo", 3), 0);
|
||||
ASSERT_EQ(iovw.iovec[1].iov_len, 6U);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "barbar", 6), 0);
|
||||
ASSERT_EQ(iovw.iovec[2].iov_len, 1U);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "q", 1), 0);
|
||||
}
|
||||
|
||||
TEST(iovw_append) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
|
||||
/* iovw_append copies the data; the wrapper owns the copies. */
|
||||
char buf[4] = { 'o', 'n', 'e', '\0' };
|
||||
ASSERT_OK(iovw_append(&iovw, buf, 3));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, 3U);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0);
|
||||
|
||||
/* Insert with a NUL */
|
||||
ASSERT_OK_ZERO(iovw_append(&iovw, buf, 4));
|
||||
ASSERT_EQ(iovw.count, 2U);
|
||||
ASSERT_EQ(iovw.iovec[1].iov_len, 4U);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "one\0", 4), 0);
|
||||
|
||||
/* Mutating the caller's buffer does not affect what's stored */
|
||||
memset(buf, 'X', sizeof buf);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "one", 3), 0);
|
||||
}
|
||||
|
||||
TEST(iovw_consume) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
|
||||
char *p = strdup("consumed");
|
||||
ASSERT_NOT_NULL(p);
|
||||
ASSERT_OK(iovw_consume(&iovw, p, strlen(p)));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
/* iovw_consume moves ownership in place, no copy */
|
||||
ASSERT_PTR_EQ(iovw.iovec[0].iov_base, p);
|
||||
|
||||
/* Zero-length: iovw_put returns 0 without adding anything, and does not free the payload.
|
||||
* Confirm by strdup'ing something and explicitly freeing it afterwards. */
|
||||
_cleanup_free_ char *q = strdup("");
|
||||
ASSERT_NOT_NULL(q);
|
||||
ASSERT_OK_ZERO(iovw_consume(&iovw, q, 0));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
}
|
||||
|
||||
TEST(iovw_isempty) {
|
||||
ASSERT_TRUE(iovw_isempty(NULL));
|
||||
|
||||
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
|
||||
ASSERT_TRUE(iovw_isempty(&iovw));
|
||||
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "x", 1));
|
||||
ASSERT_FALSE(iovw_isempty(&iovw));
|
||||
}
|
||||
|
||||
TEST(iovw_put_string_field) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
|
||||
ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "bar"));
|
||||
ASSERT_OK(iovw_put_string_field(&iovw, "BAZ=", "quux"));
|
||||
ASSERT_EQ(iovw.count, 2U);
|
||||
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0);
|
||||
ASSERT_EQ(iovw.iovec[1].iov_len, strlen("BAZ=quux"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, "BAZ=quux", strlen("BAZ=quux")), 0);
|
||||
|
||||
/* Non-replacing put: a second FOO= just appends rather than replacing */
|
||||
ASSERT_OK(iovw_put_string_field(&iovw, "FOO=", "second"));
|
||||
ASSERT_EQ(iovw.count, 3U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("FOO=bar"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "FOO=bar", strlen("FOO=bar")), 0);
|
||||
ASSERT_EQ(iovw.iovec[2].iov_len, strlen("FOO=second"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, "FOO=second", strlen("FOO=second")), 0);
|
||||
}
|
||||
|
||||
TEST(iovw_replace_string_field) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
|
||||
/* If the field does not exist yet, replace acts like put */
|
||||
ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "1"));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=1"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=1", strlen("A=1")), 0);
|
||||
|
||||
/* Replacing an existing field updates it in place */
|
||||
ASSERT_OK(iovw_replace_string_field(&iovw, "A=", "twentytwo"));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("A=twentytwo"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "A=twentytwo", strlen("A=twentytwo")), 0);
|
||||
|
||||
/* Distinct field still appends */
|
||||
ASSERT_OK(iovw_replace_string_field(&iovw, "B=", "x"));
|
||||
ASSERT_EQ(iovw.count, 2U);
|
||||
}
|
||||
|
||||
TEST(iovw_put_string_fieldf) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
|
||||
ASSERT_OK(iovw_put_string_fieldf(&iovw, "N=", "%d-%s", 42, "answer"));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=42-answer"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=42-answer", strlen("N=42-answer")), 0);
|
||||
|
||||
/* Replacing variant */
|
||||
ASSERT_OK(iovw_replace_string_fieldf(&iovw, "N=", "%d", 7));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("N=7"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "N=7", strlen("N=7")), 0);
|
||||
}
|
||||
|
||||
TEST(iovw_put_string_field_free) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper iovw = {};
|
||||
|
||||
/* iovw_put_string_field_free takes ownership of the value string (frees it on return). */
|
||||
char *v = strdup("hello");
|
||||
ASSERT_NOT_NULL(v);
|
||||
ASSERT_OK(iovw_put_string_field_free(&iovw, "K=", v));
|
||||
ASSERT_EQ(iovw.count, 1U);
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, strlen("K=hello"));
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, "K=hello", strlen("K=hello")), 0);
|
||||
}
|
||||
|
||||
TEST(iovw_rebase) {
|
||||
/* iovw_rebase shifts all iov_base pointers from an old base to a new base. Fabricate a
|
||||
* stand-in "old base" and "new base" and a wrapper with offsets pointing into the old
|
||||
* base, then verify they get rewritten to point into the new base. */
|
||||
|
||||
uint8_t old_base[64] = {}, new_base[64] = {};
|
||||
for (size_t i = 0; i < sizeof old_base; i++) {
|
||||
old_base[i] = (uint8_t) i;
|
||||
new_base[i] = (uint8_t) (100 + i);
|
||||
}
|
||||
|
||||
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
|
||||
|
||||
ASSERT_OK(iovw_put(&iovw, old_base + 0, 4));
|
||||
ASSERT_OK(iovw_put(&iovw, old_base + 10, 2));
|
||||
ASSERT_OK(iovw_put(&iovw, old_base + 30, 8));
|
||||
ASSERT_EQ(iovw.count, 3U);
|
||||
|
||||
iovw_rebase(&iovw, old_base, new_base);
|
||||
|
||||
ASSERT_PTR_EQ(iovw.iovec[0].iov_base, new_base + 0);
|
||||
ASSERT_PTR_EQ(iovw.iovec[1].iov_base, new_base + 10);
|
||||
ASSERT_PTR_EQ(iovw.iovec[2].iov_base, new_base + 30);
|
||||
|
||||
/* Lengths are preserved */
|
||||
ASSERT_EQ(iovw.iovec[0].iov_len, 4U);
|
||||
ASSERT_EQ(iovw.iovec[1].iov_len, 2U);
|
||||
ASSERT_EQ(iovw.iovec[2].iov_len, 8U);
|
||||
|
||||
/* And the contents through the new base match what we staged there */
|
||||
ASSERT_EQ(memcmp(iovw.iovec[0].iov_base, new_base + 0, 4), 0);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[1].iov_base, new_base + 10, 2), 0);
|
||||
ASSERT_EQ(memcmp(iovw.iovec[2].iov_base, new_base + 30, 8), 0);
|
||||
}
|
||||
|
||||
TEST(iovw_size) {
|
||||
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
|
||||
ASSERT_EQ(iovw_size(&iovw), 0U);
|
||||
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "abcd", 4));
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "efghij", 6));
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "kl", 2));
|
||||
ASSERT_EQ(iovw_size(&iovw), 12U);
|
||||
}
|
||||
|
||||
TEST(iovw_append_iovw) {
|
||||
_cleanup_(iovw_done_free) struct iovec_wrapper target = {};
|
||||
_cleanup_(iovw_done) struct iovec_wrapper source = {};
|
||||
|
||||
/* Appending an empty/NULL source is a no-op */
|
||||
ASSERT_OK_ZERO(iovw_append_iovw(&target, NULL));
|
||||
ASSERT_OK_ZERO(iovw_append_iovw(&target, &source));
|
||||
ASSERT_EQ(target.count, 0U);
|
||||
|
||||
ASSERT_OK(iovw_put(&source, (char*) "one", 3));
|
||||
ASSERT_OK(iovw_put(&source, (char*) "twotwo", 6));
|
||||
ASSERT_EQ(source.count, 2U);
|
||||
|
||||
/* Pre-seed target with one entry to check that append adds on top rather than replacing */
|
||||
char *seed = strdup("zero");
|
||||
ASSERT_NOT_NULL(seed);
|
||||
ASSERT_OK(iovw_put(&target, seed, strlen(seed)));
|
||||
|
||||
ASSERT_OK(iovw_append_iovw(&target, &source));
|
||||
ASSERT_EQ(target.count, 3U);
|
||||
|
||||
/* Appended entries must be fresh copies, not aliases of the source entries */
|
||||
ASSERT_TRUE(target.iovec[1].iov_base != source.iovec[0].iov_base);
|
||||
ASSERT_TRUE(target.iovec[2].iov_base != source.iovec[1].iov_base);
|
||||
|
||||
ASSERT_EQ(target.iovec[1].iov_len, 3U);
|
||||
ASSERT_EQ(memcmp(target.iovec[1].iov_base, "one", 3), 0);
|
||||
ASSERT_EQ(target.iovec[2].iov_len, 6U);
|
||||
ASSERT_EQ(memcmp(target.iovec[2].iov_base, "twotwo", 6), 0);
|
||||
|
||||
/* Source is unchanged */
|
||||
ASSERT_EQ(source.count, 2U);
|
||||
}
|
||||
|
||||
TEST(iovw_to_cstring) {
|
||||
_cleanup_(iovw_done) struct iovec_wrapper iovw = {};
|
||||
_cleanup_free_ char *s;
|
||||
|
||||
/* Empty wrapper → empty string */
|
||||
s = iovw_to_cstring(&iovw);
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_STREQ(s, "");
|
||||
s = mfree(s);
|
||||
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "foo", 3));
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "/", 1));
|
||||
ASSERT_OK(iovw_put(&iovw, (char*) "bar", 3));
|
||||
|
||||
s = iovw_to_cstring(&iovw);
|
||||
ASSERT_NOT_NULL(s);
|
||||
ASSERT_STREQ(s, "foo/bar");
|
||||
}
|
||||
|
||||
DEFINE_TEST_MAIN(LOG_INFO);
|
||||
+3
-4
@@ -261,13 +261,12 @@ static void test_log_context(void) {
|
||||
IOVEC_MAKE_STRING("ABC=def"),
|
||||
IOVEC_MAKE_STRING("GHI=jkl"),
|
||||
};
|
||||
_cleanup_free_ struct iovec_wrapper *iovw = NULL;
|
||||
ASSERT_NOT_NULL(iovw = iovw_new());
|
||||
ASSERT_OK(iovw_consume(iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1));
|
||||
struct iovec_wrapper iovw = {};
|
||||
ASSERT_OK(iovw_consume(&iovw, strdup("MNO=pqr"), STRLEN("MNO=pqr") + 1));
|
||||
|
||||
LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov));
|
||||
LOG_CONTEXT_PUSH_IOV(iov, ELEMENTSOF(iov));
|
||||
LOG_CONTEXT_CONSUME_IOV(iovw->iovec, iovw->count);
|
||||
LOG_CONTEXT_CONSUME_IOV(iovw.iovec, iovw.count);
|
||||
LOG_CONTEXT_PUSH("STU=vwx");
|
||||
|
||||
ASSERT_EQ(log_context_num_contexts(), 3U);
|
||||
|
||||
@@ -617,6 +617,28 @@ TEST(split_pair) {
|
||||
ASSERT_STREQ(b, "=");
|
||||
}
|
||||
|
||||
TEST(empty_to_null) {
|
||||
const char *s = "asdf", *n = NULL, *e = "";
|
||||
char *t = (char*) "asdf";
|
||||
const char p[] = "asdf";
|
||||
char q[] = "asdf";
|
||||
|
||||
/* empty_to_null cannot be used with constant strings, e.g.
|
||||
* empty_to_null("") fails with 'error: cast specifies array type'. */
|
||||
|
||||
ASSERT_NULL(empty_to_null(NULL));
|
||||
ASSERT_NULL(empty_to_null(n));
|
||||
ASSERT_NULL(empty_to_null(e));
|
||||
ASSERT_STREQ(empty_to_null(s), "asdf");
|
||||
ASSERT_NULL(empty_to_null(s + 4));
|
||||
ASSERT_STREQ(empty_to_null(t), "asdf");
|
||||
ASSERT_NULL(empty_to_null(t + 4));
|
||||
ASSERT_STREQ(empty_to_null(&p[0]), "asdf");
|
||||
ASSERT_NULL(empty_to_null(&p[0] + 4));
|
||||
ASSERT_STREQ(empty_to_null(&q[0]), "asdf");
|
||||
ASSERT_NULL(empty_to_null(&q[0] + 4));
|
||||
}
|
||||
|
||||
TEST(first_word) {
|
||||
assert_se(first_word("Hello", ""));
|
||||
assert_se(first_word("Hello", "Hello"));
|
||||
|
||||
+70
@@ -0,0 +1,70 @@
|
||||
#!/usr/bin/python3
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
import argparse, json, os, socket, ssl
|
||||
from http.server import BaseHTTPRequestHandler, HTTPServer
|
||||
|
||||
def sd_notify(state: str) -> bool:
|
||||
notify_socket = os.environ.get("NOTIFY_SOCKET")
|
||||
if not notify_socket:
|
||||
return False
|
||||
if notify_socket.startswith("@"):
|
||||
notify_socket = "\0" + notify_socket[1:]
|
||||
try:
|
||||
with socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM) as sock:
|
||||
sock.sendto(state.encode(), notify_socket)
|
||||
except OSError:
|
||||
return False
|
||||
return True
|
||||
|
||||
class Handler(BaseHTTPRequestHandler):
|
||||
def do_POST(self):
|
||||
length = int(self.headers.get("Content-Length", 0))
|
||||
body = self.rfile.read(length)
|
||||
|
||||
# Check optional attribute
|
||||
if auth := self.headers.get("Authorization"):
|
||||
print(f"Authorization: {auth}")
|
||||
|
||||
# Validate JSON structure
|
||||
try:
|
||||
data = json.loads(body)
|
||||
except json.JSONDecodeError:
|
||||
self.send_error(400, "Invalid JSON")
|
||||
return
|
||||
|
||||
print(f"JSON: {s if len(s := str(data)) < 80 else s[:40] + '…' + s[-40:]}")
|
||||
|
||||
if "metrics" not in data and "facts" not in data:
|
||||
self.send_error(400, "Missing 'metrics' or 'facts' field")
|
||||
return
|
||||
|
||||
response = json.dumps({"status": "ok"}).encode()
|
||||
self.send_response(200)
|
||||
self.send_header("Content-Type", "application/json")
|
||||
self.send_header("Content-Length", len(response))
|
||||
self.end_headers()
|
||||
self.wfile.write(response)
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
print(f"{self.address_string()} - {fmt % args}")
|
||||
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument("--port", type=int, default=8089)
|
||||
parser.add_argument("--cert", help="TLS certificate file")
|
||||
parser.add_argument("--key", help="TLS private key file")
|
||||
args = parser.parse_args()
|
||||
|
||||
server = HTTPServer(("", args.port), Handler)
|
||||
if args.cert and args.key:
|
||||
ctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
ctx.load_cert_chain(args.cert, args.key)
|
||||
server.socket = ctx.wrap_socket(server.socket, server_side=True)
|
||||
print(f"Serving on https://localhost:{args.port}/")
|
||||
else:
|
||||
print(f"Serving on http://localhost:{args.port}/")
|
||||
try:
|
||||
sd_notify("READY=1")
|
||||
server.serve_forever()
|
||||
except KeyboardInterrupt:
|
||||
print("\nStopped.")
|
||||
@@ -65,3 +65,32 @@ systemctl start systemd-report-basic.socket
|
||||
# Test facts via direct Varlink call on existing socket
|
||||
varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.List {}
|
||||
varlinkctl --more call /run/systemd/report/io.systemd.Basic io.systemd.Facts.Describe {}
|
||||
|
||||
# Test HTTP upload (plain http)
|
||||
FAKE_SERVER=/usr/lib/systemd/tests/integration-tests/TEST-74-AUX-UTILS/TEST-74-AUX-UTILS.units/fake-report-server.py
|
||||
CERTDIR=$(mktemp -d)
|
||||
|
||||
at_exit() {
|
||||
set +e
|
||||
systemctl stop fake-report-server fake-report-server-tls
|
||||
rm -rf "$CERTDIR"
|
||||
}
|
||||
trap at_exit EXIT
|
||||
|
||||
systemd-run -p Type=notify --unit=fake-report-server "$FAKE_SERVER"
|
||||
systemctl status fake-report-server
|
||||
|
||||
"$REPORT" metrics --url=http://localhost:8089/
|
||||
"$REPORT" facts --url=http://localhost:8089/
|
||||
|
||||
# Test HTTPS upload with generated TLS certificates
|
||||
openssl req -x509 -newkey rsa:2048 -keyout "$CERTDIR/server.key" -out "$CERTDIR/server.crt" \
|
||||
-days 1 -nodes -subj "/CN=localhost" 2>/dev/null
|
||||
|
||||
systemd-run -p Type=notify --unit=fake-report-server-tls \
|
||||
"$FAKE_SERVER" --cert="$CERTDIR/server.crt" --key="$CERTDIR/server.key" --port=8090
|
||||
systemctl status fake-report-server-tls
|
||||
|
||||
"$REPORT" metrics --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt"
|
||||
"$REPORT" facts --url=https://localhost:8090/ --key=- --trust="$CERTDIR/server.crt" \
|
||||
--extra-header='Authorization: Bearer magic string'
|
||||
|
||||
Reference in New Issue
Block a user