Compare commits

..
37 Commits
Author SHA1 Message Date
Alex Jaramillo da45642759 Release v1.14.3
This release contains:

* Fixes an issue where if the telemetry client bundle is added after
  installation required directories were not created until the OS is
  restarted. This change makes sure that all the required directories
  are created before the telemd service is started.

* Adds an option to telemctl to indicate if telemd.service is active.

* Removes telemetry client temporary directories on telemetry opt-out.

* Add a base64 encoding routine to the nica library, this library can
  be used by probes to encode binaries for http transport to telemetry
  server.

* Fixes configuration parameter on hprobe.

* Adds a probe to report Boot Error Region Table if this record exists
  for more information about BERT, consult ACPI spec
2018-01-08 18:02:28 +00:00
avjarami ed1c85a481 Fixing config parameter in probe
hprobe had an error in the short forme of the CLI config_file parameter,
this change fixes the error.
2018-01-05 13:33:25 -08:00
avjarami b4ac36f7a6 Addresing review comments 2018-01-04 17:44:59 -08:00
avjarami 2f07fd658c BERT record probe
Telemetry probe to report Boot Error Region Table if such entry exists
in /sys/firmware/acpi/tables/
2018-01-04 17:44:59 -08:00
avjarami 4b10dbec86 Base 64 encoding implementation
Providing a minimal function to encode data in base64 to allow the
use of binary data as printable (http transportable) characters.
2018-01-02 13:44:31 -08:00
avjarami bc3c7b6a4d Improvements to telemctl
* Using variable instead of literal

* Simplifying filter that removes folders listed in configuration file
2017-12-07 16:01:01 -08:00
avjarami 15d32841f0 Remove work dirs on opt-out
This change removes working directories when the user opt-out of
reporting platform telemetry.
2017-12-07 16:01:01 -08:00
avjarami e3178e1624 Adding is-active option to telemctl
This change adds a simple way to determine if telemd.service is active.
2017-12-07 16:01:01 -08:00
avjarami 7de4351f16 Create working directries on start
telemd will not start or function properly if expected directories are
not present (i.e. spool dir), this change calls systemd-tempfiles to
create dirs listed listed in telemetrics-dirs.conf
2017-12-07 16:01:01 -08:00
Patrick McCarty 73adb8981a Fix compiler warning
`readv` is declared in <sys/uio.h>

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-10-27 10:26:44 -07:00
Alex Jaramillo 5ead074c89 Release v1.14.2
This release adds a unit file to make sure that telemetry daemon will
restart when swupd performs an update.
2017-10-16 18:42:52 +00:00
Alex Jaramillo 80292b5bf8 Restart on update
Because telemd does not restart when an update is installed the daemon
could get out of sync with probes if a new version of a probe requires a
new version of telemd. This change will trigger a restart when an update
takes place.
2017-10-11 17:44:32 -07:00
Patrick McCarty 1517ff4c18 Fix build issues when -DDEBUG is enabled
The call to "fprint()" is a typo and should be "fprintf()".

Also, the "error->message" string is an artifact from the time when
telemetrics-client used glib. Now it uses libnica, which propogates
errors differently. Simply remove the format specifier for now.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-09-29 15:12:03 -07:00
Patrick McCarty 85772cb5c5 Release v1.14.1
This release bumps the record format version to account for the new
headers added in the v1.14.0 release.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-09-15 10:54:18 -07:00
Patrick McCarty d75fa6fda2 Link to telemetrics-backend repo from the README
Because the telemetrics-backend repo is now hosted on Github, link to it
from the client README, since it is part of a complete telemetrics
solution.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-09-15 10:53:09 -07:00
avjarami 6c7c5be2e2 Bumping record format version to 3 2017-09-15 10:52:19 -07:00
Patrick McCarty 913cbb1c1e Release v1.14.0
This release adds three new hardware metadata fields to include in
records created by libtelemetry: board_name, cpu_model, and
bios_version.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-09-08 07:32:13 -07:00
Patrick McCarty d3ce2f435c Run uncrustify on the source
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-09-08 07:28:42 -07:00
avjarami d0a3e6bafa Including additional host metadata in headers
Additional headers added: board_name, cpu_model, and bios_version.

    * Board name is a combination of board_name and board_vendor from
    dmi file system.

    * CPU model is read from /proc/cpuinfo.

    * BIOS version is taken from dmi file system.
2017-09-08 07:26:01 -07:00
Patrick McCarty a9b16868f5 Release v1.13.0
This release introduces some changes in behavior for the crash probe and
journal probe:

* The crash probe now ensures core files are kept whenever backtraces
  are scrubbed from telemetry records, or when errors occur during runtime
  operation. This improves the developer experience when running ones own
  programs installed at arbitrary locations on the filesystem and also
  avoids data loss; either the crash backtrace is sent via telemetry, or
  when unable to send the backtrace, the core file is kept.

* The journal probe, by default, now only sends telemetry for journal
  log messages indicating systemd service failures. The previous behavior
  of sending any log messages above a certain log level is now wrapped
  behind the "opt-in-no-privacy-filters" config setting.

Bug fixes include:

* Fixing a segfault crash in the configuration code that occurred
  whenever an incomplete configuration file was encountered.

* Fixing a performance issue in 'telem-record-gen' to avoid extraneous
  strlen() calls in the classification input validation code.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-07-05 12:02:21 -07:00
Patrick McCarty cbbc579917 journal probe: conditionally enable log level filters
Because log messages from services contain arbitrary data, and sometimes
this data is privacy sensitive, only enable the log level filters when
the privacy filter override is in effect.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-30 14:33:31 -07:00
Patrick McCarty 7080839167 configuration: make config errors more actionable
The current behavior of the configuration code is to print a generic
error message and exit if configuration parsing fails.

To clarify the reason for exiting, and thus make the error message more
actionable, print an additional error message that describes which key
from the config is affected and the type of value it accepts.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-30 14:31:53 -07:00
Patrick McCarty 0d07e76955 configuration: fix regression in parsing that results in segfault
When a key from a INI file is missing, libnica returns NULL, so we need
to properly check for a non-NULL return value before passing it to
strdup().

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-30 14:31:53 -07:00
Patrick McCarty 1c8af05cb9 telem-record-gen: avoid unnecessary strlen() calls
Since strlen(opt_class) is already called before this for loop, there is
no need to call it again. It also avoids repeatedly calling strlen() as
part of the for-loop conditional check.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-27 21:02:44 -07:00
Patrick McCarty cb4b951a1a crash probe: use a helper function for string prefix checks
Since the code repeats the same logic in several places, it is
convenient to split the routine into a helper function, startswith().

This also fixes a bug with the earlier port to libnica: in the
in_clr_build() function, strstr() was chosen as the replacement function
instead of strncmp(), which means a match will be found in any part of
the string. However, a match should only be found when it is a prefix.
To fix, switch the strstr() in that function to use startswith() as
well.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-27 21:02:44 -07:00
Patrick McCarty bb018a158f Document condition under which core files are removed
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-26 17:14:50 -07:00
Patrick McCarty 577484cdb2 crash probe: also keep core files when errors occur
During the processing of a core file or sending telemetry, any numbers
of errors may occur. Make sure the core file is not unlinked under these
conditions.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-26 17:14:50 -07:00
Patrick McCarty 049c9a35b9 crash probe: keep core files when backtraces are scrubbed
When the path filters for privacy are in effect, backtraces are scrubbed
from records, and this results in the core files being unlinked.

However, this is not friendly behavior for the developer. A common
situation that triggers the path filters is installing custom binaries
on the system (say, under /usr/local/bin or /opt/bin) for testing
purposes. To better enable developers to debug their programs, having
the core files available to process with gdb is very valuable.

Eventually, I would like to add an opt-in to keep all core files, but
I'll wait until the configuration code is refactored.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-26 17:14:50 -07:00
Patrick McCarty 56a6a4777b Release v1.12.4
This release includes a change that results in lower memory consumption
for telemd when the service is idle.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-23 11:16:07 -07:00
Patrick McCarty d2ecf76c70 Init/deinit libcurl global environment for each POST
When the daemon is sitting idle, we are seeing libcurl consume around
2MB of memory, which had been previously allocated on-the-fly for its
global environment.

To have more control over this memory consumption, making sure the
daemon uses as little memory as possible when doing no work, explicitly
allocate the libcurl global environment before each POST and deallocate
it afterwards.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-20 19:33:06 -07:00
Patrick McCarty f2aa671513 Release v1.12.3
This release fixes a buffer overrun issue in the klogscanner.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-20 15:41:00 -07:00
Patrick McCarty a1fcf14c3d Fix max number of bytes read by SYSLOG_ACTION_READ
In case the value returned by SYSLOG_ACTION_SIZE_BUFFER exceeds MAX_BUF,
the buffer later allocated has size MAX_BUF, but SYSLOG_ACTION_READ may
read more than MAX_BUF bytes to store in the buffer, leading to a buffer
overrun.

Fix the issue by ensuring SYSLOG_ACTION_READ reads at most "buflen"
bytes, the size of the allocated buffer.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-20 12:22:34 -07:00
Patrick McCarty ad8209115f Enforce buffer length cap on first loop iteration
Since loopsize is initialized to 0, the conditional statement where
loopsize increments led to the buffer size check not being executed the
first time through the loop.

Simply remove the counter variable to fix the issue.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-20 12:22:34 -07:00
Patrick McCarty fdb2fa0463 Print specific error for SYSLOG_ACTION_SIZE_BUFFER query failure
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-20 12:22:34 -07:00
Patrick McCarty 54b66b2820 Release v1.12.2
This release completes the remaining work to remove the glib dependency
in favor of libnica.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-09 14:05:04 -07:00
Patrick McCarty bccd669c91 Remove buildtime checks for glib; update README
Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-09 14:01:51 -07:00
Patrick McCarty 01fd01a49a pstore probe: switch to use libnica hashmaps
This completes the glib dependency removal work. The next commit will
clean up the makefiles and documentation.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2017-06-09 14:01:51 -07:00
31 changed files with 954 additions and 129 deletions
-1
View File
@@ -16,7 +16,6 @@ addons:
sources:
- ubuntu-toolchain-r-test
packages:
- libglib2.0-dev
- libcurl4-gnutls-dev
- valgrind
- autoconf
+4 -2
View File
@@ -15,6 +15,10 @@ component includes:
(not included in this source tree), or spools the records on disk in case
it's unable to successfully deliver them.
A telemetrics server implementation that works with this component is available
from
[clearlinux/telemetrics-backend](https://github.com/clearlinux/telemetrics-backend).
Build dependencies
---------------------
@@ -23,8 +27,6 @@ Build dependencies
- libcurl
- glib
- elfutils, which provides libelf and libdwfl libraries..
- (optional) libsystemd, for syslog-style logging to the systemd journal, and
+6 -2
View File
@@ -2,7 +2,7 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([telemetrics-client], [1.12.1], [https://clearlinux.org/])
AC_INIT([telemetrics-client], [1.14.3], [https://clearlinux.org/])
AC_CONFIG_AUX_DIR([build-aux])
AM_INIT_AUTOMAKE([1.14 -Wall -Werror -Wno-extra-portability foreign subdir-objects])
AM_SILENT_RULES([yes])
@@ -25,7 +25,6 @@ AC_PROG_MAKE_SET
# check >= 0.9.12 is required for TAP output
PKG_CHECK_MODULES([CHECK], [check >= 0.9.12])
PKG_CHECK_MODULES([CURL], [libcurl])
PKG_CHECK_MODULES([GLIB], [glib-2.0])
AC_CHECK_LIB([elf], [elf_begin], [have_elflib=yes], [AC_MSG_ERROR([Unable to find libelf from elfutils])])
AC_CHECK_LIB([dw], [dwfl_begin], [have_dwlib=yes], [AC_MSG_ERROR([Unable to find libdw from elfutils])])
AS_IF([test "x$have_elflib" = "xyes" -a "x$have_dwlib" = "xyes"],
@@ -99,6 +98,11 @@ AC_ARG_WITH([systemdsysctldir], AS_HELP_STRING([--with-systemdsysctldir=DIR],
test -z "${sysctlpath}" && sysctlpath=/usr/lib/sysctl.d
AC_SUBST(SYSTEMD_SYSCTLDIR, [${sysctlpath}])
AC_ARG_WITH([systemdsystemctldir], AS_HELP_STRING([--with-systemdsystemctldir=DIR],
[path to systemd systemctl dir @<:@default=/usr/bin@:>@]), [systemctlpath=${withval}])
test -z "${systemctlpath}" && systemctlpath=/usr/bin
AC_SUBST(SYSTEMD_SYSTEMCTLDIR, [${systemctlpath}])
# systemd.pc gives the /etc conf dir, so we can't use PKG_CONFIG for this one
AC_ARG_WITH([systemdsystemconfdir], AS_HELP_STRING([--with-systemdsystemconfdir=DIR],
[path to systemd system conf dir @<:@default=/usr/lib/systemd/system.conf.d@:>@]), [confpath=${withval}],
+30 -6
View File
@@ -18,6 +18,17 @@ declare -a SERVICES=(
SCRIPT="$0"
TELEM_DIR=/etc/telemetrics
OPT_OUT_FILE=${TELEM_DIR}/opt-out
TELEM_WRK_DIRS_CONF=/usr/lib/tmpfiles.d/telemetrics-dirs.conf
create_work_dirs() {
# Creates dirs if missing, adjust ownership if exists
systemd-tmpfiles --create ${TELEM_WRK_DIRS_CONF}
}
telem_remove_work_dirs() {
# Remove dirs
awk '/^d/{print $2}' ${TELEM_WRK_DIRS_CONF} | xargs rm -rf;
}
exit_ok() {
echo "$1" > /dev/stderr
@@ -50,15 +61,23 @@ telem_stop() {
telem_start() {
[ -f $OPT_OUT_FILE ] && exit_err "Opt out is enabled. Cannot start services."
# trigger systemd-tmpfiles work dirs creation
create_work_dirs
# the units in SERVICES are activated as needed, so no need to start them
for_each_service "start" ${SPECIAL_UNITS[@]}
}
telem_is_active() {
# check only activation units
systemctl is-active telemd.socket
}
telem_opt_out() {
[ -f $OPT_OUT_FILE ] && exit_ok "Already opted out. Nothing to do."
mkdir -p $TELEM_DIR || exit_err "Failed to create ${TELEM_DIR}."
touch $OPT_OUT_FILE || exit_err "Failed to create ${OPT_OUT_FILE}."
telem_stop
telem_remove_work_dirs
}
telem_opt_in() {
@@ -73,13 +92,16 @@ telem_restart() {
}
usage() {
printf "$SCRIPT - Control actions for telemetry services\n"
format=' %-10s %s\n'
printf "\n"
printf "%15s %-65s\n" "stop" "Stops all running telemetry services"
printf "%15s %-65s\n" "start" "Starts all telemetry services"
printf "%15s %-65s\n" "restart" "Restarts all telemetry services"
printf "%15s %-65s\n" "opt-in" "Opts in to telemetry, and starts telemetry services"
printf "%15s %-65s\n" "opt-out" "Opts out of telemetry, and stops telemetry services"
printf "%s - Control actions for telemetry services\n" "$SCRIPT"
printf "\n"
printf "$format" "stop" "Stops all running telemetry services"
printf "$format" "start" "Starts all telemetry services"
printf "$format" "restart" "Restarts all telemetry services"
printf "$format" "is-active" "Checks if telemd is active"
printf "$format" "opt-in" "Opts in to telemetry, and starts telemetry services"
printf "$format" "opt-out" "Opts out of telemetry, and stops telemetry services"
printf "\n"
exit 2
}
@@ -105,6 +127,8 @@ case $SUBCOMMAND in
telem_start ;;
restart)
telem_restart ;;
is-active)
telem_is_active ;;
*)
notice "Unknown command passed to $SCRIPT"
usage ;;
+4 -1
View File
@@ -33,7 +33,10 @@ static const char *header_names[] = {
TM_SYSTEM_BUILD_STR,
TM_KERNEL_VERSION_STR,
TM_PAYLOAD_VERSION_STR,
TM_SYSTEM_NAME_STR
TM_SYSTEM_NAME_STR,
TM_BOARD_NAME_STR,
TM_CPU_MODEL_STR,
TM_BIOS_VERSION_STR
};
const char *get_header_name(int ind)
+8 -2
View File
@@ -35,6 +35,9 @@
#define TM_KERNEL_VERSION 8
#define TM_PAYLOAD_VERSION 9
#define TM_SYSTEM_NAME 10
#define TM_BOARD_NAME 11
#define TM_CPU_MODEL 12
#define TM_BIOS_VERSION 13
#define TM_RECORD_VERSION_STR "record_format_version"
#define TM_CLASSIFICATION_STR "classification"
@@ -47,14 +50,17 @@
#define TM_KERNEL_VERSION_STR "kernel_version"
#define TM_PAYLOAD_VERSION_STR "payload_format_version"
#define TM_SYSTEM_NAME_STR "system_name"
#define TM_BOARD_NAME_STR "board_name"
#define TM_CPU_MODEL_STR "cpu_model"
#define TM_BIOS_VERSION_STR "bios_version"
#define NUM_HEADERS 11
#define NUM_HEADERS 14
/* For internal library usage. Bump the version whenever we change the record
* structure (e.g. adding or removing a header field). Note that the value
* should be an unsigned int.
*/
static const uint32_t RECORD_FORMAT_VERSION = 2;
static const uint32_t RECORD_FORMAT_VERSION = 3;
#define TM_SITE_VERSION_FILE "/etc/os-release"
#define TM_DIST_VERSION_FILE "/usr/lib/os-release"
+12 -4
View File
@@ -91,14 +91,20 @@ bool read_config_from_file(char *config_file, struct configuration *config)
keyfile = nc_ini_file_parse(config_file);
if (!keyfile) {
#ifdef DEBUG
fprintf(stderr, "ERR: Failed to read config file: %s\n",
error->message);
fprintf(stderr, "ERR: Failed to read config file\n");
#endif
return false;
} else {
for (int i = CONF_STR_MIN + 1; i < CONF_STR_MAX; i++) {
config->strValues[i] = strdup(nc_hashmap_get(nc_hashmap_get(keyfile, "settings"), config_key_str[i]));
if (config->strValues[i] == NULL) {
char *ptr;
ptr = nc_hashmap_get(nc_hashmap_get(keyfile, "settings"), config_key_str[i]);
if (ptr) {
config->strValues[i] = strdup(ptr);
if (config->strValues[i] == NULL) {
return false;
}
} else {
fprintf(stderr, "ERR: missing key with string value: %s\n", config_key_str[i]);
return false;
}
}
@@ -108,6 +114,7 @@ bool read_config_from_file(char *config_file, struct configuration *config)
if (ptr) {
config->intValues[i] = strtoll(ptr, NULL, 10);
} else {
fprintf(stderr, "ERR: missing key with integer value: %s\n", config_key_int[i]);
return false;
}
}
@@ -125,6 +132,7 @@ bool read_config_from_file(char *config_file, struct configuration *config)
config->boolValues[i] = true;
}
} else {
fprintf(stderr, "ERR: missing key with boolean value: %s\n", config_key_bool[i]);
return false;
}
}
+13
View File
@@ -0,0 +1,13 @@
[Unit]
Description=Telemetrics BERT Probe
Requires=telemd.socket
After=telemd.socket
ConditionPathExists=!/etc/telemetrics/opt-out
ConditionPathExists=/sys/firmware/acpi/tables/data/BERT
[Service]
ExecStart=@bindir@/bertprobe
User=telemetry
[Install]
WantedBy=multi-user.target
+14 -1
View File
@@ -4,7 +4,8 @@ pathfix = @sed \
-e 's|@libdir[@]|$(libdir)|g' \
-e 's|@localstatedir[@]|$(localstatedir)|g' \
-e 's|@PACKAGE_VERSION[@]|$(PACKAGE_VERSION)|g' \
-e 's|@SOCKETDIR[@]|$(SOCKETDIR)|g'
-e 's|@SOCKETDIR[@]|$(SOCKETDIR)|g' \
-e 's|@systemctldir[@]|$(SYSTEMD_SYSTEMCTLDIR)|g'
EXTRA_DIST += \
%D%/40-core-ulimit.conf \
@@ -12,6 +13,7 @@ EXTRA_DIST += \
%D%/example.conf \
%D%/hprobe.service.in \
%D%/hprobe.timer \
%D%/bert-probe.service.in \
%D%/journal-probe.service.in \
%D%/oops-probe.service.in \
%D%/pstore-probe.service.in \
@@ -21,6 +23,7 @@ EXTRA_DIST += \
%D%/telemd.path.in \
%D%/telemd.service.in \
%D%/telemd.socket.in \
%D%/telemd-update-trigger.service.in \
%D%/telemetrics-dirs.conf.in \
%D%/telemetrics.conf.in
@@ -46,6 +49,7 @@ systemdunitdir = @SYSTEMD_UNITDIR@
systemdunit_DATA = \
%D%/hprobe.service \
%D%/hprobe.timer \
%D%/bert-probe.service \
%D%/journal-probe.service \
%D%/oops-probe.service \
%D%/pstore-probe.service \
@@ -53,11 +57,15 @@ systemdunit_DATA = \
%D%/pstore-clean.service \
%D%/telemd.service \
%D%/telemd.socket \
%D%/telemd-update-trigger.service \
%D%/telemd.path
%D%/hprobe.service: %D%/hprobe.service.in
$(pathfix) < $< > $@
%D%/bert-probe.service: %D%/bert-probe.service.in
$(pathfix) < $< > $@
%D%/journal-probe.service: %D%/journal-probe.service.in
$(pathfix) < $< > $@
@@ -82,6 +90,9 @@ systemdunit_DATA = \
%D%/telemd.path: %D%/telemd.path.in
$(pathfix) < $< > $@
%D%/telemd-update-trigger.service: %D%/telemd-update-trigger.service.in
$(pathfix) < $< > $@
sysctldir = @SYSTEMD_SYSCTLDIR@
sysctl_DATA = %D%/40-crash-probe.conf
@@ -95,6 +106,7 @@ clean-local:
-rm -f %D%/telemd.service \
%D%/telemd.socket \
%D%/telemd.path \
%D%/telemd-update-trigger.service \
%D%/telemetrics.conf \
%D%/telemetrics-dirs.conf \
%D%/libtelemetry.pc \
@@ -105,4 +117,5 @@ clean-local:
%D%/klogscanner.service \
%D%/pstore-clean.service \
%D%/hprobe.service \
%D%/bert-probe.service \
%D%/.dirstamp
@@ -0,0 +1,7 @@
[Unit]
Description=Restart Telemetrics Daemon On Update
BindsTo=update-triggers.target
[Service]
Type=oneshot
ExecStart=@systemctldir@/systemctl try-restart telemd.service
+3 -8
View File
@@ -46,20 +46,17 @@ noinst_LTLIBRARIES = %D%/libtelem-shared.la
%D%/configuration.c \
%D%/nica/inifile.c \
%D%/nica/hashmap.c \
%D%/nica/b64enc.c \
%D%/configuration.h \
%D%/common.c \
%D%/common.h
%C%_libtelem_shared_la_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
$(AM_CFLAGS)
%C%_libtelem_shared_la_LDFLAGS = \
$(AM_LDFLAGS)
%C%_libtelem_shared_la_LIBADD = \
$(GLIB_LIBS)
lib_LTLIBRARIES = \
%D%/libtelemetry.la
@@ -71,12 +68,10 @@ include_HEADERS = %D%/telemetry.h
noinst_HEADERS = %D%/log.h
%C%_libtelemetry_la_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
$(AM_CFLAGS)
%C%_libtelemetry_la_LDFLAGS = \
$(AM_LDFLAGS) \
$(GLIB_LIBS) \
-version-info $(SHAREDLIB_CURRENT):$(SHAREDLIB_REVISION):$(SHAREDLIB_AGE) \
-Wl,--version-script=$(top_srcdir)/src/telemetry.sym
+154
View File
@@ -0,0 +1,154 @@
/*
* This file is part of libnica.
*
* Copyright © 2017 Intel Corporation
*
* libnica is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include "b64enc.h"
#define B64_LINE_LEN 76
static int padding[] = {0, 2, 1, 0};
static char table[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
/*
* base 64 encoding of 3 bytes to 4 printable characters
* (https://tools.ietf.org/html/rfc4648).
*
* i.e. (Reference: https://en.wikipedia.org/wiki/Base64)
*
* [ M ] [ a ] [ n ]
* bin 01001101 01100001 01101110
*
* b64 01001101 01100001 01101110 >> 18 & 0x3F -> 010011 -> ( 19 dec) T
* ------
*
* b64 01001101 01100001 01101110 >> 12 & 0x3F -> 010110 -> ( 22 dec) W
* -------
*
* b64 01001101 01100001 01101110 >> 6 & 0x3F -> 000101 -> ( 5 dec) F
* -------
*
* b64 01001101 01100001 01101110 >> 0 & 0x3F -> 101110 -> (117 dec) u
* ------
*
*/
static void b64_3b(char bin[3], char *out) {
uint32_t x = (((uint32_t) bin[0] << 16) & 0xFF0000) |
(((uint32_t) bin[1] << 8) & 0xFF00) |
(((uint32_t) bin[2]) & 0xFF);
out[3] = table[x & 0x3F];
out[2] = table[x >> 6 & 0x3F];
out[1] = table[x >> 12 & 0x3F];
out[0] = table[x >> 18 & 0x3F];
}
/*
* Provide padding to already initialized b64 output
*/
static void b64_padding(char *out, int pad_size) {
if (pad_size > 0) {
*out++ = '=';
}
if (pad_size > 1) {
*out++ = '=';
}
*out = '\0';
}
/*
* Encode file contents as b64 string
*/
int nc_b64enc_file(FILE *fh, char *buff, size_t buff_size) {
int ret_val = 1;
int out_count = 0;
int pad_size = 0;
char *buff_ptr = buff;
char bin[3] = {0, 0, 0};
size_t len = 0;
while ((len = fread(bin, 1, 3, fh)) > 0) {
b64_3b(bin, buff_ptr);
/*
Reset 2nd and 3rd bytes in case only one byte was read. If these
bytes are not clean b64 output will be corrupted with old bits.
*/
bin[1] = bin[2] = 0;
/*
Advance pointer to the next free position of the b64 encoded
buffer. This happens to be len + 1 (as longs as len >= 1 and len <= 3)
len = 1 byte -> b64 -> 2 bytes (or len + 1)
len = 2 bytes -> b64 -> 3 bytes (or len + 1)
len = 3 bytes -> b64 -> 4 bytes (or len + 1)
*/
buff_ptr += (len + 1);
/*
Line break every 76 characters
*/
out_count += (int) (len + 1);
if (out_count % B64_LINE_LEN == 0) {
*buff_ptr++ = '\n';
}
pad_size = padding[len];
/*
Check not to overflow buffer
buffer size <= b64 characters + padding length + null termination
*/
if (buff_size <= (size_t) (buff_ptr - buff) + (size_t) pad_size + 1) {
ret_val = 0;
goto end_b64_enc;
}
}
b64_padding(buff_ptr, pad_size);
end_b64_enc:
return ret_val;
}
int nc_b64enc_filename(const char *filename, char *buff, size_t buff_size) {
int ret = 0;
FILE *fh = NULL;
fh = fopen(filename, "rb");
if (fh == NULL) {
goto nc_b64_clean;
}
ret = nc_b64enc_file(fh, buff, buff_size);
nc_b64_clean:
if (fh) {
fclose(fh);
}
return ret;
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+42
View File
@@ -0,0 +1,42 @@
/*
* This file is part of libnica.
*
* Copyright © 2017 Intel Corporation
*
* libnica is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1
* of the License, or (at your option) any later version.
*
* This is a chained base64 encode implementation.
*/
#pragma once
#define _GNU_SOURCE
#include "macros.h"
/*
* Encodes contents of file handler in base64
*
* @param fh File handler to encode as base64
* @param buff Output buffer where the base64 output should be written
* @param buff_size Size of the output buffer
*
* @returns 1 in success and 0 in failure
*/
_nica_public_ int nc_b64enc_file(FILE *fh, char *buff, size_t buff_size);
/*
* Encodes contents of file (filename) in base64
*
* @param filename File to encode as base64
* @param buff Output buffer where the base64 output should be written
* @param buff_size Size of the output buffer
*
* @returns 1 in success and 0 in failure
*/
_nica_public_ int nc_b64enc_filename(const char *filename, char *buff, size_t buff_size);
+143
View File
@@ -0,0 +1,143 @@
/*
* This program is part of the Clear Linux Project
*
* Copyright 2018 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify it under
* the terms and conditions of the GNU Lesser General Public License, as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
#define _GNU_SOURCE
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <getopt.h>
#include "common.h"
#include "telemetry.h"
#include "nica/b64enc.h"
#include "config.h"
static const char bert_record_file[] = "/sys/firmware/acpi/tables/data/BERT";
static const char telem_record_class[] = "org.clearlinux/bert/debug";
static uint32_t severity = 2;
static uint32_t payload_version = 1;
void print_usage(char *prog)
{
printf("%s: Usage\n", prog);
printf(" -f, --config_file Specify a configuration file other than default\n");
printf(" -h, --help Display this help message\n");
printf(" -V, --version Print the program version\n");
}
static int allocate_payload_buffer(char **payload)
{
int ret = 0;
*payload = (char *)malloc(MAX_PAYLOAD_SIZE);
if (*payload == NULL) {
goto out;
}
*payload = memset(*payload, 0, MAX_PAYLOAD_SIZE);
ret = 1;
out:
return ret;
}
int main(int argc, char **argv)
{
struct telem_ref *tm_handle = NULL;
char *classification = (char *)telem_record_class;
char *payload = NULL;
// Following vars are for arg parsing.
int c;
char *cfile = NULL;
int opt_index = 0;
int ret = 0;
struct option opts[] = {
{ "config_file", 1, NULL, 'f' },
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
{ NULL, 0, NULL, 0 }
};
while ((c = getopt_long(argc, argv, "f:hHV", opts, &opt_index)) != -1) {
switch (c) {
case 'f':
if (asprintf(&cfile, "%s", (char *)optarg) < 0) {
exit(EXIT_FAILURE);
}
struct stat buf;
ret = stat(cfile, &buf);
/* check if the file exists and is a regular file */
if (ret == -1 || !S_ISREG(buf.st_mode)) {
exit(EXIT_FAILURE);
}
tm_set_config_file(cfile);
break;
case 'h':
print_usage(argv[0]);
exit(EXIT_SUCCESS);
case 'V':
printf(PACKAGE_VERSION "\n");
exit(EXIT_SUCCESS);
case '?':
exit(EXIT_FAILURE);
}
}
if (!(ret = allocate_payload_buffer(&payload))) {
printf("Unable to allocate more memory.\n");
goto fail;
}
if(!(ret = nc_b64enc_filename(bert_record_file, payload, MAX_PAYLOAD_SIZE))) {
printf("Failed to read payload from: %s\n", bert_record_file);
goto fail;
}
if ((ret = tm_create_record(&tm_handle, severity, classification,
payload_version)) < 0) {
printf("Failed to create record: %s\n", strerror(-ret));
goto fail;
}
if ((ret = tm_set_payload(tm_handle, payload)) < 0) {
printf("Failed to set record payload: %s\n", strerror(-ret));
goto fail;
}
free(payload);
if ((ret = tm_send_record(tm_handle)) < 0) {
printf("Failed to send record to daemon: %s\n", strerror(-ret));
goto fail;
}
fail:
free(cfile);
tm_free_record(tm_handle);
tm_handle = NULL;
return ret;
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+59 -38
View File
@@ -68,6 +68,9 @@ static char clr_build_class[40] = "org.clearlinux/crash/clr-build";
static char error_class[30] = "org.clearlinux/crash/error";
static char unknown_class[30] = "org.clearlinux/crash/unknown";
static char temp_core[] = "/tmp/corefile-XXXXXX";
static bool keep_core = false;
static const Dwfl_Callbacks cb =
{
.find_elf = dwfl_build_id_find_elf,
@@ -147,23 +150,14 @@ static int temp_core_file(void)
int tmp;
ssize_t ret;
char core[PATH_MAX] = "/tmp/corefile-XXXXXX";
/* mkstemp() opens the file with O_EXCL and 0600 permissions, so no need
* to change umask or manipulate the fd to meet those requirements.
*/
if ((tmp = mkstemp(core)) < 0) {
if ((tmp = mkstemp(temp_core)) < 0) {
telem_perror("Failed to create temp core file");
return -1;
}
#ifndef DEBUG
if (unlink(core) < 0) {
telem_perror("Failed to unlink temp core file");
return -1;
}
#endif
while (true) {
// Use Linux-specific splice(2) here;
// simplifies copying data from pipe->file
@@ -416,6 +410,18 @@ fail:
return -1;
}
static bool startswith(const char *full, const char *prefix)
{
while (*prefix) {
if (*prefix != *full) {
return false;
}
full++;
prefix++;
}
return true;
}
static bool in_clr_build(char *fullpath)
{
// Global override for privacy filters
@@ -427,8 +433,8 @@ static bool in_clr_build(char *fullpath)
* The build environment for Clear Linux packages is set up by 'mock',
* and the chroot in which rpmbuild builds the packages has this prefix.
*/
if ((strstr(fullpath, "/builddir/build/BUILD/")) ||
(strstr(fullpath, "!builddir!build!BUILD!"))) {
if (startswith(fullpath, "/builddir/build/BUILD/") ||
startswith(fullpath, "!builddir!build!BUILD!")) {
return true;
}
@@ -444,13 +450,13 @@ static bool is_banned_path(char *fullpath)
// Anything outside of /usr/, or in /usr/local/, we consider third-party
if ((strncmp(fullpath, "/usr/", 5) != 0) &&
(strncmp(fullpath, "!usr!", 5) != 0)) {
if (!startswith(fullpath, "/usr/") &&
!startswith(fullpath, "!usr!")) {
return true;
}
if ((strncmp(fullpath, "/usr/local/", 11) == 0) ||
(strncmp(fullpath, "!usr!local!", 11) == 0)) {
if (startswith(fullpath, "/usr/local/") ||
startswith(fullpath, "!usr!local!")) {
return true;
}
@@ -563,28 +569,6 @@ int main(int argc, char **argv)
exit(EXIT_FAILURE);
}
if (proc_path && in_clr_build(proc_path)) {
telem_log(LOG_NOTICE, "Ignoring core (from mock build)\n");
backtrace = nc_string_dup("Crash from Clear package build\n");
if (!send_data(&backtrace, unknown_severity, clr_build_class)) {
goto fail;
}
goto success;
}
if (proc_path && is_banned_path(proc_path)) {
telem_log(LOG_NOTICE, "Ignoring core (third-party binary)\n");
backtrace = nc_string_dup("Crash from third party\n");
if (!send_data(&backtrace, unknown_severity, unknown_class)) {
goto fail;
}
goto success;
}
if (core_file) {
core_fd = open(core_file, O_RDONLY);
if (core_fd == -1) {
@@ -617,6 +601,32 @@ int main(int argc, char **argv)
}
}
if (proc_path && in_clr_build(proc_path)) {
telem_log(LOG_NOTICE, "Ignoring core (from mock build)\n");
backtrace = nc_string_dup("Crash from Clear package build\n");
keep_core = true;
if (!send_data(&backtrace, unknown_severity, clr_build_class)) {
goto fail;
}
goto success;
}
if (proc_path && is_banned_path(proc_path)) {
telem_log(LOG_NOTICE, "Ignoring core (third-party binary)\n");
backtrace = nc_string_dup("Crash from third party\n");
keep_core = true;
if (!send_data(&backtrace, unknown_severity, unknown_class)) {
goto fail;
}
goto success;
}
elf_version(EV_CURRENT);
if (prepare_corefile(&e_core, core_fd) < 0) {
@@ -667,6 +677,11 @@ success:
ret = EXIT_SUCCESS;
fail:
// Do not remove the core file if any errors occur
if (ret == EXIT_FAILURE) {
keep_core = true;
}
free(core_file);
free(proc_name);
free(proc_path);
@@ -695,6 +710,12 @@ fail:
close(core_fd);
}
// Remove the core file by default, except when the --core-file option
// is specified, or when keep_core is overridden to true.
if (!core_file && !keep_core) {
unlink(temp_core);
}
return ret;
}
+1 -1
View File
@@ -49,7 +49,7 @@ int main(int argc, char **argv)
int ret = 0;
struct option opts[] = {
{ "cfile", 1, NULL, 'f' },
{ "config_file", 1, NULL, 'f' },
{ "heartbeat", 0, NULL, 'H' },
{ "help", 0, NULL, 'h' },
{ "version", 0, NULL, 'V' },
+22 -8
View File
@@ -38,6 +38,7 @@
#include "config.h"
#include "log.h"
#include "telemetry.h"
#include "probe.h"
#include "nica/nc-string.h"
#define BOOT_ID_LEN 33
@@ -227,9 +228,14 @@ static bool add_filters(sd_journal *journal)
/* The semantics of how journal entry matching works is described in
* detail in sd_journal_add_match(3).
*
* The matches declared here correspond to the logical expression:
* When the privacy filter override is enabled, the matches declared
* here correspond to this logical expression:
*
* BOOTID && ((P0 || P1 || P2 || P3) || EXITED)
* BOOTID && ((P0 || P1 || P2 || P3) || EXITED)
*
* Otherwise, the expression is:
*
* BOOTID && EXITED
*
* BOOTID is short for _BOOT_ID=VAL, where VAL is the boot ID for the
* current boot. P0, P1, etc stand for PRIORITY=0, etc. And EXITED is
@@ -239,12 +245,20 @@ static bool add_filters(sd_journal *journal)
JOURNAL_MATCH(data);
free(data);
JOURNAL_AND;
// The four highest log levels, all indicating errors
JOURNAL_MATCH("PRIORITY=0");
JOURNAL_MATCH("PRIORITY=1");
JOURNAL_MATCH("PRIORITY=2");
JOURNAL_MATCH("PRIORITY=3");
JOURNAL_OR;
// Filter messages with the four highest log levels, all indicating
// errors, but only when the privacy filters override is in effect;
// because the log messages contain arbitrary strings, and this probe
// does not yet keep a whitelist of allowed patterns or a blacklist of
// banned patterns.
if (access(TM_PRIVACY_FILTERS_OVERRIDE, F_OK) == 0) {
JOURNAL_MATCH("PRIORITY=0");
JOURNAL_MATCH("PRIORITY=1");
JOURNAL_MATCH("PRIORITY=2");
JOURNAL_MATCH("PRIORITY=3");
JOURNAL_OR;
}
// Only set for service-level error conditions
JOURNAL_MATCH("EXIT_CODE=exited");
+3 -4
View File
@@ -23,7 +23,6 @@ int main(void)
char *bufp = NULL;
size_t buflen = 0;
int bytes = 0;
int loopcount = 0;
oops_parser_init(write_oops_to_file);
@@ -45,13 +44,13 @@ int main(void)
// Gets the size of the kernel ring buffer
log_size = klogctl(SYSLOG_ACTION_SIZE_BUFFER, NULL, 0);
if (log_size < 0) {
telem_log(LOG_ERR, "Cannot read size of kernel ring buffer\n");
telem_log(LOG_ERR, "Cannot read size of kernel ring buffer: %s\n", strerror(errno));
return 1;
}
buflen = (size_t)log_size;
if (loopcount++ > 0 && buflen > MAX_BUF) {
if (buflen > MAX_BUF) {
buflen = MAX_BUF;
}
@@ -59,7 +58,7 @@ int main(void)
bufp = (char *)calloc(buflen, sizeof(char));
malloc_trim(0);
bytes = klogctl(SYSLOG_ACTION_READ, bufp, log_size);
bytes = klogctl(SYSLOG_ACTION_READ, bufp, (int)buflen);
if (bytes < 0) {
telem_log(LOG_ERR, "Cannot read contents of kernel ring buffer: %s\n", strerror(errno));
return 1;
+19 -23
View File
@@ -5,7 +5,8 @@ bin_PROGRAMS += \
%D%/klogscanner \
%D%/pstoreprobe \
%D%/oopsprobe \
%D%/pstoreclean
%D%/pstoreclean \
%D%/bertprobe
%C%_hprobe_SOURCES = %D%/hello.c
%C%_hprobe_LDADD = $(top_builddir)/src/libtelemetry.la
@@ -13,12 +14,17 @@ bin_PROGRAMS += \
$(AM_LDFLAGS) \
-pie
%C%_bertprobe_SOURCES = %D%/bert_probe.c \
src/nica/b64enc.c
%C%_bertprobe_LDADD = $(top_builddir)/src/libtelemetry.la
%C%_bertprobe_LDFLAGS = \
$(AM_LDFLAGS) \
-pie
%C%_telem_record_gen_SOURCES = %D%/telem_record_gen.c
%C%_telem_record_gen_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
%C%_telem_record_gen_LDADD = $(top_builddir)/src/libtelemetry.la \
$(GLIB_LIBS)
$(AM_CFLAGS)
%C%_telem_record_gen_LDADD = $(top_builddir)/src/libtelemetry.la
%C%_telem_record_gen_LDFLAGS = \
$(AM_LDFLAGS) \
-pie
@@ -48,13 +54,11 @@ endif
src/nica/nc-string.c \
%D%/probe.h
%C%_crashprobe_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
$(AM_CFLAGS)
%C%_crashprobe_LDADD = \
$(top_builddir)/src/libtelemetry.la \
$(top_builddir)/src/libtelem-shared.la \
@ELFUTILS_LIBS@ \
$(GLIB_LIBS)
@ELFUTILS_LIBS@
%C%_crashprobe_LDFLAGS = \
$(AM_LDFLAGS) \
-pie
@@ -73,12 +77,10 @@ endif
src/nica/nc-string.c \
%D%/oops_parser.c
%C%_pstoreprobe_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
$(AM_CFLAGS)
%C%_pstoreprobe_LDADD = \
$(top_builddir)/src/libtelemetry.la \
$(top_builddir)/src/libtelem-shared.la \
$(GLIB_LIBS)
$(top_builddir)/src/libtelem-shared.la
if LOG_SYSTEMD
if HAVE_SYSTEMD_JOURNAL
@@ -97,11 +99,9 @@ endif
src/nica/nc-string.c \
%D%/oops_parser.c
%C%_klogscanner_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
$(AM_CFLAGS)
%C%_klogscanner_LDADD = \
$(top_builddir)/src/libtelemetry.la \
$(GLIB_LIBS)
$(top_builddir)/src/libtelemetry.la
%C%_klogscanner_LDFLAGS = \
$(AM_LDFLAGS) \
-pie
@@ -128,12 +128,10 @@ endif
src/nica/nc-string.c \
%D%/probe.h
%C%_oopsprobe_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS)
$(AM_CFLAGS)
%C%_oopsprobe_LDADD = \
$(top_builddir)/src/libtelemetry.la \
$(top_builddir)/src/libtelem-shared.la \
$(GLIB_LIBS)
$(top_builddir)/src/libtelem-shared.la
if LOG_SYSTEMD
if HAVE_SYSTEMD_JOURNAL
@@ -154,13 +152,11 @@ bin_PROGRAMS += \
%D%/journal.c
%C%_journalprobe_CFLAGS = \
$(AM_CFLAGS) \
$(GLIB_CFLAGS) \
$(SYSTEMD_ID128_CFLAGS) \
$(SYSTEMD_JOURNAL_CFLAGS)
%C%_journalprobe_LDADD = \
$(top_builddir)/src/libtelemetry.la \
$(top_builddir)/src/libtelem-shared.la \
$(GLIB_LIBS) \
$(SYSTEMD_ID128_LIBS) \
$(SYSTEMD_JOURNAL_LIBS)
%C%_journalprobe_LDFLAGS = \
+9 -12
View File
@@ -16,7 +16,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <dirent.h>
#include <glib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
@@ -26,6 +25,7 @@
#include "log.h"
#include "telemetry.h"
#include "oops_parser.h"
#include "nica/hashmap.h"
char *pstore_dump_path = PSTOREDIR;
@@ -202,8 +202,8 @@ int main(int argc, char **argv)
DIR *pstore_dir;
struct dirent *entry;
struct chunk_list *elem, *head;
GHashTableIter iter;
gpointer key, value;
NcHashmapIter iter;
void *key, *value;
int max_part;
size_t totalsize = 0;
char *crash_dump = NULL;
@@ -212,7 +212,7 @@ int main(int argc, char **argv)
* Hash table used to store chunks belonging to a oops counter
* key -> count, value->head of linked list of chunks
*/
GHashTable *hash = g_hash_table_new(g_int_hash, g_int_equal);
NcHashmap *hash = nc_hashmap_new(nc_simple_hash, nc_simple_compare);
pstore_dir = opendir(pstore_dump_path);
if (pstore_dir == NULL) {
@@ -250,23 +250,20 @@ int main(int argc, char **argv)
elem->contents = read_contents(entry->d_name, &(elem->size));
elem->next = NULL;
gpointer head = g_hash_table_lookup(hash, (void *)(&elem->count));
void *head = nc_hashmap_get(hash, NC_HASH_KEY(elem->count));
if (head != NULL) {
elem->next = (struct chunk_list *)head;
}
gint *key = g_new(gint, 1);
*key = elem->count;
g_hash_table_insert(hash, key, elem);
nc_hashmap_put(hash, NC_HASH_KEY(elem->count), elem);
}
closedir(pstore_dir);
g_hash_table_iter_init (&iter, hash);
while (g_hash_table_iter_next (&iter, &key, &value)) {
nc_hashmap_iter_init(hash, &iter);
while (nc_hashmap_iter_next(&iter, (void **)&key, (void **)&value)) {
#ifdef DEBUG
printf("Count in the hash: %d\n", *((int *)key));
printf("Count in the hash: %d\n", NC_UNHASH_KEY(key));
#endif
head = (struct chunk_list *)value;
+4 -4
View File
@@ -150,7 +150,7 @@ fail:
int validate_opts(void)
{
size_t i;
size_t len;
int ret = 0;
/* classification */
@@ -159,15 +159,15 @@ int validate_opts(void)
return ret;
}
i = strlen(opt_class);
len = strlen(opt_class);
if ((i == 0) || (i > 120)) {
if ((len == 0) || (len > 120)) {
fprintf(stderr, "Error: Valid size for classification "
"is 1-120 chars\n");
return ret;
}
for (int c = 0; c < strlen(opt_class); c++) {
for (int c = 0; c < len; c++) {
if (isascii(opt_class[c]) == 0) {
fprintf(stderr, "Error: Non-ascii characters detected "
"in classification - aborting\n");
+9
View File
@@ -27,6 +27,7 @@
#include <fcntl.h>
#include <time.h>
#include <malloc.h>
#include <sys/uio.h>
#include "telemdaemon.h"
#include "common.h"
@@ -462,6 +463,11 @@ bool post_record_http(char *headers[], char *body, bool spool)
const char *cert_file = get_cainfo_config();
const char *tid_header = get_tidheader_config();
// Initialize the libcurl global environment once per POST. This lets us
// clean up the environment after each POST so that when the daemon is
// sitting idle, it will be consuming as little memory as possible.
curl_global_init(CURL_GLOBAL_ALL);
curl = curl_easy_init();
if (!curl) {
telem_log(LOG_ERR, "curl_easy_init(): Unable to start libcurl"
@@ -532,6 +538,9 @@ bool post_record_http(char *headers[], char *body, bool spool)
curl_slist_free_all(custom_headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return res ? false : true;
}
+158
View File
@@ -400,6 +400,62 @@ static int set_timestamp_header(struct telem_ref *t_ref)
return status;
}
/**
* Sets cpu model for telemetry record. The information from cpu is extracted
* from /proc/cpuinfo, specifically "model name" attribute.
*
* @param t_ref Telemetry Record reference obtained from tm_create_record.
*
* @return 0 if successful, or a negative errno-style value if not.
*
*/
static int set_cpu_model_header(struct telem_ref *t_ref)
{
FILE *fs = NULL;
char buf[SMALL_LINE_BUF] = { 0 };
char *model_name = NULL;
const char *attr_name = "model name";
int status = 0;
size_t attr_len = strlen(attr_name);
size_t model_str_len = 0;
fs = fopen("/proc/cpuinfo", "r");
if (fs != NULL) {
while (fgets(buf, SMALL_LINE_BUF, fs)) {
if (strncmp(attr_name, buf, attr_len) == 0) {
model_name = strchr(buf, ':');
break;
}
}
fclose(fs);
if (model_name != NULL) {
model_str_len = strlen(model_name);
if (model_str_len > 2) {
model_name = (char *)model_name + 2 * sizeof(char);
model_name[model_str_len - 2] = '\0';
} else {
model_name = "blank";
}
} else {
model_name = "blank";
fprintf(stderr, "NOTICE: Unable to find attribute:%s\n", attr_name);
}
status = set_header(
&(t_ref->record->headers[TM_CPU_MODEL]),
TM_CPU_MODEL_STR, model_name,
&(t_ref->record->header_size));
} else {
#ifdef DEBUG
fprintf(stderr, "NOTICE: Unable to open /proc/cpuinfo\n");
#endif
status = -1;
}
return status;
}
/**
* A healper function for set_host_type_header that reads the first line out of
* a file and chomps the newline if necessary. If the file does not exist the
@@ -497,6 +553,90 @@ static int get_dmi_value(const char *source, const char *key, char **buf)
return ret;
}
/**
* Sets the board name for telemetry record, this record is a combination of
* board name and board vendor. This information is read from dmi filesystem
* Board Name (board_name) and Board Vendor (board_vendor).
*
* @param t_ref Telemetry Record reference obtained from tm_create_record.
*
* @return 0 if successful, or a negative errno-style value if not.
*
*/
static int set_board_name_header(struct telem_ref *t_ref)
{
int status = 0;
int rc;
char *buf = NULL;
char *bn = NULL;
char *bv = NULL;
rc = get_dmi_value("/sys/class/dmi/id/board_name", "bn", &bn);
if (rc < 0) {
status = rc;
goto cleanup;
}
rc = get_dmi_value("/sys/class/dmi/id/board_vendor", "bv", &bv);
if (rc < 0) {
status = rc;
goto cleanup;
}
rc = asprintf(&buf, "%s|%s", bn, bv);
if (rc < 0) {
status = -ENOMEM;
goto cleanup;
} else {
status = set_header(
&(t_ref->record->headers[TM_BOARD_NAME]),
TM_BOARD_NAME_STR, buf,
&(t_ref->record->header_size));
free(buf);
}
cleanup:
if (bn != NULL) {
free(bn);
}
if (bv != NULL) {
free(bv);
}
return status;
}
/**
* Sets BIOS version header for telemetry record, this information is
* read from dmi filesystem BIOS Version (bios_version).
*
* @param t_ref Telemetry Record reference obtained from tm_create_record.
*
* @return 0 if successful, or a negative errno-style value if not.
*
*/
static int set_bios_version_header(struct telem_ref *t_ref)
{
int status = 0;
int rc = 0;
char *bios_version = NULL;
rc = get_dmi_value("/sys/class/dmi/id/bios_version", "bv", &bios_version);
if (rc < 0) {
status = rc;
} else {
status = set_header(
&(t_ref->record->headers[TM_BIOS_VERSION]),
TM_BIOS_VERSION_STR, bios_version,
&(t_ref->record->header_size));
free(bios_version);
}
return status;
}
/**
* Sets the hosttype header, which is a tuple of three values looked for
* in the dmi filesystem. System Vendor (sys_vendor), Product Name
@@ -730,6 +870,24 @@ int allocate_header(struct telem_ref *t_ref, uint32_t severity,
goto free_and_fail;
}
i++;
if ((ret = set_board_name_header(t_ref)) < 0) {
goto free_and_fail;
}
i++;
if ((ret = set_cpu_model_header(t_ref)) < 0) {
goto free_and_fail;
}
i++;
if ((ret = set_bios_version_header(t_ref)) < 0) {
goto free_and_fail;
}
i++; /* Not necessary, but including for future expansion */
return ret;
+4 -1
View File
@@ -295,7 +295,10 @@ START_TEST(check_process_record_with_correct_size_and_data)
"machine_id: 1234\ncreation_timestamp: 1418672344\narch:x86_64\n"
"host_type: macbookpro\nbuild: 200\nkernel_version: 3.15\n"
"payload_format_version: 1\n"
"system_name: clear-linux-os\n";
"system_name: clear-linux-os\n"
"board_name: Qemu|Intel\n"
"cpu_model: Intel(R) Core(TM) i7-5650U CPU @ 2.20GHz\n"
"bios_version: Qemu\n";
char *post_body = "test message";
set_up_socket_pair(&client_fd, &server_fd);
+198
View File
@@ -0,0 +1,198 @@
/*
* This program is part of the Clear Linux Project
*
* Copyright 2017 Intel Corporation
*
* This program is free software; you can redistribute it and/or modify it under
* the terms and conditions of the GNU Lesser General Public License, as
* published by the Free Software Foundation; either version 2.1 of the License,
* or (at your option) any later version.
*
* This program is distributed in the hope it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
* A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
#define OUT_MAX_LEN 100
#include <stdio.h>
#include <check.h>
#include "nica/b64enc.h"
START_TEST(check_nc_b64_no_overflow)
{
size_t n = 2;
char out[OUT_MAX_LEN];
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/foobar";
ck_assert_msg(!nc_b64enc_filename(filename, &out[0], n), "Should quit since buffer smaller than content");
}
END_TEST
START_TEST(check_nc_b64_enc_n_file_empty)
{
size_t n = OUT_MAX_LEN;
char out[OUT_MAX_LEN];
char *result = "\0";
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/empty";
ck_assert_msg(nc_b64enc_filename(filename, &out[0], n), "Error opening file");
ck_assert_str_eq(out, result);
}
END_TEST
START_TEST(check_nc_b64_enc_n_file_fo)
{
size_t n = OUT_MAX_LEN;
char out[OUT_MAX_LEN];
char *result = "Zm8=";
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/fo";
ck_assert_msg(nc_b64enc_filename(filename, &out[0], n), "Error opening file");
ck_assert_str_eq(out, result);
}
END_TEST
START_TEST(check_nc_b64_enc_n_file_foob)
{
size_t n = OUT_MAX_LEN;
char out[OUT_MAX_LEN];
char *result = "Zm9vYg==";
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/foob";
ck_assert_msg(nc_b64enc_filename(filename, &out[0], n), "Error opening file");
ck_assert_str_eq(out, result);
}
END_TEST
START_TEST(check_nc_b64_enc_n_file_foobar)
{
size_t n = OUT_MAX_LEN;
char out[OUT_MAX_LEN];
char *result = "Zm9vYmFyZm9vYmFy";
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/foobar";
ck_assert_msg(nc_b64enc_filename(filename, &out[0], n), "Error opening file");
ck_assert_str_eq(out, result);
}
END_TEST
START_TEST(check_nc_b64_enc_n_file_long_text)
{
size_t out_len = 8000;
char out[out_len];
const char *expected_out = \
"TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIG5vbnVtbXkgdml0YWUsIGluIHZpdmFtdXMgc3Vz\n"
"cGVuZGlzc2UgYWMuIFNlbXBlciBzZWQgcGhhcmV0cmEgc2NlbGVyaXNxdWUuIEVnZXQgZWxlaWZl\n"
"bmQgYW1ldCB2ZWwgbnVuYyB2b2x1dHBhdCBjdXJzdXMsIGF1Y3RvciBwbGF0ZWEgcHJldGl1bSwg\n"
"bWF0dGlzIHJpc3VzIGZhY2lsaXNpcyBmYXVjaWJ1cywgc3VzcGVuZGlzc2UgZGlhbSwgbGVjdHVz\n"
"IG1ldHVzIG51bGxhLiBOdW5jIGp1c3RvIGZhY2lsaXNpIG5hbSBmZWxpcyB2ZWwgbGFvcmVldCwg\n"
"bmliaCBsZW8gbWFzc2Egc3VzcGVuZGlzc2UgYWNjdW1zYW4gY29udmFsbGlzLCBzZWQgcXVpcyBw\n"
"ZWxsZW50ZXNxdWUgZWdlc3RhcywgbG9yZW0gYW50ZSBtb3JiaSBtYXR0aXMgdml0YWUuIFByYWVz\n"
"ZW50IGluIHJ1dHJ1bSBlZ2V0IGFjLCB2aXZhbXVzIHBlZGUgc3VzcGVuZGlzc2UgbGVjdHVzIG51\n"
"bGxhbSwgZXQgcGVkZSBldSBvZGlvIHBlZGUsIGV0aWFtIGxlbyBlZ2VzdGFzIGluLCBub251bW15\n"
"IHNlbXBlci4gQSBhY2N1bXNhbiBkdWksIHZlc3RpYnVsdW0gcmlkaWN1bHVzIGV0IGluLCBqdXN0\n"
"byBldCBwbGFjZXJhdCBkdWlzIHV0IHZpdmFtdXMgbGliZXJvLiBWZXN0aWJ1bHVtIGVnZXQgdXQg\n"
"bW9sbGlzLCBudW5jIGEgc29sbGljaXR1ZGluLCBmcmluZ2lsbGEgZXJvcyBwb3N1ZXJlIG1vbGxp\n"
"cyBhYyBuYXRvcXVlIHBlZGUsIHJpc3VzIG51bGxhIHJ1dHJ1bSB0dXJwaXMgYSB2aXRhZSBpZC4g\n"
"SW5jZXB0b3MgdGluY2lkdW50IHF1aWRlbSBhcmN1IHR1cnBpcyBudW5jIHNvbGxpY2l0dWRpbi4g\n"
"UGhhc2VsbHVzIG1ldHVzIGVyYXQgcGxhY2VyYXQsIGluIGVyYXQgbG9yZW0gbWF1cmlzIG51bGxh\n"
"LCBhdCBsaWJlcm8gZXJhdCBzdXNwZW5kaXNzZSBzYXBpZW4gbW9udGVzIHJob25jdXMsIG1hZ25h\n"
"IHF1YW0uIEp1c3RvIHByYWVzZW50IGxhY3VzIG1hc3NhLCByZXByZWhlbmRlcml0IG51bmMsIHJp\n"
"c3VzIGRpZ25pc3NpbSBmZWxpcyBub24sIGRpZ25pc3NpbSBvZGlvIHRlbGx1cyBhbGlxdWFtLCBl\n"
"bmltIHNvZGFsZXMgdGVtcG9yIHF1aXNxdWUgc29kYWxlcyBtYWduaXMgcG9ydHRpdG9yLiBMaWd1\n"
"bGEgc2VkIHJpc3VzLCBldSBlc3QgcXVpc3F1ZSwgY2xhc3Mgbm9uIGF1dGUgZXUsIHBsYXRlYSBz\n"
"Y2VsZXJpc3F1ZSBpZCBpbnRlZ2VyIGRpZ25pc3NpbW9zIGludGVnZXIgc3VzY2lwaXQuClZlaGlj\n"
"dWxhIGVpdXMgbmVjLCBjb252YWxsaXMgbWFlY2VuYXMgbGVjdHVzIHB1cnVzIHF1aXNxdWUgYWxp\n"
"cXVhbSwgZXQgZXQsIGxhY2luaWEgZGlzIGRpZ25pc3NpbSBsb3JlbS4gVm9sdXB0YXRlbSB1dCwg\n"
"dml2ZXJyYSBhZW5lYW4gcGhhc2VsbHVzIGZlbGlzLCBmZXJtZW50dW0gbGlndWxhIGVnZXQgdWxs\n"
"YW1jb3JwZXIgYW1ldCwgbWF1cmlzIGxlbyBsdWN0dXMuIEV0IGV0aWFtIHNlbXBlciBwaGFyZXRy\n"
"YSBuaWJoIG1pIG1hdXJpcy4gSGVuZHJlcml0IHNlZCBhbGlxdWFtLCBlZ2V0IGlkIHRlbGx1cyBt\n"
"YWduYSwgc2VkIGxhb3JlZXQgdWx0cmljZXMgZW5pbSwgbW9sbGlzIHN1c3BlbmRpc3NlIGluLiBF\n"
"dSBudWxsYW0gbG9yZW0gc2l0IGV0aWFtLCBvcmNpIGluIGxpYmVyby4gTnVuYyBpcHN1bSBtYXVy\n"
"aXMgZXQgc2VtIGhhYy4gRGljdHVtIGZhdWNpYnVzIGRpcyB2aXRhZSBpbiB2b2x1dHBhdCBtb3Ji\n"
"aSwgYSBkaWN0dW0sIHF1aXNxdWUgbWF1cmlzLCBhdWN0b3IgbmVjIHZlbCBwZWxsZW50ZXNxdWUg\n"
"dWxsYW1jb3JwZXIgZXRpYW0uIEN1bSBsaWJlcm8gd2lzaSBhY2N1bXNhbiBhbGlxdWFtIGNvbnNl\n"
"Y3RldHVlciB0ZWxsdXMsIHBoYXNlbGx1cyBjdXJhZSwgYXQgaWxsdW0gYW50ZSBwcmV0aXVtIG5p\n"
"YmggbW9yYmkuIFNlZCBibGFuZGl0IHB1bHZpbmFyIHB1bHZpbmFyLiBEaWN0dW0gZXN0IG51bmMg\n"
"ZWxlaWZlbmQsIHZlbGl0IHZlbGl0IHRlbXBvciBhY2N1bXNhbiBsb2JvcnRpcyBsYW9yZWV0IGNv\n"
"bmd1ZS4gU2l0IGFtZXQsIHNlZCBuaWJoIHBvcnJvIG5lcXVlIGF1Y3RvciBoeW1lbmFlb3MgcG9z\n"
"dWVyZSwgb2RpbyBudWxsYSBibGFuZGl0IGNvbmd1ZSBlbGl0LiBJZCBmYXVjaWJ1cyBldCB0ZW1w\n"
"dXMgbWFsZXN1YWRhIHBsYXRlYS4=";
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/long_text";
ck_assert_msg(nc_b64enc_filename(filename, &out[0], out_len), "Error opening file");
ck_assert_str_eq(out, expected_out);
}
END_TEST
START_TEST(check_nc_b64_enc_n_filehandler_foobar)
{
size_t n = OUT_MAX_LEN;
char out[OUT_MAX_LEN];
char *result = "Zm9vYmFyZm9vYmFy";
const char *filename = TOPSRCDIR "/tests/nc_b64enc_test_files/foobar";
FILE *fh = NULL;
fh = fopen(filename, "rb");
if (fh == NULL) {
ck_abort_msg("Unable to open foobar file");
}
ck_assert_msg(nc_b64enc_file(fh, &out[0], n), "Result does not match");
ck_assert_str_eq(out, result);
}
END_TEST
Suite *config_suite(void)
{
// A suite is comprised of test cases, defined below
Suite *s = suite_create("nc_b64enc");
// Individual unit tests are added to "test cases"
TCase *t = tcase_create("nc_b64enc");
tcase_add_test(t, check_nc_b64_no_overflow);
tcase_add_test(t, check_nc_b64_enc_n_file_empty);
tcase_add_test(t, check_nc_b64_enc_n_file_fo);
tcase_add_test(t, check_nc_b64_enc_n_file_foob);
tcase_add_test(t, check_nc_b64_enc_n_file_foobar);
tcase_add_test(t, check_nc_b64_enc_n_file_long_text);
tcase_add_test(t, check_nc_b64_enc_n_filehandler_foobar);
suite_add_tcase(s, t);
return s;
}
int main(void)
{
Suite *s;
SRunner *sr;
s = config_suite();
sr = srunner_create(s);
// Use the TAP driver for now, so that each
// unit test will PASS/FAIL in the log output.
srunner_set_log(sr, NULL);
srunner_set_tap(sr, "-");
srunner_run_all(sr, CK_SILENT);
// failed = srunner_ntests_failed(sr);
srunner_free(sr);
// if you want the TAP driver to report a hard error based
// on certain conditions (e.g. number of failed tests, etc.),
// return non-zero here instead.
return 0;
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+23 -11
View File
@@ -14,6 +14,7 @@ check_PROGRAMS = \
%D%/check_config \
%D%/check_daemon \
%D%/check_probes \
%D%/check_ncb64 \
%D%/check_libtelemetry
dist_check_SCRIPTS = \
@@ -24,12 +25,10 @@ dist_check_SCRIPTS = \
%C%_check_config_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@ \
@GLIB_CFLAGS@
@CHECK_CFLAGS@
%C%_check_config_LDADD = \
@CHECK_LIBS@ \
@GLIB_LIBS@ \
$(top_builddir)/src/libtelem-shared.la
%C%_check_daemon_SOURCES = \
@@ -40,11 +39,9 @@ dist_check_SCRIPTS = \
%C%_check_daemon_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@ \
@GLIB_CFLAGS@ \
@CURL_CFLAGS@
%C%_check_daemon_LDADD = \
@CHECK_LIBS@ \
@GLIB_LIBS@ \
@CURL_LIBS@ \
$(top_builddir)/src/libtelem-shared.la
@@ -96,11 +93,9 @@ EXTRA_DIST += \
%C%_check_probes_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@ \
@GLIB_CFLAGS@
@CHECK_CFLAGS@
%C%_check_probes_LDADD = \
@CHECK_LIBS@ \
@GLIB_LIBS@ \
$(top_builddir)/src/libtelem-shared.la
if LOG_SYSTEMD
@@ -112,17 +107,34 @@ if HAVE_SYSTEMD_JOURNAL
endif
endif
%C%_check_ncb64_SOURCES = \
%D%/check_ncb64.c \
src/nica/b64enc.h \
src/nica/b64enc.c
%C%_check_ncb64_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@
%C%_check_ncb64_LDADD = \
@CHECK_LIBS@
EXTRA_DIST += \
%D%/nc_b64enc_test_files/empty \
%D%/nc_b64enc_test_files/fo \
%D%/nc_b64enc_test_files/foob \
%D%/nc_b64enc_test_files/foobar \
%D%/nc_b64enc_test_files/long_text
%C%_check_libtelemetry_SOURCES = \
%D%/check_libtelemetry.c \
src/telemetry.c
%C%_check_libtelemetry_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@ \
@GLIB_CFLAGS@
@CHECK_CFLAGS@
%C%_check_libtelemetry_LDADD = \
@CHECK_LIBS@ \
@GLIB_LIBS@ \
$(top_builddir)/src/libtelemetry.la \
$(top_builddir)/src/libtelem-shared.la
View File
+1
View File
@@ -0,0 +1 @@
fo
+1
View File
@@ -0,0 +1 @@
foob
+1
View File
@@ -0,0 +1 @@
foobarfoobar
+2
View File
@@ -0,0 +1,2 @@
Lorem ipsum dolor sit amet, nonummy vitae, in vivamus suspendisse ac. Semper sed pharetra scelerisque. Eget eleifend amet vel nunc volutpat cursus, auctor platea pretium, mattis risus facilisis faucibus, suspendisse diam, lectus metus nulla. Nunc justo facilisi nam felis vel laoreet, nibh leo massa suspendisse accumsan convallis, sed quis pellentesque egestas, lorem ante morbi mattis vitae. Praesent in rutrum eget ac, vivamus pede suspendisse lectus nullam, et pede eu odio pede, etiam leo egestas in, nonummy semper. A accumsan dui, vestibulum ridiculus et in, justo et placerat duis ut vivamus libero. Vestibulum eget ut mollis, nunc a sollicitudin, fringilla eros posuere mollis ac natoque pede, risus nulla rutrum turpis a vitae id. Inceptos tincidunt quidem arcu turpis nunc sollicitudin. Phasellus metus erat placerat, in erat lorem mauris nulla, at libero erat suspendisse sapien montes rhoncus, magna quam. Justo praesent lacus massa, reprehenderit nunc, risus dignissim felis non, dignissim odio tellus aliquam, enim sodales tempor quisque sodales magnis porttitor. Ligula sed risus, eu est quisque, class non aute eu, platea scelerisque id integer dignissimos integer suscipit.
Vehicula eius nec, convallis maecenas lectus purus quisque aliquam, et et, lacinia dis dignissim lorem. Voluptatem ut, viverra aenean phasellus felis, fermentum ligula eget ullamcorper amet, mauris leo luctus. Et etiam semper pharetra nibh mi mauris. Hendrerit sed aliquam, eget id tellus magna, sed laoreet ultrices enim, mollis suspendisse in. Eu nullam lorem sit etiam, orci in libero. Nunc ipsum mauris et sem hac. Dictum faucibus dis vitae in volutpat morbi, a dictum, quisque mauris, auctor nec vel pellentesque ullamcorper etiam. Cum libero wisi accumsan aliquam consectetuer tellus, phasellus curae, at illum ante pretium nibh morbi. Sed blandit pulvinar pulvinar. Dictum est nunc eleifend, velit velit tempor accumsan lobortis laoreet congue. Sit amet, sed nibh porro neque auctor hymenaeos posuere, odio nulla blandit congue elit. Id faucibus et tempus malesuada platea.