Compare commits

..
65 Commits
Author SHA1 Message Date
Alex Jaramillo d8d3cdee49 Release v2.0.1
Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-09-25 15:57:03 +00:00
Alex Jaramillo 839ee5c126 Retry logic for post failure on boot
This change alters the logic of post telemetry message to backend to
handle cases when the network or name resolution service is not
available when the daemon starts. Currently if a message is not
delivered it will be spooled and it should have to wait 900 seconds
(default value) for a future loop to process this message. With this
change when the daemon is started and is unable to deliver telemetry
messages it will atempt to re-send those messages using a t*t delay in
seconds up to t = 7 (or 1, 4, 9, 16, 25, 36, 49 seconds).

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-09-24 15:18:12 -07:00
Alex Jaramillo a08a89ab7f Fix send record check bug
This change fixes a bug where the telem-record-gen probe returns 1
though the execution of the probe was successful.

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-09-06 13:33:39 -07:00
Alex Jaramillo f81e4c7fba Telemetry refactoring
This change adds a daemon that handles telemetry record retention,
record reporting, and record limiting policies. This new daemon
receives records in a folder that is monitored by inotify.

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-09-06 09:57:01 -07:00
Alex Jaramillo 0d07d50a1f Addressing comments 2018-08-27 10:40:12 -07:00
Alex Jaramillo 3b096674dc Addresing comment 2018-08-27 10:40:12 -07:00
Alex Jaramillo c946c7dae9 Clip new line in model string 2018-08-27 10:40:12 -07:00
Alex Jaramillo 91a5c82bd3 Fix memory leak 2018-08-27 10:40:12 -07:00
Alex Jaramillo c02f74be5b Add print only option 2018-08-27 10:40:12 -07:00
Patrick McCarty 2082befdb7 Revert "Removing crash dump configuration"
Installing a crashprobe sysctl conf file by default is, in my opinion,
the most useful way to enable the crashprobe for Linux distributions.

The fact that Clear Linux does not use it anymore does not necessarily
dictate policy for telemetrics-client itself. My preference would be to
simply exclude the conf file in the Clear Linux telemetrics-client
package, since that is very easy to do with autospec. Also, the new
crash handling implementation in Clear Linux requires a systemd patch
that won't be upstreamed, so this is another good reason to install the
conf by default.

This reverts commit d1425173a9.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2018-07-30 16:20:05 -07:00
Patrick McCarty f2206d1cc8 crash probe: retry processing when errors occur
It is possible that transient errors may be raised when processing the
core file, so speculatively retry for any processing errors that appear
for the first run.

Signed-off-by: Patrick McCarty <patrick.mccarty@intel.com>
2018-07-27 20:54:49 -07:00
Alex Jaramillo d1425173a9 Removing crash dump configuration
This change removes the crash probe exclusive use of the core dump
reported by the kernel. Other component above telemetry will handle
this to properly report core dumps to telemetry (when available).

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-07-27 15:25:01 -07:00
Alex Jaramillo 98b36d5609 Release v1.16.2
This realease contains changes to add source file references as a
workaround for failing libcheck tests. This changes allows telemetry
client to be built using gcc8. This has to be fixed properly in the
future.

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-07-27 14:19:11 -07:00
Alex Jaramillo e59b75689e Release v1.16.1
This release contains:

* Fix for failing unit tests during build when the system where the
package is being built has a telemetry client running.

* Added documentation to probes.

* Documenting local debugging features.

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-05-14 14:39:30 +00:00
Alex Jaramillo e301dad48e Fix for journal tmp directory
This change fixes a bug where journal unit tests fail if the system
where the code is build has telemetry running.

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-05-14 07:22:39 -07:00
Alex Jaramillo 16d8abae81 Bumping libcheck version to 0.12
Bumping libcheck version to 0.12 this version provides additional assertions.

Signed-off-by: Alex Jaramillo <alex.v.jaramillo@intel.com>
2018-05-14 07:22:39 -07:00
avjarami 4aa1f9f3c4 Adding documentation for probes
Including description of collected metadata and specific information
about what data is collected by default probes that are provided by
telemetry client.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-04-11 09:19:41 -07:00
avjarami af35ee3a9d Docs for new features
* Added brief description of new configuration values.
* Documenting new header.
* Describing use of features for local debug.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-04-10 21:22:22 -07:00
avjarami 03fbd65fbd Release v1.16.0
This release contains:

* record_retention_enabled configuration key: when configuration value
  is enabled copies of the last 100 telemetry record payload are kept
  locally.

* record_server_delivery_enabled configuration key: when this key is
  set to true (default value) records are reported to server. When key
  is set to false records are not sent. This configuration can be used
  with record_retention_enabled to retain debug information locally
  only.

* A new switch (-i/--include_record) for 'telemctl journal' command,
  this switch allows the inclusion of record contents when printing
  journal to standard output.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-23 10:22:49 -07:00
avjarami d1f11980be Record retention feature
This change contains:

* New configuration keys: record_retention_enabled and
  record_server_delivery_enabled. These keys are needed to control
  remote delivery of records and record retention. These keys are
  optional to preserve backward compatibility with existing custom
  configurations.

* Record copy implementation. This change allows to save copies of
  records locally when feature is enabled in configuration. This
  operation is independent of record spooling and record reporting
  to remote server.

* New telem_journal argument to allow record payload print from
  local copy (if it exists).

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-23 10:07:47 -07:00
avjarami d75e27a5c4 Fixing memory leaks
This change fixes memory leaks present in journal printing command line
interface and journal printing api.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-14 06:22:59 -07:00
avjarami 9baf5f821e Use shared random id function
Random id generation function in telemetry.c is equivalent to function
in util.c, this change reuses the function defined in util.c in
telemetry.c and removes the no longer needed function.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-14 06:22:59 -07:00
avjarami be98da141d Add items to ignore list
Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-14 06:22:59 -07:00
avjarami 55bb762479 Release v1.15.0
This release contains:

* A new header event_id, this value can be used by a probe or probes to
  group multiple records when these records are the result of a unique
  event in the system, i.e. an update could create multiple records and
  these records could be tagged with a single event id.

* Telemetry record logging, this change adds a log of records reported
  to telemetry server. The log can be accessed using telemctl journal
  sub-command.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-12 15:13:26 -07:00
avjarami be78434f2c journal dir creation
Adding entry to template for journal directory

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-12 14:45:42 -07:00
avjarami 8edd75df21 Running uncrustify
Fixing syntax and indentation inconsistencies.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-12 14:30:11 -07:00
avjarami dc4f4e67e4 Addressing review
Addressing comments from first code review and fixing travis-ci
check_journal error in prune test.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-09 13:16:34 -08:00
avjarami 41e9090dd8 Refactoring tests to use fixture
Using fixture for event id tests instead of creating a new record for
every event id test.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-09 13:16:34 -08:00
avjarami cbcc5e24d5 Tests for journal feature
Adding tests for journal exported functions.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-09 13:16:34 -08:00
avjarami 4a9d8212c8 Basic record journaling
This change adds a logging mechanism for records submitted to telemetry
client. A basic journal is added to keep track of the last 100 valid
records that the daemon processed.

This journal can be queried by a new command line interface that was
added as part of the change 'telem_journal'.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-03-09 13:16:34 -08:00
avjarami 8a9dbc7528 Adding event_id header
* An event_id header is needed to group records when multiple records
  are generated by same event.

* Adding new available parameter to telem_record_gen, making possible
  for this utility to tag multiple records with same event_id.

Signed-off-by: avjarami <alex.v.jaramillo@intel.com>
2018-02-28 21:54:46 -08:00
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
81 changed files with 4984 additions and 1012 deletions
+4 -1
View File
@@ -33,11 +33,13 @@ build-aux/m4/ltversion.m4
*.trs
/cscope.*
/tags
telemd
telemprobd
telempostd
autoscan.log
configure.scan
tprobe
hprobe
bertprobe
crashprobe
journalprobe
telem-record-gen
@@ -45,6 +47,7 @@ klogscanner
oopsprobe
pstoreclean
pstoreprobe
telem_journal
make.out
types_c.taghl
src/recomp.sh
+3 -3
View File
@@ -25,9 +25,9 @@ addons:
- libdw-dev
install:
- wget http://downloads.sourceforge.net/project/check/check/0.10.0/check-0.10.0.tar.gz
- tar -xvf check-0.10.0.tar.gz
- pushd check-0.10.0 && ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu && make -j48 && sudo make install && popd
- wget https://github.com/libcheck/check/releases/download/0.12.0/check-0.12.0.tar.gz
- tar -xvf check-0.12.0.tar.gz
- pushd check-0.12.0 && ./configure --prefix=/usr --libdir=/usr/lib/x86_64-linux-gnu && make -j48 && sudo make install && popd
script:
- ./configure && make && make check
+1
View File
@@ -20,6 +20,7 @@ include $(top_srcdir)/src/local.mk
include $(top_srcdir)/src/data/local.mk
include $(top_srcdir)/src/nica/local.mk
include $(top_srcdir)/src/probes/local.mk
include $(top_srcdir)/src/journal/local.mk
include $(top_srcdir)/tests/local.mk
release:
+152 -21
View File
@@ -6,15 +6,22 @@ solution for Linux-based operating systems. Specifically, the front end
component includes:
- telemetrics probes that collect specific types of data from the operating
system.
system. For more info on probes go [here](./src/probes).
- a library, libtelemetry, that telemetrics probes use to create telemetrics
records and send them to the daemon for further processing.
- a daemon, telemd, that prepares the records to send to a telemetrics server
- telemprobd, a daemon that handles communication with probes and forwards records
to a second daemon.
- telempostd, this daemon sends telemetry records to a telemetrics server
(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
---------------------
@@ -26,7 +33,7 @@ Build dependencies
- elfutils, which provides libelf and libdwfl libraries..
- (optional) libsystemd, for syslog-style logging to the systemd journal, and
socket/path activation of telemd by systemd.
socket/path activation of telemprobd and telempostd by systemd.
Build and installation
@@ -56,31 +63,38 @@ and modify the /etc version.
Descriptions of config options are listed below in the Usage section.
Starting the daemon
Starting the client
---------------------
If the client was compiled with systemd support the respective activation units
should be already in place (after a ```make install``` invocation). In this case
the client wil start automatically when data is made available to it. i.e. when
executing an ```/usr/bin/hprobe``` command.
Method 1 (recommended):
```{r, engine='bash', count_lines}
systemctl start telemd.socket telemd.path
telemctl start
```
Note: the above invocation technically readies the service for both socket and
path activation, so you may not see the daemon start right away.
path activation, so you may not see an "active" status.
Method 2:
```{r, engine='bash', count_lines}
systemctl start telemd.service
systemctl start telemprobd.service
systemctl start telempostd.service
```
Method 3:
```{r, engine='bash', count_lines}
telemd &
telemprobd &
telempostd &
```
Configure the daemon to autostart at boot
Configure the client to autostart at boot
---------------------
Method 1 (recommended):
@@ -88,22 +102,23 @@ Method 1 (recommended):
Enable the socket-activated service and path unit:
```{r, engine='bash', count_lines}
systemctl enable telemd.socket telemd.path
systemctl enable telemprobd.socket telempostd.path
```
Method 2:
Enable the service itself, which automatically enables the socket and path
Enable services, which automatically enables the socket and path
units as well:
```{r, engine='bash', count_lines}
systemctl enable telemd.service
systemctl enable telemprobd.service
systemctl enable telempostd.service
```
Usage
---------------------
Once the daemon is running, the telemetrics probes will be ready to use.
Once the client is running, the telemetrics probes will be ready to use.
Available probes:
@@ -113,11 +128,11 @@ that telemetrics-client works. It sends a "hello world" record to the server.
* crash probe: A handler for core files that sends the corresponding backtraces
to the server.
The daemon uses the following configuration options from the configuration file:
The client uses the following configuration options from the configuration file:
* server: This specifies the web server that the daemon sends the telemetry records to
* server: This specifies the web server that the telempostd sends the telemetry records to
* socket_path: This specifies the path of the unix domain socket that the
daemon listens on for connections from the probes
telemprobd listens on for connections from the probes
* spool_dir: This config option is related to spooling. If the daemon is not
able to send the telemetry records to the backend server due to reasons such
as the network availability, then it stores the records in a spool directory.
@@ -127,7 +142,7 @@ The daemon uses the following configuration options from the configuration file:
```{r, engine='bash', count_lines}
mkdir -p /var/spool/telemetry
chown -R telemetry:telemetry /var/spool/telemetry
systemctl restart telemd.service
systemctl restart telemprobd.service
```
* record_expiry: This is the time in minutes after which the records in the
@@ -162,6 +177,56 @@ The daemon uses the following configuration options from the configuration file:
threshold has been reached. Currently the options are 'drop' or 'spool', with
spool being the default. If spool is chosen, records will be spooled and sent
at a later time.
* record_retention_enabled: When this key is enabled (true) the daemon saves a
copy of the payload on disk from all valid records. To avoid the excessive use
of disk space only the latest 100 records are kept. The default value for this
configuration key is false.
* record_server_delivery_enabled: This key controls the delivery of records to
```server```, when enabled (default value) the record will be posted to the
address in the configuration file. If this configuration key is disabled (false)
records will not be spooled or posted to backend. This configuration key can
be used in combination with ```record_retention_enabled``` to keep copies of
telemetry records locally only.
Data reported
---------------------
The data reported by the telemetry client could be understood as two main sets:
metadata and a payload.
The metadata is used to report details of a machine's architecture. The
following are the metadata values currently collected (Record Format Version 4):
* record_format_version: version of the record, currently is 'Version 4'. This
value changes when new metadata is added.
* classification: this field is used to identify the type of record sent by a
specific client probe; classifications use the format DOMAIN/PROBE/REST, where
DOMAIN is the vendor of the probe, PROBE is the probe name, and REST is a
probe-defined field to classify what is contained in the payload.
* severity: this is an integer value between 1 and 4 where 1 is "low" and 4 is
"critical"
* machine_id: a machine identifier that is rotate every 3 days for privacy reasons.
* creation_timestamp: timestamp when the record was collected.
* arch: a string describing machine architecture i.e. 'x86_64'.
* host_type: a string with the combination of 'System Vendor', 'Product Name', and
'Product Version' read from dmi file system.
* build: OS build number.
* kernel_version: Kernel version.
* payload_format_version: version of the payload, currently is 'Version 1'.
* system_name: the value after 'ID=' from '/etc/os-release' (or distribution
provided folder)
* board_name: a string read from dmi file system that combines 'Board Name' and
'Board Vendor'.
* cpu_model: cpu model name extracted from '/proc/cpuinfo'.
* bios_version: BIOS version.
* event_id: an id to group multiple records if these were generated by a single
event occurrence.
The payload as mentioned above is reported by probes. The telemetry library adds
the metadata to the payload (done programatically when using library API) for
more information about probes go [here](./src/probes).
Machine id
---------------------
@@ -177,16 +242,82 @@ you can opt in by creating a static machine id file named
# echo "unique machine ID" > /etc/telemetrics/opt-in-static-machine-id
```
The telemetry daemon reads, at most, the first 32 characters from this file
The telemetry client reads, at most, the first 32 characters from this file
uses it for the machine id. You can put a string like 'my-machine-name' in this
file to easily identify your machine. Restart telemd for the machine id
file to easily identify your machine. Restart telemprobd for the machine id
changes to take effect by running:
```{r, engine='bash', count_lines}
# systemctl restart telemd.service
# systemctl restart telemprobd.service
```
You can switch back to the rotating machine id by deleting the override file
and restarting the daemon. You can do a quick test to check that your
and restarting the client. You can do a quick test to check that your
machine-id has changed by running "hprobe" and verifying that a record has
landed on your backend telemetrics server, with the specified machine id.
Event Id
----------------------
This is a 32 character lowercase hexadecimal string i.e. '5de9de8d5f3c6a7d445d75ba01cc3322'.
This header is used to group multiple records by an event id. Before this header
every single record could have been thought of an event, however this is not always
the case. There are "events" that trigger the creation of multiple records (i.e.
updates). The ```event_id``` header was added for probes with the capability to
detect events and group records based on such events. This header was added to
```telem-record-gen``` and can be specified using the ```-e``` (```--event-id```
long form) switch.
```{r, engine=bash, count_lines}
-e, --event-id Event id to use in the record
```
Debugging locally with telemetrics-client
-----------------------------------------------
The function of the telemetrics-client is to handle the transport of information
reported by a probe to a backend (see ```server``` key in configuration). This
information is helpful for developers to debug and fix reported crashes, however
developers not always have access to the backend in these case users can leverage
features added for local debugging. The following is a list of steps to enable
local debug:
* *Enabling record retention*: this step configures telempostd to keep
copies of telemetry records locally. To enable record retention set the value of
```record_retention_enabled``` from ```false``` to ```true```. Optionally set
```record_server_delivery_enabled`` to ```false``` to keep records local only.
Remember to restart the client after configuration values are updated
(```telemctl restart```).
* *Creating a record*: run ```hprobe``` command to create a record for the purposes
of this step by step guide. Once we have the record or records that you need to
capture locally you can display the data.
* *Displaying record metadata*: telempostd keeps metadata of any valid record,
to display this data a new option to telemctl was added ```telemctl journal```.
Assuming that the last record created was the record from previous step `hprobe` we
can use `tail -n 1` to print the last created record only, i.e.
```
$ sudo telemctl journal | tail -n 1
$ org.clearlinux/hello/world Mon 2018-04-02 17:48:01 UTC a19a0d41ba16788881e274b19b8a1be4 5de9de8d5f3c6a7d445d75ba01cc3322 60c014cd-4693-40f1-b334-548cd932949b
```
The headers for the metadata (along with other information) can be printed using the
```-V``` switch with ```telemctl journal``` command, i.e.
```
$ sudo telemctl journal -V | head -n 1
$ Classification Time stamp Record ID Event ID Boot ID
```
* *Displaying record payload*: to print the content of a record payload you can use
the ```-i``` (```--include_record``` long format) option to ```telemctl journal```
command. To print the specific record you created you can use the option ```-r```
(```--record_id``` long format) with the Record Id of the generated record, i.e.
```
$ sudo telemctl journal --record_id a19a0d41ba16788881e274b19b8a1be4 --include_record
$ org.clearlinux/hello/world Mon 2018-04-02 17:48:01 UTC a19a0d41ba16788881e274b19b8a1be4 5de9de8d5f3c6a7d445d75ba01cc3322 60c014cd-4693-40f1-b334-548cd932949b
$ hello
```
+8 -3
View File
@@ -2,13 +2,13 @@
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
AC_INIT([telemetrics-client], [1.12.2], [https://clearlinux.org/])
AC_INIT([telemetrics-client], [2.0.1], [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])
LT_INIT([disable-static])
AC_CONFIG_MACRO_DIRS([build-aux/m4])
AC_CONFIG_SRCDIR([src/main.c])
AC_CONFIG_SRCDIR([src/probe.c])
AC_CONFIG_HEADERS([config.h])
# Checks for programs.
@@ -23,7 +23,7 @@ AC_PROG_MAKE_SET
# Checks for libraries.
# check >= 0.9.12 is required for TAP output
PKG_CHECK_MODULES([CHECK], [check >= 0.9.12])
PKG_CHECK_MODULES([CHECK], [check >= 0.12])
PKG_CHECK_MODULES([CURL], [libcurl])
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])])
@@ -98,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}],
+2 -1
View File
@@ -1,6 +1,7 @@
MANPAGES = \
docs/man/telemctl.1 \
docs/man/telemd.1 \
docs/man/telemprobd.1 \
docs/man/telempostd.1 \
docs/man/telem-record-gen.1 \
docs/man/telemetry.3 \
docs/man/telemetrics.conf.5
+3
View File
@@ -77,6 +77,9 @@ File to read payload from.
.IP \(bu 2
\fB\-R\fP, \fB\-\-record\-version\fP <version>:
Version number for format of payload (default 1).
.IP \(bu 2
\fB\-e\fP, \fB\-\-event\-id\fP <version>:
Event id to use in the record. If not provided a ramdonly generated id will be assigned to record.
.UNINDENT
.UNINDENT
.UNINDENT
+4
View File
@@ -62,6 +62,10 @@ OPTIONS
* ``-R``, ``--record-version`` <version>:
Version number for format of payload (default 1).
* ``-e``, ``--event-id`` <version>:
Event id to use in the record. If not provided a randomly generated id will be assigned to record.
RETURN VALUES
=============
+3
View File
@@ -55,6 +55,9 @@ Opts in to telemetry, and starts telemetry services. The opt\-out file
\fBopt\-out\fP:
Opts out of telemetry, and stops telemetry services. The opt\-out file
\fB/etc/telemetrics/opt\-out\fP is created.
.IP \(bu 2
\fBis\-active\fP:
Checks if telemetry client daemons are active (telemprobd and telempostd).
.UNINDENT
.UNINDENT
.UNINDENT
+3
View File
@@ -40,6 +40,9 @@ OPTIONS
Opts out of telemetry, and stops telemetry services. The opt-out file
``/etc/telemetrics/opt-out`` is created.
* ``is-active``:
Checks if telemetry client daemons are active (telemprobd and telempostd).
RETURN VALUES
=============
+97
View File
@@ -0,0 +1,97 @@
.\" Man page generated from reStructuredText.
.
.TH TELEMPOSTD 1 "" "" ""
.SH NAME
telempostd \- Telemetry client service
.
.nr rst2man-indent-level 0
.
.de1 rstReportMargin
\\$1 \\n[an-margin]
level \\n[rst2man-indent-level]
level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
-
\\n[rst2man-indent0]
\\n[rst2man-indent1]
\\n[rst2man-indent2]
..
.de1 INDENT
.\" .rstReportMargin pre:
. RS \\$1
. nr rst2man-indent\\n[rst2man-indent-level] \\n[an-margin]
. nr rst2man-indent-level +1
.\" .rstReportMargin post:
..
.de UNINDENT
. RE
.\" indent \\n[an-margin]
.\" old: \\n[rst2man-indent\\n[rst2man-indent-level]]
.nr rst2man-indent-level -1
.\" new: \\n[rst2man-indent\\n[rst2man-indent-level]]
.in \\n[rst2man-indent\\n[rst2man-indent-level]]u
..
.SH SYNOPSIS
.sp
\fBtelempostd\fP <flags>
.sp
\fB/etc/telemetrics/telemetrics.conf\fP
.sp
\fB/usr/share/defaults/telemetrics/telemetrics.conf\fP
.sp
\fB/etc/telemetrics/opt\-in\-static\-machine\-id\fP
.SH DESCRIPTION
.sp
The \fBtelempostd\fP program delivers locally generated telemetry records to a remote
telemetry service. Telemetry data can be in any format, and is relayed as\-is.
.SH OPTIONS
.INDENT 0.0
.INDENT 3.5
.INDENT 0.0
.IP \(bu 2
\fB\-f\fP, \fB\-\-config_file\fP [<file>]:
Configuration file. This overides the other parameters.
.IP \(bu 2
\fB\-h\fP, \fB\-\-help\fP:
Display this help message.
.IP \(bu 2
\fB\-V\fP, \fB\-\-version\fP:
Print the program version.
.UNINDENT
.UNINDENT
.UNINDENT
.SH FILES
.INDENT 0.0
.IP \(bu 2
\fB/usr/share/defaults/telemetrics/telemetrics.conf\fP
.INDENT 2.0
.INDENT 3.5
If no custom configuration file is found, \fBtelempostd\fP uses the
settings in this file.
.UNINDENT
.UNINDENT
.IP \(bu 2
\fB/etc/telemetrics/telemetrics.conf\fP
.INDENT 2.0
.INDENT 3.5
Custom configuration file that \fBtelempostd\fP reads. See \fBtelemetrics.conf\fP(5).
.UNINDENT
.UNINDENT
.UNINDENT
.SH EXIT STATUS
.sp
0 when no errors occurred. A non\-zero exit status indicates a failure occurred.
.SH SEE ALSO
.INDENT 0.0
.IP \(bu 2
\fBtelemetry\fP(3)
.IP \(bu 2
\fBtelemetrics.conf\fP(5)
.IP \(bu 2
\fI\%https://github.com/clearlinux/telemetrics\-client\fP
.IP \(bu 2
\fI\%https://clearlinux.org/documentation/\fP
.UNINDENT
.SH COPYRIGHT
(C) 2018 Intel Corporation, CC-BY-SA-3.0
.\" Generated by docutils manpage writer.
.
+71
View File
@@ -0,0 +1,71 @@
==========
telempostd
==========
------------------------
Telemetry client service
------------------------
:Copyright: \(C) 2018 Intel Corporation, CC-BY-SA-3.0
:Manual section: 1
SYNOPSIS
========
``telempostd`` \<flags\>
``/etc/telemetrics/telemetrics.conf``
``/usr/share/defaults/telemetrics/telemetrics.conf``
``/etc/telemetrics/opt-in-static-machine-id``
DESCRIPTION
===========
The ``telempostd`` program delivers locally generated telemetry records to a remote
telemetry service. Telemetry data can be in any format, and is relayed as-is.
OPTIONS
=======
* ``-f``, ``--config_file`` \[\<file\>\]:
Configuration file. This overrides the other parameters.
* ``-h``, ``--help``:
Display this help message.
* ``-V``, ``--version``:
Print the program version.
FILES
=====
* ``/usr/share/defaults/telemetrics/telemetrics.conf``
If no custom configuration file is found, ``telempostd`` uses the
settings in this file.
* ``/etc/telemetrics/telemetrics.conf``
Custom configuration file that ``telempostd`` reads. See ``telemetrics.conf``\(5).
EXIT STATUS
===========
0 when no errors occurred. A non-zero exit status indicates a failure occurred.
SEE ALSO
========
* ``telemetry``\(3)
* ``telemetrics.conf``\(5)
* https://github.com/clearlinux/telemetrics-client
* https://clearlinux.org/documentation/
+2 -2
View File
@@ -41,8 +41,8 @@ level margin: \\n[rst2man-indent\\n[rst2man-indent-level]]
\fB/etc/telemetrics/opt\-in\-static\-machine\-id\fP
.SH DESCRIPTION
.sp
The \fBtelemd\fP program delivers locally generated telemetry records to a remote
telemetry service. Telemetry data can be in any format, and is relayed as\-is.
The \fBtelemd\fP program handles communication between telemetry client and telemetry
probes.
.SH OPTIONS
.INDENT 0.0
.INDENT 3.5
@@ -1,9 +1,9 @@
======
telemd
======
==========
telemprobd
==========
------------------------
Telemetry client service
Telemetry probes service
------------------------
:Copyright: \(C) 2017 Intel Corporation, CC-BY-SA-3.0
@@ -13,7 +13,7 @@ Telemetry client service
SYNOPSIS
========
``telemd`` \<flags\>
``telemprobd`` \<flags\>
``/etc/telemetrics/telemetrics.conf``
@@ -25,8 +25,8 @@ SYNOPSIS
DESCRIPTION
===========
The ``telemd`` program delivers locally generated telemetry records to a remote
telemetry service. Telemetry data can be in any format, and is relayed as-is.
The ``telemprobd`` program handles communication between telemetry client and telemetry
probes.
OPTIONS
@@ -47,12 +47,12 @@ FILES
* ``/usr/share/defaults/telemetrics/telemetrics.conf``
If no custom configuration file is found, ``telemd`` uses the
If no custom configuration file is found, ``telemprobd`` uses the
settings in this file.
* ``/etc/telemetrics/telemetrics.conf``
Custom configuration file that ``telemd`` reads. See ``telemetrics.conf``\(5).
Custom configuration file that ``telemprobd`` reads. See ``telemetrics.conf``\(5).
* ``/etc/telemetrics/opt-in-static-machine-id``
+45 -11
View File
@@ -2,8 +2,8 @@
declare -a SPECIAL_UNITS=(
hprobe.timer
telemd.socket
telemd.path
telemprobd.socket
telempostd.path
)
declare -a SERVICES=(
@@ -12,12 +12,24 @@ declare -a SERVICES=(
oops-probe.service
pstore-probe.service
klogscanner.service
telemd.service
telemprobd.service
telempostd.service
)
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 +62,24 @@ 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
echo "telemprobd :" $(systemctl is-active telemprobd.socket)
echo "telempostd :" $(systemctl is-active telempostd.path)
}
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() {
@@ -72,20 +93,29 @@ telem_restart() {
telem_start
}
telem_journal_cli() {
telem_journal "$@"
}
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 telemprobd and telempostd are 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 "$format" "journal" "Prints telemtry journal contents. Use -h argument with"
printf "$format" "" "command for more options"
printf "\n"
exit 2
}
if [ $# -ne 1 ]; then
usage
if [ "$1" != "journal" ] && [ $# -ne 1 ]; then
usage
fi
if [ $EUID -ne 0 ]; then
@@ -105,6 +135,10 @@ case $SUBCOMMAND in
telem_start ;;
restart)
telem_restart ;;
is-active)
telem_is_active ;;
journal)
telem_journal_cli "$@" ;;
*)
notice "Unknown command passed to $SCRIPT"
usage ;;
+5 -1
View File
@@ -33,7 +33,11 @@ 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,
TM_EVENT_ID_STR
};
const char *get_header_name(int ind)
+18 -2
View File
@@ -35,6 +35,10 @@
#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_EVENT_ID 14
#define TM_RECORD_VERSION_STR "record_format_version"
#define TM_CLASSIFICATION_STR "classification"
@@ -47,14 +51,26 @@
#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 TM_EVENT_ID_STR "event_id"
#define NUM_HEADERS 11
#define NUM_HEADERS 15
#define EVENT_ID_ALPHAB "0123456789abcdef"
#define EVENT_ID_LEN 32
/* Journal */
#define JOURNAL_PATH "/var/log/telemetry/journal"
#define JOURNAL_TMPDIR "/var/log/telemetry/"
#define RECORD_RETENTION_DIR "/var/log/telemetry/records"
/* 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 = 4;
#define TM_SITE_VERSION_FILE "/etc/os-release"
#define TM_DIST_VERSION_FILE "/usr/lib/os-release"
+41 -7
View File
@@ -47,8 +47,8 @@ const char *config_key_int[] = { NULL, "record_expiry", "spool_max_size",
"byte_window_length", "record_burst_limit",
"byte_burst_limit", NULL };
const char *config_key_bool[] = { NULL, "rate_limit_enabled",
"daemon_recycling_enabled", NULL };
const char *config_key_bool[] = { NULL, "rate_limit_enabled", "daemon_recycling_enabled",
"record_retention_enabled", "record_server_delivery_enabled", NULL };
static struct configuration config = { { 0 }, { 0 }, { 0 }, false, NULL };
@@ -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,7 +132,22 @@ bool read_config_from_file(char *config_file, struct configuration *config)
config->boolValues[i] = true;
}
} else {
return false;
/* New configuration keys, CONF_RECORD_RETENTION_ENABLED and CONF_RECORD_SERVER_DELIVERY_ENABLED
* default values, otherwise update will require changes in custom configurations */
if (i == CONF_RECORD_RETENTION_ENABLED) {
#ifdef DEBUG
fprintf(stderr, "WARN: missing boolean optional key: %s\n", config_key_bool[i]);
#endif
config->boolValues[i] = RECORD_RETENTION_ENABLED_DEFAULT;
} else if (i == CONF_RECORD_SERVER_DELIVERY_ENABLED) {
#ifdef DEBUG
fprintf(stderr, "WARN: missing boolean optional key: %s\n", config_key_bool[i]);
#endif
config->boolValues[i] = RECORD_SERVER_DELIVERY_ENABLED_DEFAULT;
} else {
fprintf(stderr, "ERR: missing key with boolean value: %s\n", config_key_bool[i]);
return false;
}
}
}
}
@@ -348,4 +370,16 @@ bool daemon_recycling_enabled_config(void)
initialize_config();
return config.boolValues[CONF_DAEMON_RECYCLING_ENABLED];
}
bool record_retention_enabled_config(void)
{
initialize_config();
return config.boolValues[CONF_RECORD_RETENTION_ENABLED];
}
bool record_server_delivery_enabled_config(void)
{
initialize_config();
return config.boolValues[CONF_RECORD_SERVER_DELIVERY_ENABLED];
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+10
View File
@@ -19,6 +19,8 @@
#include <stdint.h>
#define TM_MAX_WINDOW_LENGTH (1 /*h*/ * 60 /*m*/)
#define RECORD_RETENTION_ENABLED_DEFAULT false
#define RECORD_SERVER_DELIVERY_ENABLED_DEFAULT true
enum config_str_keys {
CONF_STR_MIN = 0,
@@ -47,6 +49,8 @@ enum config_bool_keys {
CONF_BOOL_MIN = 0,
CONF_RATE_LIMIT_ENABLED,
CONF_DAEMON_RECYCLING_ENABLED,
CONF_RECORD_RETENTION_ENABLED,
CONF_RECORD_SERVER_DELIVERY_ENABLED,
CONF_BOOL_MAX
};
@@ -117,4 +121,10 @@ const char *get_tidheader_config(void);
/* Gets whether recycling is enabled */
bool daemon_recycling_enabled_config(void);
/* Gets whether local retention of records is enabled */
bool record_retention_enabled_config(void);
/* Gets whether records should be sent to server_addr */
bool record_server_delivery_enabled_config(void);
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+13
View File
@@ -0,0 +1,13 @@
[Unit]
Description=Telemetrics BERT Probe
Requires=telemprobd.socket
After=telemprobd.socket
ConditionPathExists=!/etc/telemetrics/opt-out
ConditionPathExists=/sys/firmware/acpi/tables/data/BERT
[Service]
ExecStart=@bindir@/bertprobe
User=telemetry
[Install]
WantedBy=multi-user.target
+46
View File
@@ -0,0 +1,46 @@
[settings]
server=http://127.0.0.1
cainfo=/tmp/cacert.crt
tidheader=X-Telemetry-TID:\s6907c830-eed9-4ce9-81ae-76daf8d88f0f
socket_path=/tmp/test_telem_socket
#record expiry time in minutes
record_expiry=1200
spool_dir=/tmp/spool
#maximum size of the spool directory in KB
spool_max_size=1024
#time in seconds for processing spool
spool_process_time=900
#rate limiting record burst limit
record_burst_limit=100
#rate limiting record window length
record_window_length = 15
#rate limiting byte burst limit
byte_burst_limit = 1000
#rate limiting byte window length
byte_window_length=20
#rate limit enabled
rate_limit_enabled=true
#rate limit strategy if record not send
rate_limit_strategy=spool
#daemon recycling enabled
daemon_recycling_enabled=true
#server delivery disable
record_server_delivery_enabled=false
#local copy enabled
record_retention_enabled=true
+2 -2
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Telemetrics Heartbeat Probe
Requires=telemd.socket
After=telemd.socket
Requires=telemprobd.socket
After=telemprobd.socket
ConditionPathExists=!/etc/telemetrics/opt-out
[Service]
+2 -2
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Telemetrics Systemd Journal Probe
Requires=telemd.socket
After=telemd.socket
Requires=telemprobd.socket
After=telemprobd.socket
ConditionPathExists=!/etc/telemetrics/opt-out
[Service]
+2 -2
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Telemetrics Kernel Log Scanner
Requires=telemd.socket
After=telemd.socket oops-probe.service
Requires=telemprobd.socket
After=telemprobd.socket oops-probe.service
[Service]
ExecStart=@bindir@/klogscanner
+33 -13
View File
@@ -4,23 +4,28 @@ 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 \
%D%/40-crash-probe.conf.in \
%D%/example.conf \
%D%/example.1.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 \
%D%/klogscanner.service.in \
%D%/pstore-clean.service.in \
%D%/libtelemetry.pc.in \
%D%/telemd.path.in \
%D%/telemd.service.in \
%D%/telemd.socket.in \
%D%/telempostd.service.in \
%D%/telempostd.path.in \
%D%/telemprobd.service.in \
%D%/telemprobd.socket.in \
%D%/telemprobd-update-trigger.service.in \
%D%/telemetrics-dirs.conf.in \
%D%/telemetrics.conf.in
@@ -46,18 +51,24 @@ 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 \
%D%/klogscanner.service \
%D%/pstore-clean.service \
%D%/telemd.service \
%D%/telemd.socket \
%D%/telemd.path
%D%/telemprobd.service \
%D%/telemprobd.socket \
%D%/telemprobd-update-trigger.service \
%D%/telempostd.service \
%D%/telempostd.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) < $< > $@
@@ -73,13 +84,19 @@ systemdunit_DATA = \
%D%/pstore-clean.service: %D%/pstore-clean.service.in
$(pathfix) < $< > $@
%D%/telemd.service: %D%/telemd.service.in
%D%/telempostd.path: %D%/telempostd.path.in
$(pathfix) < $< > $@
%D%/telemd.socket: %D%/telemd.socket.in
%D%/telempostd.service: %D%/telempostd.service.in
$(pathfix) < $< > $@
%D%/telemd.path: %D%/telemd.path.in
%D%/telemprobd.service: %D%/telemprobd.service.in
$(pathfix) < $< > $@
%D%/telemprobd.socket: %D%/telemprobd.socket.in
$(pathfix) < $< > $@
%D%/telemprobd-update-trigger.service: %D%/telemprobd-update-trigger.service.in
$(pathfix) < $< > $@
sysctldir = @SYSTEMD_SYSCTLDIR@
@@ -92,9 +109,11 @@ systemconfdir = @SYSTEMD_SYSTEMCONFDIR@
systemconf_DATA = %D%/40-core-ulimit.conf
clean-local:
-rm -f %D%/telemd.service \
%D%/telemd.socket \
%D%/telemd.path \
-rm -f %D%/telemprobd.service \
%D%/telemprobd.socket \
%D%/telempostd.service \
%D%/telempostd.path \
%D%/telemprobd-update-trigger.service \
%D%/telemetrics.conf \
%D%/telemetrics-dirs.conf \
%D%/libtelemetry.pc \
@@ -105,4 +124,5 @@ clean-local:
%D%/klogscanner.service \
%D%/pstore-clean.service \
%D%/hprobe.service \
%D%/bert-probe.service \
%D%/.dirstamp
+2 -2
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Telemetrics Kernel Oops Probe
Requires=telemd.socket
After=telemd.socket
Requires=telemprobd.socket
After=telemprobd.socket
[Service]
ExecStart=@bindir@/oopsprobe
+2 -2
View File
@@ -1,7 +1,7 @@
[Unit]
Description=Telemetrics Pstore Probe
Requires=telemd.socket
After=telemd.socket
Requires=telemprobd.socket
After=telemprobd.socket
[Service]
ExecStart=@bindir@/pstoreprobe
+2
View File
@@ -1,5 +1,7 @@
d @localstatedir@/lib/telemetry 0755 telemetry telemetry -
d @localstatedir@/spool/telemetry 0750 telemetry telemetry -
d @localstatedir@/log/telemetry 0750 telemetry telemetry -
d @localstatedir@/log/telemetry/records 0750 telemetry telemetry -
d @localstatedir@/cache/telemetry 0750 telemetry telemetry -
d @localstatedir@/cache/telemetry/oops 0750 telemetry telemetry -
d @localstatedir@/cache/telemetry/pstore 0750 telemetry telemetry -
+10
View File
@@ -55,3 +55,13 @@ rate_limit_strategy=spool
# has not any client nor spool data, then it exits.
# this is to ensure that latest code runs.
daemon_recycling_enabled=true
# record server delivery enabled - when enabled records will be delivered
# to server otherwise records will be ignored. This configuration can be used
# with 'record_retention_enable' configuration value to keep records local only.
record_server_delivery_enabled=true
# record retention enabled - when enabled a copy of reported telemetry records
# will be kept locally. This configuration combined with 'record_server_delivery_enabled'
# value can be used to keep records local only.
record_retention_enabled=false
@@ -1,5 +1,5 @@
[Unit]
Description=Telemetry Daemon Spool
Description=Telemetry Post Daemon staging
ConditionPathExists=!/etc/telemetrics/opt-out
[Path]
+12
View File
@@ -0,0 +1,12 @@
[Unit]
Description=Telemetrics Post Daemon
ConditionPathExists=!/etc/telemetrics/opt-out
[Service]
ExecStart=@bindir@/telempostd
WorkingDirectory=@localstatedir@/spool/telemetry
User=telemetry
[Install]
Also=telempostd.path
WantedBy=multi-user.target
@@ -0,0 +1,7 @@
[Unit]
Description=Restart Telemetrics Daemon On Update
BindsTo=update-triggers.target
[Service]
Type=oneshot
ExecStart=@systemctldir@/systemctl try-restart telemprobd.service
@@ -3,10 +3,10 @@ Description=Telemetrics Daemon
ConditionPathExists=!/etc/telemetrics/opt-out
[Service]
ExecStart=@bindir@/telemd
ExecStart=@bindir@/telemprobd
WorkingDirectory=@localstatedir@/spool/telemetry
User=telemetry
[Install]
Also=telemd.socket telemd.path
Also=telemprobd.socket
WantedBy=multi-user.target
+162
View File
@@ -0,0 +1,162 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <limits.h>
#include <stdbool.h>
#include <sys/stat.h>
#include "log.h"
#include "util.h"
#include "common.h"
#include "iorecord.h"
bool _fgets(char *s, int n, FILE *stream)
{
char *i;
if (!fgets(s, n, stream)) {
return false;
}
if ((i = strchr(s, '\n')) != NULL) {
*i = '\0';
}
return true;
}
void stage_record(char *filepath, char *headers[], char *body)
{
int tmpfd;
FILE *tmpfile = NULL;
// Use default path if not provided
if (filepath == NULL) {
telem_log(LOG_ERR, "filepath value must be provided, aborting\n");
exit(EXIT_FAILURE);
}
tmpfd = mkstemp(filepath);
if (!tmpfd) {
telem_perror("Error opening staging file");
close(tmpfd);
if (unlink(filepath)) {
telem_perror("Error deleting staging file");
}
goto clean_exit;
}
// open file
tmpfile = fdopen(tmpfd, "a");
if (!tmpfile) {
telem_perror("Error opening temp stage file");
close(tmpfd);
if (unlink(filepath)) {
telem_perror("Error deleting temp stage file");
}
goto clean_exit;
}
// write headers
for (int i = 0; i < NUM_HEADERS; i++) {
fprintf(tmpfile, "%s\n", headers[i]);
}
//write body
fprintf(tmpfile, "%s\n", body);
fflush(tmpfile);
fclose(tmpfile);
clean_exit:
return;
}
bool read_record(char *fullpath, char *headers[], char **body)
{
int i, ret = 0;
bool result = false;
FILE *fp = NULL;
long offset;
char line[LINE_MAX] = { 0 };
struct stat buf;
long size;
ret = stat(fullpath, &buf);
if (ret == -1) {
telem_log(LOG_ERR, "Unable to stat record %s in staging\n", fullpath);
return false;
}
size = buf.st_size;
fp = fopen(fullpath, "r");
if (fp == NULL) {
telem_log(LOG_ERR, "Unable to open file %s in staging\n", fullpath);
return false;
}
for (i = 0; i < NUM_HEADERS; i++) {
const char *header_name = get_header_name(i);
if (!_fgets(line, LINE_MAX, fp)) {
telem_log(LOG_ERR, "Error while parsing staged record\n");
fclose(fp);
return false;
}
//Get rid of trailing newline
result = get_header(line, header_name, &headers[i]);
if (!result) {
telem_log(LOG_ERR, "read_record: Incorrect"
" headers in record\n");
goto read_error;
}
}
offset = ftell(fp);
if (offset == -1) {
telem_perror("Failed to get staged file offset of record payload");
result = false;
goto read_error;
}
size = size - offset + 1;
*body = malloc((size_t)size);
if (*body == NULL) {
telem_log(LOG_ERR, "Could not allocate memory for payload from staged file\n");
result = false;
goto read_error;
}
memset(*body, 0, (size_t)size);
//read rest of file
size_t newlen = fread(*body, sizeof(char), (size_t)size, fp);
if (newlen == 0) {
telem_perror("Error reading staged file");
result = false;
goto read_error;
} else {
result = true;
}
read_error:
if (fp) {
fclose(fp);
}
return result;
}
+38
View File
@@ -0,0 +1,38 @@
/*
* 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.
*/
#include <stdbool.h>
/**
* Save a telemetry record to disk
*
* @param path pointer to a directory to save record
* @param headers pointer to array of headers and values
* @param body record message content
*
*/
void stage_record(char *path, char *headers[], char *body);
/**
* Reads a telemetry record
*
* @param fullpath pointer to full path file name
* @param headers pointer to array of headers and values
* @param body record message content
*
* @return true if successful otherwise false
*/
bool read_record(char *fullpath, char *headers[], char **body);
+112
View File
@@ -0,0 +1,112 @@
/*
* 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 <getopt.h>
#include "common.h"
#include "journal.h"
void print_usage(void)
{
printf(" Usage\n");
printf(" -r, --record_id Print record with specific record_id\n");
printf(" -e, --event_id Print records with specific event_id\n");
printf(" -c, --classification Print records with specific classification\n");
printf(" -b, --boot_id Print records with specific boot_id\n");
printf(" -i, --include_record Include record content\n");
printf(" -V, --verbose Verbose output\n");
printf(" -h, --help Display this help message\n");
}
int main(int argc, char **argv)
{
int rc = EXIT_SUCCESS;
int count = 0;
int verbose_output = 0;
int record = 0;
char *boot_id = NULL;
char *record_id = NULL;
char *event_id = NULL;
char *classification = NULL;
struct TelemJournal *telem_journal = NULL;
/** opts */
int c;
int opt_index = 0;
struct option opts[] = {
{ "record_id", 1, NULL, 'r' },
{ "event_id", 1, NULL, 'e' },
{ "classification", 1, NULL, 'c' },
{ "boot_id", 1, NULL, 'b' },
{ "verbose", 0, NULL, 'V' },
{ "include_record", 0, NULL, 'i' },
{ "help", 0, NULL, 'h' },
{ NULL, 0, NULL, 0 }
};
while ((c = getopt_long(argc, argv, "r:e:c:b:Vih", opts, &opt_index)) != -1) {
switch (c) {
case 'r':
record_id = optarg;
break;
case 'e':
event_id = optarg;
break;
case 'c':
classification = optarg;
break;
case 'b':
boot_id = optarg;
break;
case 'V':
verbose_output = 1;
break;
case 'i':
record = 1;
break;
case 'h':
print_usage();
exit(EXIT_SUCCESS);
case '?':
/** default */
print_usage();
exit(EXIT_FAILURE);
}
}
if ((telem_journal = open_journal(JOURNAL_PATH))) {
if (verbose_output) {
fprintf(stdout, "%-30s %-27s %-32s %-32s %-36s\n", "Classification", "Time stamp",
"Record ID", "Event ID", "Boot ID");
}
count = print_journal(telem_journal, classification, record_id, event_id, boot_id, record);
if (verbose_output) {
fprintf(stdout, "Total records: %d\n", count);
}
close_journal(telem_journal);
} else {
fprintf(stderr, "Unable to open journal\n");
rc = EXIT_FAILURE;
}
return rc;
}
+709
View File
@@ -0,0 +1,709 @@
/*
* 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
#define ID_LEN 32
#define BOOTID_LEN 37 // Includes the \n character at the end
#define BOOTID_FILE "/proc/sys/kernel/random/boot_id"
#define MID_BUFF 1024
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <inttypes.h>
#include <sys/uio.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
#include "log.h"
#include "util.h"
#include "common.h"
#include "journal.h"
/*
* Packs journal entry values into one line string separated
* by '\036' RS.
*
* @param entry A pointer to a JournalEntry struct with values
* to serialize.
* @param buff A pointer to be initialized and set with the
* serialized values.
*
* @return 0 on success, -1 on failure.
*/
static int serialize_journal_entry(struct JournalEntry *entry, char **buff)
{
int rc = 0;
if (entry == NULL) {
return -1;
}
if (asprintf(buff, "%s\036%lld\036%s\036%s\036%s", entry->record_id, (long long int)entry->timestamp,
entry->classification, entry->event_id, entry->boot_id) < 0) {
#ifdef DEBUG
fprintf(stderr, "Error: unable to serialize data in buff\n");
#endif
rc = -1;
}
return rc;
}
/**
* Frees journal entry struct members and journal entry pointer.
*
* @param the pointer to the entry structure to free.
*/
static void free_journal_entry(struct JournalEntry *entry)
{
if (entry) {
free(entry->classification);
free(entry->record_id);
free(entry->event_id);
free(entry->boot_id);
free(entry);
}
}
/**
* Unpacks a record that was stored in file and initializes a
* JournalEntry struct with parsed values.
*
* @param line A pointer to the data to unpack.
* @param entry A pointer to be initialized and set with values
* parsed from parameter line.
*
* @return 0 on success, -1 on failure.
*/
static int deserialize_journal_entry(char *line, struct JournalEntry **entry)
{
int rc = -1;
size_t len = 0;
size_t llen = 0;
size_t offset = 0;
struct JournalEntry *e = NULL;
if (line == NULL) {
return -1;
}
e = malloc(sizeof(struct JournalEntry));
if (!e) {
return -1;
}
llen = strlen(line);
for (int i = 0; i < 5; i++) {
char *out = NULL;
if (offset > llen) {
rc = -1;
break;
}
len = strcspn(line + offset, "\036\n");
out = strndup((line + offset), len);
offset += len + 1;
if (out) {
switch (i) {
case 0:
e->record_id = out;
break;
case 1:
e->timestamp = atoll(out);
free(out);
break;
case 2:
e->classification = out;
break;
case 3:
e->event_id = out;
break;
case 4:
e->boot_id = out;
rc = 0;
break;
default:
assert(i < 0 && i > 4);
free(out);
rc = 1;
}
} else {
rc = -1;
break;
}
}
if (rc == 0) {
*entry = e;
} else {
free_journal_entry(e);
}
return rc;
}
/**
* Counts the number of records in journal.
*
* @param telem_journal Pointer to a telemetry journal
* to get record count.
*
* @return record count
*
*/
static int get_record_count(struct TelemJournal *telem_journal)
{
int count = 0;
size_t len;
ssize_t read;
char *line = NULL;
rewind(telem_journal->fptr);
// read until the end line by line
while ((read = getline(&line, &len, telem_journal->fptr)) != -1) {
count++;
}
free(line);
return count;
}
/**
* Serializes JournalEntry and saves it to file.
*
* @param fptr A FILE pointer.
* @param entry A pointer to data.
*
* @return 0 on succes, !=0 on failure
*/
static int save_entry(FILE *fptr, struct JournalEntry *entry)
{
int rc = -1;
char *serialized_data = NULL;
if (fptr == NULL || entry == NULL) {
return rc;
}
if ((rc = serialize_journal_entry(entry, &serialized_data)) == 0) {
#ifdef DEBUG
printf("Saving: %s\n", serialized_data);
#endif
fprintf(fptr, "%s\n", serialized_data);
fflush(fptr);
free(serialized_data);
}
return rc;
}
/**
* Reads the boot unique identifier from BOOTID_FILE
*
* @param buff pointer to a BOOTID_LEN allocated
*
* @return 0 on success, -1 on failure
*/
static int read_boot_id(char buff[])
{
int rc = -1;
FILE *fs = NULL;
fs = fopen(BOOTID_FILE, "r");
if (!fs) {
#ifdef DEBUG
fprintf(stderr, "Unable to open %s for reading: %d\n", BOOTID_FILE, errno);
#endif
return rc;
}
if (fgets(buff, BOOTID_LEN, fs)) {
rc = 0;
}
fclose(fs);
return rc;
}
/**
* Moves the file position indicator n lines.
*
* @param n Number of lines to skip.
* @param fptr A file pointer.
* @param found_record A pointer to a function to be called
* every time a valid record is found, if
* pointer is not set to null.
*
* @returns 0 on success, on failure -1 if n > existing
* lines in file or errno if failure happens
* during file operation.
*/
static int skip_n_lines(int n, FILE *fptr, int (*found_record)(char *))
{
int rc = 0;
size_t len = 0;
char *line = NULL;
struct JournalEntry *entry = NULL;
if (fptr == NULL) {
#ifdef DEBUG
fprintf(stderr, "fptr argument to skip_n_lines is invalid\n");
#endif
// File descriptor in bad state
return EBADFD;
}
if (fseek(fptr, 0, 0) != 0) {
return errno;
}
for (int i = 0; i < n; i++) {
if (getline(&line, &len, fptr) == -1) {
rc = -1;
break;
}
if (found_record != NULL) {
deserialize_journal_entry(line, &entry);
if (entry) {
found_record(entry->record_id);
free_journal_entry(entry);
entry = NULL;
}
}
}
free(line);
return rc;
}
/**
* Copies contents of fptr to a known location.
*
* @param fptr A file pointer from source file.
*
* @returns 0 on success, errno on failure
*/
static int copy_to_tmp(FILE *fptr, char *tmp_path)
{
int rc = 0;
size_t len;
char *line = NULL;
FILE *fptr_tmp = NULL;
if (fptr == NULL) {
return -1;
}
fptr_tmp = fopen(tmp_path, "w");
if (!fptr_tmp) {
return errno;
}
// Copy lines to new file
while (getline(&line, &len, fptr) > -1) {
if (fprintf(fptr_tmp, "%s", line) < 0) {
rc = errno;
break;
}
}
if (fclose(fptr_tmp) != 0) {
rc = errno;
#ifdef DEBUG
perror("Error: ");
#endif
}
free(line);
return rc;
}
/* Exported function */
TelemJournal *open_journal(const char *journal_file)
{
FILE *fptr = NULL;
char boot_id[BOOTID_LEN] = { '\0' };
struct TelemJournal *telem_journal;
// Use default location if journal_file parameter is NULL
if (journal_file == NULL) {
fptr = fopen(JOURNAL_PATH, "a+");
} else {
fptr = fopen(journal_file, "a+");
}
if (fptr == NULL) {
#ifdef DEBUG
perror("Error while opening journal file: ");
#endif
return NULL;
}
if (read_boot_id(boot_id) != 0) {
#ifdef DEBUG
perror("Error while reading boot_id: ");
#endif
return NULL;
}
telem_journal = malloc(sizeof(struct TelemJournal));
if (!telem_journal) {
fprintf(stderr, "Unable to allocate more memory\n");
return NULL;
}
telem_journal->fptr = fptr;
telem_journal->journal_file = \
(journal_file == NULL) ? strdup(JOURNAL_PATH) : strdup(journal_file);
/* boot_id includes \n at the end, strip RC during duplication */
telem_journal->boot_id = strndup(boot_id, BOOTID_LEN - 1);
telem_journal->record_count = get_record_count(telem_journal);
telem_journal->record_count_limit = RECORD_LIMIT;
telem_journal->latest_record_id = NULL;
telem_journal->prune_entry_callback = NULL;
#ifdef DEBUG
printf("Records in db: %d\n", telem_journal->record_count);
#endif
return telem_journal;
}
/* Exported function */
void close_journal(TelemJournal *telem_journal)
{
if (telem_journal) {
free(telem_journal->boot_id);
free(telem_journal->journal_file);
free(telem_journal->latest_record_id);
fclose(telem_journal->fptr);
free(telem_journal);
}
}
/**
* Checks if class is a prefix i.e. t/\* or t/t/\*
*
* @param class A pointer to string with classification
* value to check.
* @return 1 if class is a prefix, 0 otherwise
*/
static int is_class_prefix(char *class)
{
size_t class_len = strlen(class);
return (strcmp((char *)(class + class_len - 2), "/*") == 0) ? 1 : 0;
}
/**
* Print records content
*
* @param record_id Unique record identifier
*
*/
static void print_record(char *record_id)
{
int rc = 0;
size_t read = 0;
char buff[MID_BUFF] = { '\0' };
char *filepath = NULL;
FILE *recordfp = NULL;
rc = asprintf(&filepath, "%s/%s", RECORD_RETENTION_DIR, record_id);
if (rc == -1) {
// just bail out, there are worse problems
return;
}
recordfp = fopen(filepath, "r");
if (!recordfp) {
telem_perror("Error when opening a record to print");
return;
}
while ((read = fread(buff, 1, sizeof(buff), recordfp)) > 0) {
fwrite(buff, 1, read, stdout);
}
free(filepath);
fclose(recordfp);
}
/* Exported function */
int print_journal(TelemJournal *telem_journal, char *classification,
char *record_id, char *event_id, char *boot_id,
bool include_record)
{
int n = 0;
int rc = 0;
int count = 0;
char str_time[80] = { '\0' };
char *line = NULL;
size_t len = 0;
FILE *journal_fileptr = NULL;
struct tm ts;
struct JournalEntry *entry = NULL;
if (telem_journal == NULL) {
return -1;
}
journal_fileptr = fopen(telem_journal->journal_file, "r");
if (!journal_fileptr) {
return -1;
}
// Advance file pointer to line n
n = telem_journal->record_count - telem_journal->record_count_limit;
if (n > 0) {
rc = skip_n_lines(n, journal_fileptr, NULL);
if (rc == -1) {
return rc;
} else if (rc != 0) {
fprintf(stderr, "An error occurred while advancing journal file: %s\n", strerror(rc));
}
}
while (getline(&line, &len, journal_fileptr) != -1) {
deserialize_journal_entry(line, &entry);
if (entry) {
/* filter entry out if one is provided */
if (record_id != NULL && strcmp(entry->record_id, record_id) != 0) {
goto skip_print;
}
if (boot_id != NULL && strcmp(entry->boot_id, boot_id) != 0) {
goto skip_print;
}
if (event_id != NULL && strcmp(entry->event_id, event_id) != 0) {
goto skip_print;
}
// In the case of class checking prefixes is an option
if (classification != NULL) {
// Check prefixes when classification ends in /*, otherwise use strcomp
if (is_class_prefix(classification)) {
if (strncmp(entry->classification, classification, strlen(classification) - 1) != 0) {
continue;
}
} else if (strcmp(entry->classification, classification) != 0) {
continue;
}
}
/* end filters section */
ts = *localtime(&entry->timestamp);
if (strftime(str_time, sizeof(str_time), "%a %Y-%m-%d %H:%M:%S %Z", &ts) == 0) {
continue;
}
/* print record metadata */
fprintf(stdout, "%-30s %s %s %s %s\n", entry->classification, str_time, entry->record_id, entry->event_id, entry->boot_id);
/* print record content */
if (include_record) {
print_record(entry->record_id);
}
count++;
}
skip_print:
free_journal_entry(entry);
}
free(line);
fclose(journal_fileptr);
return count;
}
/**
* Validation for event id. Thic check makes sure
* that an event_id is a 32 hexadecimal chars long.
*
* @param event_id A pointer to id value.
*
* @return 0 on success, 1 on failure
*/
static int validate_event_id(char *event_id)
{
const char alphab[] = EVENT_ID_ALPHAB;
if (event_id == NULL) {
return 1;
}
if (strlen(event_id) != EVENT_ID_LEN) {
return 1;
}
if (strspn(event_id, alphab) != EVENT_ID_LEN) {
return 1;
}
return 0;
}
/* Exported function */
int new_journal_entry(TelemJournal *telem_journal, char *classification,
time_t timestamp, char *event_id)
{
int rc = 1;
char boot_id[BOOTID_LEN] = { '\0' };
char *record_id = NULL;
struct JournalEntry *entry = NULL;
if (telem_journal == NULL) {
#ifdef DEBUG
fprintf(stderr, "Error: telem_journal was not initialized\n");
#endif
return rc;
}
if (validate_classification(classification) != 0) {
return rc;
}
if (validate_event_id(event_id) != 0) {
return rc;
}
entry = malloc(sizeof(struct JournalEntry));
if (!entry) {
fprintf(stderr, "Error: unable to allocate more memory\n");
return rc;
}
entry->classification = NULL;
entry->record_id = NULL;
entry->event_id = NULL;
entry->boot_id = NULL;
if (get_random_id(&record_id) != 0) {
#ifdef DEBUG
fprintf(stderr, "Error: unable to generate random id\n");
#endif
goto quit;
}
if (read_boot_id(boot_id) != 0) {
#ifdef DEBUG
fprintf(stderr, "Error: unable to read boot_id\n");
#endif
goto quit;
}
if ((entry->classification = strdup(classification)) == NULL) {
goto quit;
}
if ((entry->event_id = strdup(event_id)) == NULL) {
goto quit;
}
entry->record_id = record_id;
/* boot_id includes \n at the end, strip RC during duplication */
entry->boot_id = strndup(boot_id, BOOTID_LEN - 1);
entry->timestamp = timestamp;
if ((rc = save_entry(telem_journal->fptr, entry)) == 0) {
telem_journal->record_count = telem_journal->record_count + 1;
#ifdef DEBUG
fprintf(stdout, "%d records in journal\n", telem_journal->record_count);
#endif
}
free(telem_journal->latest_record_id);
telem_journal->latest_record_id = strdup(entry->record_id);
quit:
free_journal_entry(entry);
return rc;
}
/* Exported function */
int prune_journal(struct TelemJournal *telem_journal, char *tmp_dir)
{
int rc = 1;
int count = 0;
int deviation = DEVIATION;
char *tmp_file_path = NULL;
if (telem_journal == NULL) {
return rc;
}
if (telem_journal->record_count > (deviation + telem_journal->record_count_limit)) {
count = telem_journal->record_count - telem_journal->record_count_limit;
// jump to line# count
if ((rc = skip_n_lines(count, telem_journal->fptr, telem_journal->prune_entry_callback)) != 0) {
#ifdef DEBUG
fprintf(stderr, "Error: skipping %d journal lines\n", count);
#endif
// if rc == -1 (n > #lines in file, change to errno style)
return (rc == -1) ? EADDRNOTAVAIL : rc;
}
// Use default if not tmp_dir is specified
if (tmp_dir == NULL) {
if ((asprintf(&tmp_file_path, "%s/.journal", JOURNAL_TMPDIR) == -1)) {
return ENOMEM;
}
} else {
if ((asprintf(&tmp_file_path, "%s/.journal", tmp_dir) == -1)) {
return ENOMEM;
}
}
// create new file with rest of file
if ((rc = copy_to_tmp(telem_journal->fptr, tmp_file_path)) != 0) {
#ifdef DEBUG
fprintf(stderr, "Error: copying partial journal to temp journal file\n");
#endif
goto quit;
}
// close file handler
if ((rc = fclose(telem_journal->fptr)) != 0) {
#ifdef DEBUG
fprintf(stderr, "Error: closing journal file handler\n");
#endif
goto quit;
}
// overwrite file
if ((rc = rename(tmp_file_path, telem_journal->journal_file)) != 0) {
#ifdef DEBUG
fprintf(stderr, "Error: while overwriting journal file\n");
#endif
goto quit;
}
// reopen file handler
telem_journal->fptr = fopen(telem_journal->journal_file, "a+");
if (!telem_journal->fptr) {
#ifdef DEBUG
fprintf(stderr, "Error: re-opening journal file\n");
#endif
return rc;
}
// update record count
telem_journal->record_count = telem_journal->record_count - count;
#ifdef DEBUG
fprintf(stdout, "record_count: %d\n", telem_journal->record_count);
#endif
}
rc = 0;
quit:
free(tmp_file_path);
return rc;
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+117
View File
@@ -0,0 +1,117 @@
/*
* 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
/* default record limit */
#define RECORD_LIMIT 100
#define DEVIATION 50
#include <time.h>
#include <stdio.h>
#include <stdbool.h>
/* Journal entry type */
typedef struct JournalEntry {
time_t timestamp;
char *classification;
char *record_id;
char *event_id;
char *boot_id;
} JournalEntry;
/* Telemetry journal type */
typedef struct TelemJournal {
FILE *fptr;
char *journal_file;
char *boot_id;
char *latest_record_id;
int record_count;
int record_count_limit;
int (*prune_entry_callback)(char *);
} TelemJournal;
/**
* Telemetry journal initialization.
*
* @param journal_file A pointer to a string containing
* the full path to file used as journal storage.
* If journal_file is set to NULL a default value
* will be used.
*
* @returns a telemetry journal structure in success or
* NULL in case of failure.
*/
TelemJournal *open_journal(const char *journal_file);
/**
* Closes journal file and deallocates memory that was
* previously initialized by open_journal call.
*
* @param telem_journal A pointer to struct that has a
* reference to journal file pointer and other
* members.
*/
void close_journal(TelemJournal *telem_journal);
/**
* Prints journal contents to stdout. Use function parameters
* to filter journal entries to print.
*
* @param telem_journal A pointer to struct initialized
* by open_journal call.
* @param classification A pointer to string used as
* classification filter.
* @param record_id A pointer to string used as record_id filter.
* @param event_id A pointer to string used as event_id filter.
* @param boot_id A pointer to string used as boor_id filter.
* @param include_record A flag to control record content print.
*
* @return the number of lines printed to stdout on success, -1
* on failure.
*/
int print_journal(TelemJournal *telem_journal, char *classification,
char *record_id, char *event_id, char *boot_id,
bool include_record);
/**
* Creates a new entry in journal.
*
* @param telem_journal A pointer to telemetry journal.
* @param classification A pointer to a classification value.
* @param timestamp Record creation time stamp value.
* @param event_id A pointer to a unique id value for record
* event.
*
* @return 0 on success 1 on failure.
*/
int new_journal_entry(TelemJournal *telem_journal, char *classification,
time_t timestamp, char *event_id);
/**
* Checks number of lines in log and prunes the oldest records
* if journal grows more than telem_journal->record_count_limit.
*
* @param telem_journal A pointer to telemetry journal struct
* returned by open_journal call.
* @param tmp_dir A pointer to a string with the path to use for
* temp file during pruning process.
*
* @return 0 on success, errno on failure
*/
int prune_journal(TelemJournal *telem_journal, char *tmp_dir);
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+9
View File
@@ -0,0 +1,9 @@
bin_PROGRAMS += \
%D%/telem_journal
%C%_telem_journal_SOURCES = %D%/cli.c \
%D%/journal.c \
src/util.c \
src/common.c
# vim: filetype=automake tabstop=8 shiftwidth=8 noexpandtab
+62 -18
View File
@@ -1,42 +1,85 @@
bin_PROGRAMS = %D%/telemd
bin_PROGRAMS = \
%D%/telemprobd \
%D%/telempostd
%C%_telemd_SOURCES = \
%D%/main.c \
%C%_telemprobd_SOURCES = \
%D%/probe.c \
%D%/telemdaemon.c \
%D%/telemdaemon.h \
%D%/spool.c \
%D%/spool.h
%D%/iorecord.c \
%D%/journal/journal.c \
%D%/journal/journal.h
%C%_telemd_LDADD = $(CURL_LIBS) \
%C%_telemprobd_LDADD = $(CURL_LIBS) \
%D%/libtelem-shared.la
%C%_telemd_CFLAGS = \
%C%_telemprobd_CFLAGS = \
$(AM_CFLAGS)
%C%_telemd_LDFLAGS = \
%C%_telemprobd_LDFLAGS = \
$(AM_LDFLAGS) \
-pie
if HAVE_SYSTEMD_DAEMON
%C%_telemd_CFLAGS += \
$(SYSTEMD_DAEMON_CFLAGS)
%C%_telemd_LDADD += \
$(SYSTEMD_DAEMON_LIBS)
%C%_telemprobd_CFLAGS += \
$(SYSTEMD_DAEMON_CFLAGS)
%C%_telemprobd_LDADD += \
$(SYSTEMD_DAEMON_LIBS)
endif
if LOG_SYSTEMD
if HAVE_SYSTEMD_JOURNAL
%C%_telemd_CFLAGS += \
$(SYSTEMD_JOURNAL_CFLAGS)
%C%_telemd_LDADD += \
$(SYSTEMD_JOURNAL_LIBS)
%C%_telemprobd_CFLAGS += \
$(SYSTEMD_JOURNAL_CFLAGS)
%C%_telemprobd_LDADD += \
$(SYSTEMD_JOURNAL_LIBS)
endif
endif
# Telemetry post daemon
%C%_telempostd_SOURCES = \
%D%/post.c \
%D%/telempostdaemon.c \
%D%/telempostdaemon.h \
%D%/journal/journal.c \
%D%/journal/journal.h \
%D%/spool.h \
%D%/spool.c \
%D%/retention.h \
%D%/retention.c \
%D%/iorecord.c \
%D%/iorecord.h
%C%_telempostd_LDADD = $(CURL_LIBS) \
%D%/libtelem-shared.la
%C%_telempostd_CFLAGS = \
$(AM_CFLAGS)
%C%_telempostd_LDFLAGS = \
$(AM_LDFLAGS) \
-pie
if HAVE_SYSTEMD_DAEMON
%C%_telempostd_CFLAGS += \
$(SYSTEMD_DAEMON_CFLAGS)
%C%_telempostd_LDADD += \
$(SYSTEMD_DAEMON_LIBS)
endif
if LOG_SYSTEMD
if HAVE_SYSTEMD_JOURNAL
%C%_telempostd_CFLAGS += \
$(SYSTEMD_JOURNAL_CFLAGS)
%C%_telempostd_LDADD += \
$(SYSTEMD_JOURNAL_LIBS)
endif
endif
# set library version info
SHAREDLIB_CURRENT=3
SHAREDLIB_CURRENT=4
SHAREDLIB_REVISION=0
SHAREDLIB_AGE=0
SHAREDLIB_AGE=1
noinst_LTLIBRARIES = %D%/libtelem-shared.la
@@ -46,6 +89,7 @@ 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
+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);
+98
View File
@@ -0,0 +1,98 @@
/*
* 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.
*/
#include <stdio.h>
#include <stdlib.h>
#include <getopt.h>
#include <unistd.h>
#include <stdbool.h>
#include <sys/stat.h>
#include "log.h"
#include "spool.h"
#include "configuration.h"
#include "telempostdaemon.h"
bool (*post_record_ptr)(char *[], char *) = post_record_http;
void print_usage(char *prog)
{
printf("%s: Usage\n", prog);
printf(" -f, --config_file Configuration file. This overides the other parameters\n");
printf(" -h, --help Display this help message\n");
printf(" -V, --version Print the program version\n");
}
int main(int argc, char **argv)
{
int c;
int ret = 0;
int opt_index = 0;
char *config_file = NULL;
TelemPostDaemon daemon;
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:hV", opts, &opt_index)) != -1) {
switch (c) {
case 'f':
config_file = optarg;
struct stat buf;
ret = stat(config_file, &buf);
/* check if the file exists and is a regular file */
if (ret == -1 || !S_ISREG(buf.st_mode)) {
telem_log(LOG_ERR, "Configuration file"
" path not valid\n");
exit(EXIT_FAILURE);
}
set_config_file(config_file);
break;
case 'V':
printf(PACKAGE_VERSION "\n");
exit(EXIT_SUCCESS);
case 'h':
print_usage(argv[0]);
exit(EXIT_SUCCESS);
case '?':
print_usage(argv[0]);
exit(EXIT_FAILURE);
}
}
initialize_daemon(&daemon);
daemon.current_spool_size = get_spool_dir_size();
/* When path activated this will process
* the activating message or previously
* spooled data */
staging_records_loop(&daemon);
/* This function is blocking, it will
* block until a signal is received */
run_daemon(&daemon);
close_daemon(&daemon);
return 0;
}
+6 -29
View File
@@ -49,14 +49,12 @@
#include "log.h"
#include "telemdaemon.h"
#include "configuration.h"
#include "spool.h"
/*
* Using a function pointer for unit testing to isolate the call to actual post function.
* The call to post the record to the server is stubbed out in the unit tests
* using pointer to a fake function.
*/
bool (*post_record_ptr)(char *[], char *, bool) = post_record_http;
void print_usage(char *prog)
{
@@ -220,19 +218,9 @@ int main(int argc, char **argv)
telem_log(LOG_INFO, "Listening on socket...\n");
/* Calculate the spool directory size */
if (is_spool_valid()) {
daemon.is_spool_valid = true;
daemon.current_spool_size = get_spool_dir_size();
}
int spool_process_time = spool_process_time_config();
spool_records_loop(&(daemon.current_spool_size));
time_t last_spool_run_time = time(NULL);
time_t last_daemon_start_time = time(NULL);
bool daemon_recycling_enabled = daemon_recycling_enabled_config();
int spool_process_time = spool_process_time_config();
time_t last_daemon_start_time = time(NULL);
ret = update_machine_id();
if (ret == -1) {
@@ -276,12 +264,6 @@ int main(int argc, char **argv)
telem_log(LOG_INFO, "Received a SIGHUP signal\n");
/* reload configuration file */
reload_config();
if (is_spool_valid()) {
daemon.is_spool_valid = true;
//calc_spool_dir_size();
daemon.current_spool_size = get_spool_dir_size();
}
}
}
@@ -337,21 +319,15 @@ int main(int argc, char **argv)
}
} else {
time_t now = time(NULL);
/* If spool is empty and time to recycle the daemon has elapsed*/
if (daemon_recycling_enabled && (daemon.current_spool_size == 0) &&
/* time to recycle the daemon has elapsed*/
if (daemon_recycling_enabled &&
difftime(now, last_daemon_start_time) >= TM_DAEMON_EXIT_TIME) {
/* Exit */
telem_log(LOG_INFO, "Daemon exiting for recycling\n");
goto clean_exit;
}
}
time_t now = time(NULL);
if (difftime(now, last_spool_run_time) >= spool_process_time) {
spool_records_loop(&(daemon.current_spool_size));
last_spool_run_time = time(NULL);
}
if (difftime(now, last_refresh_time) >= TM_REFRESH_RATE) {
int ret = update_machine_id();
if (ret == -1) {
@@ -362,12 +338,13 @@ int main(int argc, char **argv)
}
clean_exit:
/* Free memory before exiting */
while ((cl = LIST_FIRST(&(daemon.client_head))) != NULL) {
remove_client(&(daemon.client_head), cl);
}
free(daemon.pollfds);
free(daemon.machine_id_override);
if (LIST_EMPTY(&(daemon.client_head))) {
telem_log(LOG_INFO, "Client list cleared\n");
}
+53
View File
@@ -0,0 +1,53 @@
# Probes
The default probes provided along the telemetry client code are:
* bertprobe: this probe reports on the Boot Error Region Table if such
entry is found in ```/sys/firmware/acpi/tables/BERT```.
* crashprobe: This probe processes core dump files. It can be registered as
the kernel core file handler in /proc/sys/kernel/core_pattern.
* hprobe: a simple probe that sends a keep alive message. This probe can also
be useful for testing
* journalprobe: a probe that monitors the systemd journal for log messages
from failed services.
* oopsprobe: a probe to collect 'oops messages' when the kernel detects a
problem.
* pstoreprobe: probe to collect messages left on pstore filesystem.
* telem-record-gen: this is a "general-purpose probe" for sending custom
records on-the-fly. This tool can be used stand alone or as a part of a
script to implement a probe.
For example to generate an hprobe like message we can execute the following
command:
```
$ telem-record-gen --class org.example/hello/world --payload hello
```
for more options use the ```--help``` switch i.e.:
```
$ telem-record-gen --help
Usage:
telem-record-gen [OPTIONS] - create and send a custom telemetry record
Help Options:
-h, --help Show help options
Application Options:
-f, --config-file Path to configuration file (not implemented yet)
-V, --version Print the program version
-s, --severity Severity level (1-4) - (default 1)
-c, --class Classification level_1/level_2/level_3
-p, --payload Record body (max size = 8k)
-P, --payload-file File to read payload from
-R, --record-version Version number for format of payload (default 1)
-e, --event-id Event id to use in the record
```
+142
View File
@@ -0,0 +1,142 @@
/*
* 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: */
+61 -44
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) {
@@ -631,16 +641,12 @@ int main(int argc, char **argv)
nc_string_append_printf(header, "Signal: %ld\n", signal_num);
}
if (process_corefile(&backtrace) < 0) {
goto fail;
}
/* On Clear Linux OS, missing symbols may appear if automatic debuginfo
* downloads are still in flight. So if any missing symbols appear on
* the first run (indicated by the presence of "??? - ["), wait 10
* seconds and try again.
* seconds and try again. Also retry if errors occur in the first run.
*/
if (strstr(backtrace->str, "??? - [")) {
if ((process_corefile(&backtrace) < 0) || (strstr(backtrace->str, "??? - ["))) {
sleep(10);
if (prepare_corefile(&e_core, core_fd) < 0) {
@@ -667,6 +673,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 +706,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;
+9 -1
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,6 +14,13 @@ 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)
+93 -21
View File
@@ -38,16 +38,21 @@ static char *opt_class = NULL;
static char *opt_payload = NULL;
static uint32_t payload_version = 1;
static char *opt_payload_file = NULL;
static char *opt_event_id = NULL;
static bool opt_echo = false;
static bool opt_nopost = false;
static const struct option prog_opts[] = {
{ "help", no_argument, 0, 'h' },
{ "config-file", required_argument, 0, 'f' },
{ "echo", no_argument, 0, 'o' },
{ "no-post", no_argument, 0, 'n' },
{ "version", no_argument, 0, 'V' },
{ "severity", required_argument, 0, 's' },
{ "class", required_argument, 0, 'c' },
{ "payload", required_argument, 0, 'p' },
{ "payload-file", required_argument, 0, 'P' },
{ "record-version", required_argument, 0, 'R' },
{ "event-id", required_argument, 0, 'e' },
{ 0, 0, 0, 0 }
};
@@ -60,13 +65,15 @@ static void print_help(void)
printf(" -h, --help Show help options\n");
printf("\n");
printf("Application Options:\n");
printf(" -f, --config-file Path to configuration file (not implemented yet)\n");
printf(" -V, --version Print the program version\n");
printf(" -s, --severity Severity level (1-4) - (default 1)\n");
printf(" -c, --class Classification level_1/level_2/level_3\n");
printf(" -p, --payload Record body (max size = 8k)\n");
printf(" -c, --class Classification level_1/level_2/level_3 (required)\n");
printf(" -p, --payload Record body (max size = 8k) (required)\n");
printf(" -P, --payload-file File to read payload from\n");
printf(" -R, --record-version Version number for format of payload (default 1)\n");
printf(" -e, --event-id Event id to use in the record\n");
printf(" -o, --echo Echo record to stdout\n");
printf(" -n, --no-post Do not post record just print\n");
printf("\n");
}
@@ -91,7 +98,7 @@ int parse_options(int argc, char **argv)
long unsigned int tmp = 0;
int opt;
while ((opt = getopt_long(argc, argv, "hc:Vs:c:p:P:R:", prog_opts, NULL)) != -1) {
while ((opt = getopt_long(argc, argv, "hc:Vs:c:p:P:R:e:on", prog_opts, NULL)) != -1) {
switch (opt) {
case 'h':
print_help();
@@ -140,6 +147,18 @@ int parse_options(int argc, char **argv)
payload_version = (uint32_t)tmp;
break;
case 'e':
opt_event_id = strdup(optarg);
if (opt_event_id == NULL) {
goto fail;
}
break;
case 'o':
opt_echo = true;
break;
case 'n':
opt_nopost = true;
break;
}
}
@@ -150,8 +169,9 @@ fail:
int validate_opts(void)
{
size_t i;
size_t len;
int ret = 0;
const char alphab[] = EVENT_ID_ALPHAB;
/* classification */
if (opt_class == NULL) {
@@ -159,15 +179,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");
@@ -187,6 +207,20 @@ int validate_opts(void)
return ret;
}
/* Event id */
if (opt_event_id) {
size_t result = 0;
if ((result = strlen(opt_event_id)) != EVENT_ID_LEN) {
fprintf(stderr, "Error: event_id length %zu it should have %d characters\n", result, EVENT_ID_LEN);
return ret;
}
if ((result = strspn(opt_event_id, alphab)) != EVENT_ID_LEN) {
fprintf(stderr, "Error: event_id contains invalid character '%c' at position %zu, valid characters are %s\n",
opt_event_id[result], result, alphab);
return ret;
}
}
return 1;
}
@@ -298,30 +332,62 @@ out1:
return ret;
}
int send_record(char *payload)
int instanciate_record(struct telem_ref **t_ref, char *payload)
{
struct telem_ref *t_ref;
int ret = 0;
if (tm_create_record(&t_ref, (uint32_t)severity,
opt_class, payload_version) < 0) {
if ((ret = tm_create_record(t_ref, (uint32_t)severity,
opt_class, payload_version)) < 0) {
goto out1;
}
if (tm_set_payload(t_ref, payload) < 0) {
goto out2;
if ((ret = opt_event_id && tm_set_event_id(*t_ref, opt_event_id)) < 0) {
goto out1;
}
if (tm_send_record(t_ref) >= 0) {
ret = 1;
if ((ret = tm_set_payload(*t_ref, payload)) < 0) {
goto out1;
}
out2:
tm_free_record(t_ref);
out1:
return ret;
}
int send_record(char *payload)
{
struct telem_ref *t_ref = NULL;
int ret = 0;
if ((ret = instanciate_record(&t_ref, payload)) < 0) {
goto out;
}
if ((ret = tm_send_record(t_ref)) < 0) {
goto out;
}
out:
tm_free_record(t_ref);
return ret;
}
int print_record(char *payload)
{
struct telem_ref *t_ref = NULL;
int ret = 0;
int i = 0;
if ((ret = instanciate_record(&t_ref, payload)) != -1) {
for (i = 0; i < NUM_HEADERS; i++) {
fprintf(stdout, "%s", t_ref->record->headers[i]);
}
fprintf(stdout, "%s\n", t_ref->record->payload);
tm_free_record(t_ref);
}
return ret;
}
int main(int argc, char **argv)
{
int ret = EXIT_FAILURE;
@@ -339,7 +405,11 @@ int main(int argc, char **argv)
goto fail;
}
if (!send_record(payload)) {
if (opt_echo == true) {
print_record(payload);
}
if (opt_nopost == false && send_record(payload) != 0) {
goto fail;
}
@@ -348,6 +418,8 @@ fail:
free(config_file);
free(opt_class);
free(opt_payload);
free(opt_event_id);
free(payload);
return ret;
}
+46
View File
@@ -0,0 +1,46 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include "log.h"
#include "common.h"
int delete_record_by_id(char *record_id)
{
int ret = 0;
char *record_path = NULL;
ret = asprintf(&record_path, "%s/%s", RECORD_RETENTION_DIR, record_id);
if (ret == -1) {
return 1;
}
ret = unlink(record_path);
if (ret == -1) {
telem_perror("Error deleting saved record");
}
free(record_path);
return 0;
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+27
View File
@@ -0,0 +1,27 @@
/*
* 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
/**
* Delete record identified by record unique id
*
* @param record_id Unique identifier of entry
*
*/
int delete_record_by_id(char *record_id);
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+3 -3
View File
@@ -27,7 +27,7 @@
#include <errno.h>
#include "spool.h"
#include "telemdaemon.h"
#include "telempostdaemon.h"
#include "log.h"
#include "configuration.h"
#include "util.h"
@@ -221,7 +221,7 @@ void transmit_spooled_record(char *record_path, bool *post_succeeded, long size)
}
//Get rid of trailing newline
strtok(line, "\n");
if (get_header(line, header_name, &headers[i], strlen(header_name))) {
if (get_header(line, header_name, &headers[i])) {
continue;
} else {
telem_log(LOG_ERR, "transmit_spooled_record: Incorrect"
@@ -254,7 +254,7 @@ void transmit_spooled_record(char *record_path, bool *post_succeeded, long size)
goto read_error;
}
*post_succeeded = post_record_http(headers, payload, false);
*post_succeeded = post_record_http(headers, payload);
if (*post_succeeded) {
unlink(record_path);
}
+20 -357
View File
@@ -27,7 +27,9 @@
#include <fcntl.h>
#include <time.h>
#include <malloc.h>
#include <sys/uio.h>
#include "iorecord.h"
#include "telemdaemon.h"
#include "common.h"
#include "util.h"
@@ -41,24 +43,7 @@ void initialize_daemon(TelemDaemon *daemon)
daemon->nfds = 0;
daemon->pollfds = NULL;
daemon->client_head = head;
daemon->bypass_http_post_ts = 0;
daemon->is_spool_valid = false;
initialize_rate_limit(daemon);
daemon->current_spool_size = 0;
}
void initialize_rate_limit(TelemDaemon *daemon)
{
for (int i = 0; i < TM_RATE_LIMIT_SLOTS; i++) {
daemon->record_burst_array[i] = 0;
daemon->byte_burst_array[i] = 0;
}
daemon->rate_limit_enabled = rate_limit_enabled_config();
daemon->record_burst_limit = record_burst_limit_config();
daemon->record_window_length = record_window_length_config();
daemon->byte_burst_limit = byte_burst_limit_config();
daemon->byte_window_length = byte_window_length_config();
daemon->rate_limit_strategy = rate_limit_strategy_config();
daemon->machine_id_override = NULL;
}
client *add_client(client_list_head *client_head, int fd)
@@ -265,25 +250,15 @@ void machine_id_replace(char **machine_header, char *machine_id_override)
void process_record(TelemDaemon *daemon, client *cl)
{
int i = 0;
int ret = 0;
char *headers[NUM_HEADERS];
bool do_spool = false;
bool record_sent = false;
char *tok = NULL;
size_t header_size = 0;
size_t message_size = 0;
char *temp_headers = NULL;
char *msg;
char *body;
int current_minute = 0;
bool record_check_passed = true;
bool byte_check_passed = true;
bool record_burst_enabled = true;
bool byte_burst_enabled = true;
/* Gets the current minute of time */
time_t temp = time(NULL);
struct tm *tm_s = localtime(&temp);
current_minute = tm_s->tm_min;
char *recordpath = NULL;
header_size = *(uint32_t *)cl->buf;
message_size = cl->size - header_size;
@@ -297,7 +272,7 @@ void process_record(TelemDaemon *daemon, client *cl)
for (i = 0; i < NUM_HEADERS; i++) {
const char *header_name = get_header_name(i);
if (get_header(tok, header_name, &headers[i], strlen(header_name))) {
if (get_header(tok, header_name, &headers[i])) {
if (strcmp(header_name, TM_MACHINE_ID_STR) == 0) {
machine_id_replace(&headers[i], daemon->machine_id_override);
}
@@ -311,67 +286,15 @@ void process_record(TelemDaemon *daemon, client *cl)
/* TODO : check if the body is within the limits. */
body = msg + header_size;
if (inside_direct_spool_window(daemon, time(NULL))) {
telem_log(LOG_INFO, "process_record: delivering directly to spool\n");
spool_record(daemon, headers, body);
goto end;
}
if (daemon->record_window_length == -1 ||
daemon->byte_window_length == -1) {
telem_log(LOG_ERR, "Invalid value for window length\n");
/* Save record to stage */
ret = asprintf(&recordpath, "%s/XXXXXX", spool_dir_config());
if (ret == -1) {
telem_log(LOG_ERR, "Failed to allocate memory for record name in staging folder, aborting\n");
exit(EXIT_FAILURE);
}
/* Checks if entirety of rate limiting is enabled */
if (daemon->rate_limit_enabled) {
/* Checks whether record and byte bursts are enabled individually */
record_burst_enabled = burst_limit_enabled(daemon->record_burst_limit);
byte_burst_enabled = burst_limit_enabled(daemon->byte_burst_limit);
if (record_burst_enabled) {
record_check_passed = rate_limit_check(current_minute,
daemon->record_burst_limit, daemon->record_window_length,
daemon->record_burst_array, TM_RECORD_COUNTER);
}
if (byte_burst_enabled) {
byte_check_passed = rate_limit_check(current_minute,
daemon->byte_burst_limit, daemon->byte_window_length,
daemon->byte_burst_array, cl->size);
}
/* If both record and byte burst disabled, rate limiting disabled */
if (!record_burst_enabled && !byte_burst_enabled) {
daemon->rate_limit_enabled = false;
}
}
/* Sends record if rate limiting is disabled, or all checks passed */
if (!daemon->rate_limit_enabled || (record_check_passed && byte_check_passed)) {
/* Send the record as https post */
record_sent = post_record_ptr(headers, body, true);
}
// Get rate-limit strategy
do_spool = spool_strategy_selected(daemon);
// Drop record
if (!record_sent && !do_spool) {
goto end;
}
// Spool Record
else if (!record_sent && do_spool) {
start_network_bypass(daemon);
telem_log(LOG_INFO, "process_record: initializing direct-spool window\n");
spool_record(daemon, headers, body);
} else {
/* Updates rate limiting arrays if record sent */
if (record_burst_enabled) {
rate_limit_update (current_minute, daemon->record_window_length,
daemon->record_burst_array, TM_RECORD_COUNTER);
}
if (byte_burst_enabled) {
rate_limit_update (current_minute, daemon->byte_window_length,
daemon->byte_burst_array, cl->size);
}
}
stage_record(recordpath, headers, body);
free(recordpath);
end:
free(temp_headers);
for (int k = 0; k < i; k++)
@@ -379,234 +302,6 @@ end:
return;
}
bool burst_limit_enabled(int64_t burst_limit)
{
return (burst_limit > -1) ? true : false;
}
bool rate_limit_check(int current_minute, int64_t burst_limit, int
window_length, size_t *array, size_t incValue)
{
size_t count = 0;
/* Variable to determine last minute allowed in burst window */
int window_start = (TM_RATE_LIMIT_SLOTS + (current_minute - window_length + 1))
% TM_RATE_LIMIT_SLOTS;
/* The modulo is not placed in the for loop inself, because if
* the current minute is less than the window start (from wrapping)
* the for loop will never be entered.
*/
/* Counts all elements within burst_window in array */
for (int i = window_start; i < (window_start + window_length); i++) {
count += array[i % TM_RATE_LIMIT_SLOTS];
if ((array[i % TM_RATE_LIMIT_SLOTS] + incValue) >= SIZE_MAX) {
/* Exceeds maximum size array can hold */
return false;
}
}
/* Include current record being processed to count */
count += incValue;
/* Determines whether the count has exceeded the limit or not */
return (count > burst_limit) ? false : true;
}
void rate_limit_update(int current_minute, int window_length, size_t *array,
size_t incValue)
{
/* Variable to determine the amount of zero'd out spots needed */
int blank_slots = (TM_RATE_LIMIT_SLOTS - window_length);
/* Update specified array depending on increment value */
array[current_minute] += incValue;
/* Zero out expired records for record array */
for (int i = current_minute + 1; i < (blank_slots + current_minute + 1); i++) {
array[i % TM_RATE_LIMIT_SLOTS] = 0;
}
}
bool spool_strategy_selected(TelemDaemon *daemon)
{
/* Performs action depending on strategy choosen */
return (strcmp(daemon->rate_limit_strategy, "spool") == 0) ? true : false;
}
bool inside_direct_spool_window(TelemDaemon *daemon, time_t current_time)
{
return (current_time < daemon->bypass_http_post_ts + 1800) ? true : false;
}
void start_network_bypass(TelemDaemon *daemon)
{
daemon->bypass_http_post_ts = time(NULL);
}
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
telem_log(LOG_DEBUG, "Received data:\n%s\n", ptr);
return size * nmemb;
}
bool post_record_http(char *headers[], char *body, bool spool)
{
CURL *curl;
int res = 0;
char *content = "Content-Type: application/text";
struct curl_slist *custom_headers = NULL;
char errorbuf[CURL_ERROR_SIZE];
long http_response = 0;
const char *cert_file = get_cainfo_config();
const char *tid_header = get_tidheader_config();
curl = curl_easy_init();
if (!curl) {
telem_log(LOG_ERR, "curl_easy_init(): Unable to start libcurl"
" easy session, exiting\n");
exit(EXIT_FAILURE);
/* TODO: check if memory needs to be released */
}
// Errors for any curl_easy_* functions will store nice error messages
// in errorbuf, so send log messages with errorbuf contents
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuf);
curl_easy_setopt(curl, CURLOPT_URL, server_addr_config());
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_POST, 1);
#ifdef DEBUG
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
#endif
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
for (int i = 0; i < NUM_HEADERS; i++) {
custom_headers = curl_slist_append(custom_headers, headers[i]);
}
custom_headers = curl_slist_append(custom_headers, tid_header);
// This should be set by probes/libtelemetry in the future
custom_headers = curl_slist_append(custom_headers, content);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, custom_headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(body));
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
if (strlen(cert_file) > 0) {
if (access(cert_file, F_OK) != -1) {
curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file);
telem_log(LOG_INFO, "cafile was set to %s\n", cert_file);
}
}
telem_log(LOG_DEBUG, "Executing curl operation...\n");
errorbuf[0] = 0;
res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_response);
if (res) {
size_t len = strlen(errorbuf);
if (len) {
telem_log(LOG_ERR, "Failed sending record: %s%s", errorbuf,
((errorbuf[len - 1] != '\n') ? "\n" : ""));
} else {
telem_log(LOG_ERR, "Failed sending record: %s\n",
curl_easy_strerror(res));
}
} else if (http_response != 201 && http_response != 200) {
/* 201 means the record was successfully created
* 200 is a generic "ok".
* TODO: Make list of white-listed response codes configurable?
*/
telem_log(LOG_ERR, "Encountered error %ld on the server\n",
http_response);
// We treat HTTP error codes the same as libcurl errors
res = 1;
} else {
telem_log(LOG_INFO, "Record sent successfully\n");
}
curl_slist_free_all(custom_headers);
curl_easy_cleanup(curl);
return res ? false : true;
}
void spool_record(TelemDaemon *daemon, char *headers[], char *body)
{
int ret = 0;
struct stat stat_buf;
int64_t max_spool_size = 0;
char *tmpbuf = NULL;
FILE *tmpfile = NULL;
int tmpfd = 0;
if (!daemon->is_spool_valid) {
/* If spool is not valid, simply drop the record */
return;
}
//check if the size is greater than 1 MB/spool max size
max_spool_size = spool_max_size_config();
if (max_spool_size != -1) {
telem_log(LOG_DEBUG, "Total size of spool dir: %ld\n", daemon->current_spool_size);
if (daemon->current_spool_size < 0) {
telem_log(LOG_ERR, "Error getting spool directory size: %s\n",
strerror(-(int)(daemon->current_spool_size)));
return;
} else if (daemon->current_spool_size >= (max_spool_size * 1024)) {
telem_log(LOG_INFO, "Spool dir full, dropping record\n");
return;
}
}
// create file with record
ret = asprintf(&tmpbuf, "%s/XXXXXX", spool_dir_config());
if (ret == -1) {
telem_log(LOG_ERR, "Failed to allocate memory for record name, aborting\n");
exit(EXIT_FAILURE);
}
tmpfd = mkstemp(tmpbuf);
if (tmpfd == -1) {
telem_perror("Error while creating temp file");
goto spool_err;
}
//fp = fopen(spool_dir_path, const char *mode);
tmpfile = fdopen(tmpfd, "a");
if (!tmpfile) {
telem_perror("Error opening temp file");
close(tmpfd);
if (unlink(tmpbuf)) {
telem_perror("Error deleting temp file");
}
goto spool_err;
}
// write headers
for (int i = 0; i < NUM_HEADERS; i++) {
fprintf(tmpfile, "%s\n", headers[i]);
}
//write body
fprintf(tmpfile, "%s\n", body);
fflush(tmpfile);
fclose(tmpfile);
/* The stat should not fail here unless it is ENOMEM */
if (stat(tmpbuf, &stat_buf) == 0) {
daemon->current_spool_size += (stat_buf.st_blocks * 512);
}
spool_err:
free(tmpbuf);
return;
}
void add_pollfd(TelemDaemon *daemon, int fd, short events)
{
assert(daemon);
@@ -671,7 +366,7 @@ bool get_machine_id(char *machine_id)
return true;
}
int machine_id_write(uint64_t upper, uint64_t lower)
int machine_id_write(char *new_id)
{
FILE *fp;
int ret = 0;
@@ -683,7 +378,7 @@ int machine_id_write(uint64_t upper, uint64_t lower)
goto file_error;
}
ret = fprintf(fp, "%.16" PRIx64 "%.16" PRIx64, upper, lower);
ret = fprintf(fp, "%s", new_id);
if (ret < 0) {
telem_perror("Unable to write to machine id file");
goto file_error;
@@ -700,49 +395,17 @@ file_error:
int generate_machine_id(void)
{
int frandom = -1;
struct stat stat_buf;
int ret = 0;
struct iovec random_id[2];
uint64_t random_id_upper;
uint64_t random_id_lower;
int result = -1;
int result = 0;
char *new_id = NULL;
ret = stat("/dev/urandom", &stat_buf);
if (ret == -1) {
telem_log(LOG_ERR, "Unable to stat urandom device\n");
if ((result = get_random_id(&new_id)) != 0) {
goto rand_err;
}
if (!S_ISCHR(stat_buf.st_mode)) {
telem_log(LOG_ERR, "/dev/urandom is not a character device file\n");
goto rand_err;
}
/* TODO : check for major and minor numbers to be extra sure ??
((stat_buffer.st_rdev == makedev(1, 8)) || (stat_buffer.st_rdev == makedev(1, 9))*/
frandom = open("/dev/urandom", O_RDONLY);
if (frandom < 0) {
telem_perror("Error opening /dev/urandom");
goto rand_err;
}
random_id[0].iov_base = &random_id_upper;
random_id[0].iov_len = 8;
random_id[1].iov_base = &random_id_lower;
random_id[1].iov_len = 8;
if (readv(frandom, random_id, 2) < 0) {
telem_perror("Error while reading /dev/urandom");
goto rand_err;
}
result = machine_id_write(random_id_upper, random_id_lower);
result = machine_id_write(new_id);
rand_err:
if (frandom >= 0) {
close(frandom);
}
free(new_id);
return result;
}
+1 -123
View File
@@ -54,23 +54,6 @@ typedef struct TelemDaemon {
size_t current_alloc;
/* client list head */
client_list_head client_head;
/* Time of last failed post */
time_t bypass_http_post_ts;
/* Rate limit record and byte arrays */
size_t record_burst_array[TM_RATE_LIMIT_SLOTS];
size_t byte_burst_array[TM_RATE_LIMIT_SLOTS];
/* Rate Limit Configurations */
bool rate_limit_enabled;
int64_t record_burst_limit;
int record_window_length;
int64_t byte_burst_limit;
int byte_window_length;
const char *rate_limit_strategy;
bool is_spool_valid;
long current_spool_size;
char *machine_id_override;
} TelemDaemon;
@@ -81,13 +64,6 @@ typedef struct TelemDaemon {
*/
void initialize_daemon(TelemDaemon *daemon);
/**
* Initialize rate-limiting in the daemon
*
* @param daemon A pointer to the daemon structure
*/
void initialize_rate_limit(TelemDaemon *daemon);
/**
* Add poll fd struct to the array of pollfds.
*
@@ -170,105 +146,6 @@ void terminate_client(TelemDaemon *daemon, client *cl, nfds_t index);
*/
void process_record(TelemDaemon *daemon, client *cl);
/**
* Check if burst limiting is enabled
*
* @param burst_limit Burst limit amount. If negative rate limiting
* is disabled
*
* @return true if rate limiting for burst_limit variable is enabled
*/
bool burst_limit_enabled(int64_t burst_limit);
/**
* Checks whether record does not exceed the burst threshold
*
* @param current_minute The current minute of time the record processed
* @param burst_limit Amount of records that can be passed within a window
* @param window_length Window of time burst limit refers to
* @param array Pointer to array that stores record or byte count
* @param incValue Amount to increase array slot by
*
* @return true if record does not exceed threshold, and false otherwise
*/
bool rate_limit_check(int current_minute, int64_t burst_limit, int
window_length, size_t *array, size_t incValue);
/**
* Updates rate limit arrays with passing record information
*
* @param current_minute The current minute of time the record processed
* @param window_length Window of time burst limit refers to
* @param array Pointer to array that stores record or byte count
* @param incValue Amount to increase array slot by
*
*/
void rate_limit_update(int current_minute, int window_length,
size_t *array, size_t incValue);
/**
* Checks if the spool strategy is chosen for rate-limiting
*
* @param daemon Pointer to the daemon
*
* @return true if spool is choosen false if drop is choosen
*/
bool spool_strategy_selected(TelemDaemon *daemon);
/**
* Check if daemon should direct-spool after encountering network issues.
*
* @param daemon Pointer to the daemon
* @param time The time as time_t (usually from time())
*
* @return true if inside direct spool window, false otherwise.
*/
bool inside_direct_spool_window(TelemDaemon *daemon, time_t time);
/**
* Set the start time of the direct spool window. Will use the
* current time.
*
* @param daemon Pointer to the daemon
*
* @return no return value.
*/
void start_network_bypass(TelemDaemon *daemon);
/**
* Function pointer for posting the records to the server. Replaced by a
* mock function in the unit tests.
*
* @param header_values The header data read from the socket
* @param body The payload of the record read
* @param spool Indicate whether the record should be spooled if not
* successfully delivered
*
* @return true if the record was successfully posted
*/
extern bool (*post_record_ptr)(char *headers[], char *body, bool spool);
/**
* Post records on a http connection to server
*
* @param header_values The header data read from the socket
* @param body The payload of the record read
* @param spool Indicate whether the record should be spooled if not
* successfully delivered
*
* @return true if the record was successfully posted
*/
bool post_record_http(char *header_values[], char *body, bool spool);
/**
* Spool the record in case it cannot be delivered to the server
*
* @param headers The headers for the record
* @param body The payload of the record
*
*/
void spool_record(TelemDaemon *daemon, char *headers[], char *body);
/**
* Get random machine id stored in file
*
@@ -290,4 +167,5 @@ int update_machine_id(void);
* or if the file is empty
*/
char *read_machine_id_override(void);
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+249 -27
View File
@@ -15,6 +15,7 @@
*/
#define _GNU_SOURCE
#include <string.h>
#include <stdio.h>
#include <sys/stat.h>
@@ -30,6 +31,7 @@
#include <inttypes.h>
#include <ctype.h>
#include "util.h"
#include "common.h"
#include "configuration.h"
#include "telemetry.h"
@@ -137,38 +139,14 @@ static int set_severity_header(struct telem_ref *t_ref, uint32_t severity)
*/
static int set_classification_header(struct telem_ref *t_ref, char *classification)
{
size_t i, j;
int slashes = 0;
int x = 0;
j = strlen(classification);
if (j > MAX_CLASS_LENGTH) {
return -EINVAL;
}
for (i = 0, x = 0; i <= (j - 1); i++, x++) {
if (classification[i] == '/') {
slashes++;
x = 0;
} else {
if (x > MAX_SUBCAT_LENGTH) {
return -EINVAL;
}
}
}
if (slashes != 2) {
#ifdef DEBUG
fprintf(stderr, "ERR: Classification string should have two /s.\n");
#endif
if (validate_classification(classification) == 1) {
return -EINVAL;
}
return set_header(&(t_ref->record->headers[TM_CLASSIFICATION]),
TM_CLASSIFICATION_STR, classification,
&(t_ref->record->header_size));
}
/**
@@ -338,7 +316,7 @@ static int set_system_build_header(struct telem_ref *t_ref)
/**
* Sets the header reporting the machine_id. Note, the machine_id is really
* managed by the telemetrics daemon (telemd), and populated by the daemon
* managed by the telemetrics daemon (telemprobd), and populated by the daemon
* as it delivers the records. Consequently, the header is simply set to a
* dummy value of 0xFFFFFFF.
*
@@ -400,6 +378,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 - 3] = '\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 +531,118 @@ 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 event_id header, this id is an identifier that multiple
* records can share. This means that one event can lead to multiple
* records.
*
* @param t_ref Telemetry Record reference obtained from tm_create_record.
*
* @return 0 if successful, -1 on failure
*
*/
static int set_event_id_header(struct telem_ref *t_ref)
{
int rc = 0;
char *buff = NULL;
rc = get_random_id(&buff);
if (rc == 0) {
rc = set_header(
&(t_ref->record->headers[TM_EVENT_ID]),
TM_EVENT_ID_STR, buff,
&(t_ref->record->header_size));
}
free(buff);
return rc;
}
/**
* 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 +876,30 @@ 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++;
if ((ret = set_event_id_header(t_ref)) < 0) {
goto free_and_fail;
}
i++; /* Not necessary, but including for future expansion */
return ret;
@@ -859,7 +1029,59 @@ int tm_set_payload(struct telem_ref *t_ref, char *payload)
}
/**
* Write nbytes from buf to fd. Used to send records to telemd.
* Checks for id to have length = 32 characters
* and hexadecimal characters only.
*
* @param id A pointer to string to be checked
*
* @return 0 on success, 1 on failure
*
*/
static int validate_event_id(char *id)
{
int rc = 0;
const char alphab[] = EVENT_ID_ALPHAB;
// make sure is not null
if (!id) {
return 1;
}
// 32 - 32 = 0
if ((rc = (int)(strlen(id) - EVENT_ID_LEN)) != 0) {
return 1;
}
// verify alphabet
// strspn checks for characters in alphab
if (strspn(id, alphab) != EVENT_ID_LEN) {
return 1;
}
return rc;
}
int tm_set_event_id(struct telem_ref *t_ref, char *event_id)
{
int rc = -1;
if (!validate_event_id(event_id)) {
if (t_ref && t_ref->record && t_ref->record->headers) {
// free default id before overriding
free(t_ref->record->headers[TM_EVENT_ID]);
// set new event_id
if (asprintf(&(t_ref->record->headers[TM_EVENT_ID]),
"%s: %s\n", TM_EVENT_ID_STR, event_id) > 0) {
rc = 0;
}
}
}
return rc;
}
/**
* Write nbytes from buf to fd. Used to send records to telemprobd.
*
* @param fd Socket fd obtained from tm_get_socket.
* @param buf Data to be written to the socket.
+17 -1
View File
@@ -31,7 +31,10 @@
* - a library, libtelemetry, that telemetrics probes use to create telemetrics
* records and send them to the daemon for further processing.
*
* - a daemon, telemd, that prepares the records to send to a telemetrics server
* - telemprobd, a daemon that handles communication with probes and forwards
* records to a second daemon.
*
* - telempostd, this daemon sends telemetry records to a telemetrics server
* (not included in this source tree), or spools the records on disk in case
* it's unable to successfully deliver them.
*
@@ -85,6 +88,19 @@ void tm_set_config_file(char *c_file);
int tm_create_record(struct telem_ref **t_ref, uint32_t severity,
char *classification, uint32_t payload_version);
/**
* Sets the event_id to a user defined id
*
* @param t_ref A pointer to a telem_ref struct that was previously initialized
* this struct will have an event_id already initialized and it will be
* overriden by a user provided event_id
* @param event_id A pointer to a string that contains the 32 character length
* event_id
*
* @return 0 on success, or a negative errno-style value on error
*/
int tm_set_event_id(struct telem_ref *t_ref, char *event_id);
/**
* Set the payload field of a telemetrics record
*
+5
View File
@@ -16,3 +16,8 @@ TM_3_0_0 {
tm_set_payload;
tm_send_record;
} TM_2_0_0;
TM_4_0_0 {
global:
tm_set_event_id;
} TM_3_0_0;
+754
View File
@@ -0,0 +1,754 @@
/*
* 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 <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <assert.h>
#include <signal.h>
#include <dirent.h>
#include <malloc.h>
#include <stdbool.h>
#include <sys/stat.h>
#include <curl/curl.h>
#include <sys/signalfd.h>
#include "log.h"
#include "util.h"
#include "spool.h"
#include "iorecord.h"
#include "retention.h"
#include "telempostdaemon.h"
/* spool window check */
static bool inside_direct_spool_window(TelemPostDaemon *daemon, time_t current_time)
{
return (current_time < daemon->bypass_http_post_ts + 1800) ? true : false;
}
/* set http bypass */
static void start_network_bypass(TelemPostDaemon *daemon)
{
daemon->bypass_http_post_ts = time(NULL);
}
/* burst limit check */
bool burst_limit_enabled(int64_t burst_limit)
{
return (burst_limit > -1) ? true : false;
}
/* rate limit check */
bool rate_limit_check(int current_minute, int64_t burst_limit, int
window_length, size_t *array, size_t incValue)
{
size_t count = 0;
/* Variable to determine last minute allowed in burst window */
int window_start = (TM_RATE_LIMIT_SLOTS + (current_minute - window_length + 1))
% TM_RATE_LIMIT_SLOTS;
/* The modulo is not placed in the for loop itself, because if
* the current minute is less than the window start (from wrapping)
* the for loop will never be entered.
*/
/* Counts all elements within burst_window in array */
for (int i = window_start; i < (window_start + window_length); i++) {
count += array[i % TM_RATE_LIMIT_SLOTS];
if ((array[i % TM_RATE_LIMIT_SLOTS] + incValue) >= SIZE_MAX) {
/* Exceeds maximum size array can hold */
return false;
}
}
/* Include current record being processed to count */
count += incValue;
/* Determines whether the count has exceeded the limit or not */
return (count > burst_limit) ? false : true;
}
/* spool strategy check */
bool spool_strategy_selected(TelemPostDaemon *daemon)
{
/* Performs action depending on strategy choosen */
return (strcmp(daemon->rate_limit_strategy, "spool") == 0) ? true : false;
}
/* rate limit check */
void rate_limit_update(int current_minute, int window_length, size_t *array,
size_t incValue)
{
/* Variable to determine the amount of zero'd out spots needed */
int blank_slots = (TM_RATE_LIMIT_SLOTS - window_length);
/* Update specified array depending on increment value */
array[current_minute] += incValue;
/* Zero out expired records for record array */
for (int i = current_minute + 1; i < (blank_slots + current_minute + 1); i++) {
array[i % TM_RATE_LIMIT_SLOTS] = 0;
}
}
static void set_pollfd(TelemPostDaemon *daemon, int fd, enum fdindex i, short events)
{
assert(daemon);
assert(fd);
daemon->pollfds[i].fd = fd;
daemon->pollfds[i].events = events;
daemon->pollfds[i].revents = 0;
}
static void initialize_signals(TelemPostDaemon *daemon)
{
int sigfd;
sigset_t mask;
sigemptyset(&mask);
if (sigaddset(&mask, SIGHUP) != 0) {
telem_perror("Error adding signal SIGHUP to mask");
exit(EXIT_FAILURE);
}
if (sigaddset(&mask, SIGINT) != 0) {
telem_perror("Error adding signal SIGINT to mask");
exit(EXIT_FAILURE);
}
if (sigaddset(&mask, SIGTERM) != 0) {
telem_perror("Error adding signal SIGTERM to mask");
exit(EXIT_FAILURE);
}
if (sigaddset(&mask, SIGPIPE) != 0) {
telem_perror("Error adding signal SIGPIPE to mask");
exit(EXIT_FAILURE);
}
if (sigprocmask(SIG_BLOCK, &mask, NULL) == -1) {
telem_perror("Error changing signal mask with SIG_BLOCK");
exit(EXIT_FAILURE);
}
sigfd = signalfd(-1, &mask, 0);
if (sigfd == -1) {
telem_perror("Error creating the signalfd");
exit(EXIT_FAILURE);
}
daemon->sfd = sigfd;
set_pollfd(daemon, sigfd, signlfd, POLLIN);
}
static void initialize_rate_limit(TelemPostDaemon *daemon)
{
for (int i = 0; i < TM_RATE_LIMIT_SLOTS; i++) {
daemon->record_burst_array[i] = 0;
daemon->byte_burst_array[i] = 0;
}
daemon->rate_limit_enabled = rate_limit_enabled_config();
daemon->record_burst_limit = record_burst_limit_config();
daemon->record_window_length = record_window_length_config();
daemon->byte_burst_limit = byte_burst_limit_config();
daemon->byte_window_length = byte_window_length_config();
daemon->rate_limit_strategy = rate_limit_strategy_config();
}
static void initialize_record_delivery(TelemPostDaemon *daemon)
{
daemon->record_retention_enabled = record_retention_enabled_config();
daemon->record_server_delivery_enabled = record_server_delivery_enabled_config();
}
void initialize_daemon(TelemPostDaemon *daemon)
{
assert(daemon);
daemon->bypass_http_post_ts = 0;
daemon->is_spool_valid = is_spool_valid();
daemon->record_journal = open_journal(JOURNAL_PATH);
daemon->fd = inotify_init();
if (daemon->fd < 0) {
telem_perror("Error initializing inotify");
exit(EXIT_FAILURE);
}
daemon->wd = inotify_add_watch(daemon->fd, spool_dir_config(), IN_CLOSE_WRITE);
initialize_signals(daemon);
set_pollfd(daemon, daemon->fd, watchfd, POLLIN);
initialize_rate_limit(daemon);
initialize_record_delivery(daemon);
/* Register record retention delete action as a callback to prune entry */
if (daemon->record_journal != NULL && daemon->record_retention_enabled) {
daemon->record_journal->prune_entry_callback = &delete_record_by_id;
}
daemon->current_spool_size = 0;
}
size_t write_callback(char *ptr, size_t size, size_t nmemb, void *userdata)
{
telem_log(LOG_DEBUG, "Received data:\n%s\n", ptr);
return size * nmemb;
}
bool post_record_http(char *headers[], char *body)
{
CURL *curl;
int res = 0;
char *content = "Content-Type: application/text";
struct curl_slist *custom_headers = NULL;
char errorbuf[CURL_ERROR_SIZE];
long http_response = 0;
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"
" easy session, exiting\n");
exit(EXIT_FAILURE);
/* TODO: check if memory needs to be released */
}
// Errors for any curl_easy_* functions will store nice error messages
// in errorbuf, so send log messages with errorbuf contents
curl_easy_setopt(curl, CURLOPT_ERRORBUFFER, errorbuf);
curl_easy_setopt(curl, CURLOPT_URL, server_addr_config());
curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 5L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 10L);
curl_easy_setopt(curl, CURLOPT_POST, 1);
#ifdef DEBUG
curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
#endif
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
for (int i = 0; i < NUM_HEADERS; i++) {
custom_headers = curl_slist_append(custom_headers, headers[i]);
}
custom_headers = curl_slist_append(custom_headers, tid_header);
// This should be set by probes/libtelemetry in the future
custom_headers = curl_slist_append(custom_headers, content);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, custom_headers);
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, strlen(body));
curl_easy_setopt(curl, CURLOPT_USE_SSL, CURLUSESSL_TRY);
if (strlen(cert_file) > 0) {
if (access(cert_file, F_OK) != -1) {
curl_easy_setopt(curl, CURLOPT_CAINFO, cert_file);
telem_log(LOG_INFO, "cafile was set to %s\n", cert_file);
}
}
telem_log(LOG_DEBUG, "Executing curl operation...\n");
errorbuf[0] = 0;
res = curl_easy_perform(curl);
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_response);
if (res) {
size_t len = strlen(errorbuf);
if (len) {
telem_log(LOG_DEBUG, "Failed sending record: %s%s", errorbuf,
((errorbuf[len - 1] != '\n') ? "\n" : ""));
} else {
telem_log(LOG_DEBUG, "Failed sending record: %s\n",
curl_easy_strerror(res));
}
} else if (http_response != 201 && http_response != 200) {
/* 201 means the record was successfully created
* 200 is a generic "ok".
*/
telem_log(LOG_ERR, "Encountered error %ld on the server\n",
http_response);
// We treat HTTP error codes the same as libcurl errors
res = 1;
} else {
telem_log(LOG_INFO, "Record sent successfully\n");
}
curl_slist_free_all(custom_headers);
curl_easy_cleanup(curl);
curl_global_cleanup();
return res ? false : true;
}
static void save_local_copy(TelemPostDaemon *daemon, char *body)
{
int ret = 0;
char *tmpbuf = NULL;
FILE *tmpfile = NULL;
if (daemon == NULL || daemon->record_journal == NULL ||
daemon->record_journal->latest_record_id == NULL) {
return;
}
ret = asprintf(&tmpbuf, "%s/%s", RECORD_RETENTION_DIR,
daemon->record_journal->latest_record_id);
if (ret == -1) {
telem_log(LOG_ERR, "Failed to allocate memory for record full path, aborting\n");
return;
}
tmpfile = fopen(tmpbuf, "w");
if (!tmpfile) {
telem_perror("Error opening local record copy temp file");
goto save_err;
}
// Save body
fprintf(tmpfile, "%s\n", body);
fclose(tmpfile);
save_err:
free(tmpbuf);
return;
}
static void save_entry_to_journal(TelemPostDaemon *daemon, time_t t_stamp, char *headers[])
{
char *classification_value;
char *event_id_value;
if (get_header_value(headers[TM_CLASSIFICATION], &classification_value) &&
get_header_value(headers[TM_EVENT_ID], &event_id_value)) {
if (new_journal_entry(daemon->record_journal, classification_value, t_stamp, event_id_value) != 0) {
telem_log(LOG_INFO, "new_journal_entry in process_record: failed saving record entry\n");
}
}
free(classification_value);
free(event_id_value);
}
/* Check window length conf values */
static bool windows_length_value_check(TelemPostDaemon *daemon)
{
if (daemon->record_window_length == -1 ||
daemon->byte_window_length == -1) {
telem_log(LOG_ERR, "Invalid value for window length\n");
return false;
}
return true;
}
/* Rate limiting checks */
static void rate_limit_checks(TelemPostDaemon *daemon, bool *record_check_passed, bool *byte_check_passed)
{
time_t temp = time(NULL);
struct tm *tm_s = localtime(&temp);
int current_minute;
current_minute = tm_s->tm_min;
bool record_burst_enabled = burst_limit_enabled(daemon->record_burst_limit);
bool byte_burst_enabled = burst_limit_enabled(daemon->byte_burst_limit);
/* Checks if entirety of rate limiting is enabled */
if (daemon->rate_limit_enabled) {
/* Checks whether record and byte bursts are enabled individually */
record_burst_enabled = burst_limit_enabled(daemon->record_burst_limit);
byte_burst_enabled = burst_limit_enabled(daemon->byte_burst_limit);
if (record_burst_enabled) {
*record_check_passed = rate_limit_check(current_minute,
daemon->record_burst_limit, daemon->record_window_length,
daemon->record_burst_array, TM_RECORD_COUNTER);
}
if (byte_burst_enabled) {
*byte_check_passed = rate_limit_check(current_minute,
daemon->byte_burst_limit, daemon->byte_window_length,
daemon->byte_burst_array, RECORD_SIZE_LEN);
}
/* If both record and byte burst disabled, rate limiting disabled */
if (!record_burst_enabled && !byte_burst_enabled) {
daemon->rate_limit_enabled = false;
}
}
}
/* Wrapper for save local copy */
static void apply_retention_policies(TelemPostDaemon *daemon, char *body)
{
if (daemon->record_retention_enabled) {
save_local_copy(daemon, body);
}
}
/* Deliver record to backend if rate limiting policies are met otherwise
* spool record for future delivery */
static bool deliver_record(TelemPostDaemon *daemon, char *headers[], char *body)
{
bool ret = false;
int current_minute;
bool do_spool = false;
bool record_sent = false;
time_t temp = time(NULL);
struct tm *tm_s = localtime(&temp);
current_minute = tm_s->tm_min;
/* Checks flags */
bool record_check_passed = true;
bool byte_check_passed = true;
bool record_burst_enabled = burst_limit_enabled(daemon->record_burst_limit);
bool byte_burst_enabled = burst_limit_enabled(daemon->byte_burst_limit);
/* Perform record and byte rate limiting checks */
rate_limit_checks(daemon, &record_check_passed, &byte_check_passed);
/* Sends record if rate limiting is disabled, or all checks passed */
if (!daemon->rate_limit_enabled || (record_check_passed && byte_check_passed)) {
/* Send the record as https post */
record_sent = post_record_ptr(headers, body);
/**
* This is the only point where an error condition could be returned
* if the record was not sent
* */
ret = record_sent;
}
// Get rate-limit strategy
do_spool = spool_strategy_selected(daemon);
// Drop record
if (!record_sent && !do_spool) {
// Not an error condition
ret = true;
}
// Spool Record
else if (!record_sent && do_spool) {
start_network_bypass(daemon);
telem_log(LOG_INFO, "process_record: initializing direct-spool window\n");
// False will keep record around
ret = false;
} else {
/* Updates rate limiting arrays if record sent */
if (record_burst_enabled) {
rate_limit_update(current_minute, daemon->record_window_length,
daemon->record_burst_array, TM_RECORD_COUNTER);
}
if (byte_burst_enabled) {
rate_limit_update(current_minute, daemon->byte_window_length,
daemon->byte_burst_array, RECORD_SIZE_LEN);
}
}
return ret;
}
bool process_staged_record(char *filename, bool is_retry, TelemPostDaemon *daemon)
{
int k;
bool ret = false;
char *headers[NUM_HEADERS];
char *body = NULL;
struct stat buf;
time_t current_time = time(NULL);
int64_t max_spool_size = 0;
for (k = 0; k < NUM_HEADERS; k++) {
headers[k] = NULL;
}
/** Load record **/
if ((ret = read_record(filename, headers, &body)) == false) {
telem_log(LOG_WARNING, "unable to read record\n");
ret = true; // Record corrupted? true will remove record
goto end_processing_file;
}
/** Get file information **/
if (stat(filename, &buf) == -1) {
telem_perror("Processing staged file unable to stat record in spool");
ret = true; // true to remove it
goto end_processing_file;
}
/** Update spool directory size **/
(daemon->current_spool_size) += (buf.st_blocks * 512);
/** Check that record is not expired **/
if (!S_ISREG(buf.st_mode) ||
(current_time - buf.st_mtime > (record_expiry_config() * 60)) ||
(buf.st_uid != getuid())) {
ret = true; // Expired, true to remove it
goto end_processing_file;
}
/* Retries should not be recorded */
if (is_retry == false) {
/** Journal entry **/
save_entry_to_journal(daemon, current_time, headers);
/** Record retention **/
apply_retention_policies(daemon, body);
}
/** Record delivery **/
if (!daemon->record_server_delivery_enabled) {
#ifdef DEBUG
telem_log(LOG_WARNING, "record server delivery disabled\n");
#endif
// Not an error condition
ret = true;
goto end_processing_file;
}
/** Spool policies **/
if (inside_direct_spool_window(daemon, time(NULL))) {
#ifdef DEBUG
telem_log(LOG_INFO, "process_record: delivering directly to spool\n");
#endif
/* Check spool max size conf */
max_spool_size = spool_max_size_config();
if (max_spool_size != -1 &&
daemon->current_spool_size >= (max_spool_size * 1024)) {
// Drop record
telem_log(LOG_INFO, "Spool dir full, dropping record\n");
ret = true;
} else {
// Keep record, non error condition
ret = false;
}
goto end_processing_file;
}
/** Check window_length **/
if (windows_length_value_check(daemon) == false) {
exit(EXIT_FAILURE);
}
/** Deliver or spool **/
ret = deliver_record(daemon, headers, body);
end_processing_file:
/** Update spool size if record will be removed **/
if (ret) {
(daemon->current_spool_size) -= (buf.st_blocks * 512);
}
telem_log(LOG_DEBUG, "spool_size: %ld\n", daemon->current_spool_size);
free(body);
for (k = 0; k < NUM_HEADERS; k++) {
free(headers[k]);
}
return ret;
}
static int directory_dot_filter(const struct dirent *entry)
{
if ((strcmp(entry->d_name, ".") == 0) || (strcmp(entry->d_name, "..") == 0)) {
return 0;
} else {
return 1;
}
}
int staging_records_loop(TelemPostDaemon *daemon)
{
int ret;
int processed;
int numentries;
struct dirent **namelist;
numentries = scandir(spool_dir_config(), &namelist, directory_dot_filter, NULL);
processed = 0;
if (numentries == 0) {
telem_log(LOG_DEBUG, "No entries in staging\n");
return numentries;
} else if (numentries < 0) {
telem_perror("Error while scanning staging");
return numentries;
}
for (int i = 0; i < numentries; i++) {
char *record_path;
telem_log(LOG_DEBUG, "Processing staged record: %s\n",
namelist[i]->d_name);
ret = asprintf(&record_path, "%s/%s", spool_dir_config(), namelist[i]->d_name);
if (ret == -1) {
telem_log(LOG_ERR, "Failed to allocate memory for staging record full path\n");
exit(EXIT_FAILURE);
}
if (process_staged_record(record_path, true, daemon)) {
unlink(record_path);
processed++;
}
free(record_path);
}
for (int i = 0; i < numentries; i++) {
free(namelist[i]);
}
free(namelist);
return numentries - processed;
}
void run_daemon(TelemPostDaemon *daemon)
{
int ret;
int retry_attempt = MAX_RETRY_ATTEMPTS;
int spool_process_time = spool_process_time_config();
bool daemon_recycling_enabled = daemon_recycling_enabled_config();
time_t last_spool_run_time = time(NULL);
time_t last_daemon_start_time = time(NULL);
assert(daemon);
assert(daemon->pollfds);
assert(daemon->pollfds[signlfd].fd);
assert(daemon->pollfds[watchfd].fd);
/* Post at boot failed initialize retries variable */
if (daemon->bypass_http_post_ts != 0) {
retry_attempt = 1;
}
while (1) {
int retry_delay = spool_process_time;
malloc_trim(0);
if (retry_attempt < MAX_RETRY_ATTEMPTS) {
retry_delay = retry_attempt * retry_attempt;
daemon->bypass_http_post_ts = 0;
telem_log(LOG_INFO, "Record delivery failed will retry in %d seconds",
retry_delay);
}
ret = poll(daemon->pollfds, NFDS, retry_delay * 1000);
if (ret == -1) {
telem_perror("Failed to poll daemon file descriptors");
break;
} else if (ret != 0) {
if (daemon->pollfds[signlfd].revents != 0) {
struct signalfd_siginfo fdsi;
ssize_t s;
s = read(daemon->sfd, &fdsi, sizeof(struct signalfd_siginfo));
if (s != sizeof(struct signalfd_siginfo)) {
telem_perror("Error while reading from the signal"
"file descriptor");
exit(EXIT_FAILURE);
}
if (fdsi.ssi_signo == SIGTERM || fdsi.ssi_signo == SIGINT) {
telem_log(LOG_INFO, "Received either a \
SIGINT/SIGTERM signal\n");
break;
}
} else if (daemon->pollfds[watchfd].revents != 0) {
int ret = 0;
ssize_t i = 0;
ssize_t length = 0;
char buffer[BUFFER_LEN];
length = read(daemon->fd, buffer, BUFFER_LEN);
if (length < 0) {
telem_perror("Error while reading from inotify"
"watcher");
exit(EXIT_FAILURE);
}
while (i < length) {
struct inotify_event *event = (struct inotify_event *)&buffer[i];
if (event->len) {
if (event->mask & IN_CLOSE_WRITE && !(event->mask & IN_ISDIR)) {
char *record_name = NULL;
/* Retrieve foldername from watch id? */
ret = asprintf(&record_name, "%s/%s", spool_dir_config(), event->name);
if (ret == -1) {
telem_log(LOG_ERR, "Failed to allocate memory for record full path, aborting\n");
exit(EXIT_FAILURE);
}
/* Process inotify event */
if (process_staged_record(record_name, false, daemon)) {
unlink(record_name);
}
free(record_name);
}
}
i += (ssize_t)EVENT_SIZE + event->len;
}
}
} else {
time_t now = time(NULL);
/* time to recycle the daemon has elapsed*/
if (daemon_recycling_enabled &&
difftime(now, last_daemon_start_time) >= TM_DAEMON_EXIT_TIME) {
/* Exit */
telem_log(LOG_INFO, "Telemetry post daemon exiting for recycling\n");
break;
}
/* Check if this is a retry */
if (retry_attempt < MAX_RETRY_ATTEMPTS) {
if (staging_records_loop(daemon) == 0) {
retry_attempt = MAX_RETRY_ATTEMPTS + 1;
} else {
retry_attempt++;
}
} else if (retry_attempt == MAX_RETRY_ATTEMPTS) {
retry_attempt = MAX_RETRY_ATTEMPTS + 1;
telem_log(LOG_ERR, "Record deliver failed after %d attempts",
MAX_RETRY_ATTEMPTS);
}
/* Check spool */
if (difftime(now, last_spool_run_time) >= spool_process_time) {
spool_records_loop(&(daemon->current_spool_size));
last_spool_run_time = time(NULL);
}
}
/* Check journal records and prune if needed */
ret = prune_journal(daemon->record_journal, JOURNAL_TMPDIR);
if (ret != 0) {
telem_log(LOG_WARNING, "Unable to prune journal\n");
}
}
}
void close_daemon(TelemPostDaemon *daemon)
{
if (daemon->fd) {
if (daemon->wd) {
inotify_rm_watch(daemon->fd, daemon->wd);
}
close(daemon->fd);
}
close_journal(daemon->record_journal);
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+134
View File
@@ -0,0 +1,134 @@
/*
* 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 EVENT_SIZE sizeof(struct inotify_event)
#define BUFFER_LEN 1024 * (EVENT_SIZE + 16)
#define NFDS 2
#define TM_RATE_LIMIT_SLOTS (1 /*h*/ * 60 /*m*/)
#define TM_RECORD_COUNTER (1)
#define MAX_RETRY_ATTEMPTS 8
#include <poll.h>
#include <stdbool.h>
#include <sys/inotify.h>
#include "common.h"
#include "journal/journal.h"
#include "configuration.h"
enum fdindex {signlfd, watchfd};
typedef struct TelemPostDaemon {
int fd;
int wd;
int sfd;
char event_buffer[BUFFER_LEN];
struct pollfd pollfds[NFDS];
/* Telemetry Journal*/
TelemJournal *record_journal;
/* Time of last failed post */
time_t bypass_http_post_ts;
/* Rate limit record and byte arrays */
size_t record_burst_array[TM_RATE_LIMIT_SLOTS];
size_t byte_burst_array[TM_RATE_LIMIT_SLOTS];
/* Rate Limit Configurations */
bool rate_limit_enabled;
int64_t record_burst_limit;
int record_window_length;
int64_t byte_burst_limit;
int byte_window_length;
const char *rate_limit_strategy;
/* Spool configuration */
bool is_spool_valid;
long current_spool_size;
/* Record local copy and delivery */
bool record_retention_enabled;
bool record_server_delivery_enabled;
} TelemPostDaemon;
/**
* Initializes daemon
*
* @param daemon a pointer to telemetry post daemon
*/
void initialize_daemon(TelemPostDaemon *daemon);
/**
* Starts daemon
*
* @param daemon a pointer to telemetry post daemon
*/
void run_daemon(TelemPostDaemon *daemon);
/**
* Cleans up inotify descriptors
*
* @param daemon a pointer to telemetry post daemon
*/
void close_daemon(TelemPostDaemon *daemon);
/**
* Processed record written on disk
*
* @param filename a pointor to record on disk
* @param is_retry a boolean value that indicates if
* the record has been previously processed.
* @param daemon post to telemetry post daemon
*/
bool process_staged_record(char *filename, bool is_retry, TelemPostDaemon *daemon);
/**
* Scans staging directory to process files that were
* missed by file watcher
*
* @param daemon a pointer to telemetry post daemon
* @return the number of records that were removed from spool
*/
int staging_records_loop(TelemPostDaemon *daemon);
/**
* Posts a record to backend
*
* @param headers a pointer to an array with keys and values
* @param body a pointer to the payload
*/
bool post_record_http(char *headers[], char *body);
/**
* Pointer to function to isolate backend call during
* unit testing.
*
* @param headers pointer to array of keys
* @param body a pinter to payload
* */
extern bool (*post_record_ptr)(char *headers[], char *body);
/** Helper functions **/
/* rate limit check */
bool rate_limit_check(int current_minute, int64_t burst_limit,
int window_length, size_t *array, size_t incValue);
/* burst limit check */
bool burst_limit_enabled(int64_t burst_limit);
/* rate limit check */
void rate_limit_update(int current_minute, int window_length, size_t *array,
size_t incValue);
/* spool strategy check */
bool spool_strategy_selected(TelemPostDaemon *daemon);
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+107 -1
View File
@@ -16,20 +16,28 @@
#define _GNU_SOURCE
#define RANDOM_ID_LEN 32
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
#include <assert.h>
#include <stdlib.h>
#include <inttypes.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
#include <errno.h>
#include "common.h"
#include "util.h"
bool get_header(const char *haystack, const char *needle, char **line, size_t len)
bool get_header(const char *haystack, const char *needle, char **line)
{
size_t len = strlen(needle);
if (haystack && !strncmp(haystack, needle, len)) {
*line = strdup(haystack);
return true;
@@ -37,6 +45,28 @@ bool get_header(const char *haystack, const char *needle, char **line, size_t le
return false;
}
bool get_header_value(const char *header, char **value)
{
char *sep = NULL;
*value = NULL;
if (header == NULL) {
return false;
}
if ((sep = strchr(header, ':')) != NULL) {
sep++;
// skip space after colon if there's one
if (*sep == ' ') {
sep++;
}
*value = strdup(sep);
}
return (bool)(value != NULL);
}
void *reallocate(void **addr, size_t *allocated, size_t requested)
{
void *newaddr;
@@ -107,4 +137,80 @@ long get_directory_size(const char *dir_path)
return total_size;
}
/**
* Generates a 32 characters random id
*
* @param buffer pointer to allocate and copy data
*
*/
int get_random_id(char **buff)
{
int result = -1;
int frandom = -1;
uint64_t random_id[2] = { '\0' };
frandom = open("/dev/urandom", O_RDONLY);
if (frandom < 0) {
return -1;
}
if (read(frandom, &random_id, sizeof(random_id)) == sizeof(random_id)) {
if (asprintf(buff, "%.16" PRIx64 "%.16" PRIx64, random_id[0], random_id[1]) == RANDOM_ID_LEN) {
result = 0;
}
}
close(frandom);
return result;
}
/**
* Validate classification value. A valid classification
* is a string with 2 slashes, with max length of
* MAX_CLASS_LENGTH and strings betweeb slashes should
* have a max length of MAX_SUBCAT_LENGTH.
*
* @param classification A pointer to classification value
* to be validated.
*
* @return 0 on sucess and 1 on failure
*/
int validate_classification(char *classification)
{
size_t i, j;
int slashes = 0;
int x = 0;
if (classification == NULL) {
return 1;
}
j = strlen(classification);
if (j > MAX_CLASS_LENGTH) {
return 1;
}
for (i = 0, x = 0; i <= (j - 1); i++, x++) {
if (classification[i] == '/') {
slashes++;
x = 0;
} else {
if (x > MAX_SUBCAT_LENGTH) {
return 1;
}
}
}
if (slashes != 2) {
#ifdef DEBUG
fprintf(stderr, "ERR: Classification string should have two /s.\n");
#endif
return 1;
}
return 0;
}
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+10 -1
View File
@@ -22,9 +22,18 @@
void *reallocate(void **addr, size_t *allocated, size_t requested);
/* Check if haystacks begins with needle and return a copy of haystack */
bool get_header(const char *haystack, const char *needle, char **line, size_t len);
bool get_header(const char *haystack, const char *needle, char **line);
/* Get the value of a header */
bool get_header_value(const char *header, char **value);
/* Get the size of the directory */
long get_directory_size(const char *sdir);
/* Initialize buff and copy generated id */
int get_random_id(char **buff);
/* Validates classification value */
int validate_classification(char *classification);
/* vi: set ts=8 sw=8 sts=4 et tw=80 cino=(0: */
+30
View File
@@ -55,6 +55,34 @@ START_TEST(check_read_valid_config)
}
END_TEST
START_TEST(check_read_valid_config_defaults)
{
char *config_file = TOPSRCDIR "/src/data/example.conf";
configuration config = { { 0 }, { 0 }, { 0 }, false, NULL };
int ret = read_config_from_file(config_file, &config);
ck_assert(ret == true);
// RECORD_RETENTION_ENABLED_DEFAULT = false
ck_assert(config.boolValues[CONF_RECORD_RETENTION_ENABLED] == RECORD_RETENTION_ENABLED_DEFAULT);
// RECORD_SERVER_DELIVERY_ENABLED_DEFAULT = true
ck_assert(config.boolValues[CONF_RECORD_SERVER_DELIVERY_ENABLED] == RECORD_SERVER_DELIVERY_ENABLED_DEFAULT);
}
END_TEST
START_TEST(check_read_valid_config_record_retention_delivery)
{
char *config_file = TOPSRCDIR "/src/data/example.1.conf";
configuration config = { { 0 }, { 0 }, { 0 }, false, NULL };
int ret = read_config_from_file(config_file, &config);
ck_assert(ret == true);
ck_assert(config.boolValues[CONF_RECORD_RETENTION_ENABLED] == true);
ck_assert(config.boolValues[CONF_RECORD_SERVER_DELIVERY_ENABLED] == false);
}
END_TEST
START_TEST(check_config_initialised)
{
char *config_file = ABSTOPSRCDIR "/src/data/example.conf";
@@ -89,6 +117,8 @@ Suite *config_suite(void)
TCase *t = tcase_create("config");
tcase_add_test(t, check_read_config_for_invalid_file);
tcase_add_test(t, check_read_valid_config);
tcase_add_test(t, check_read_valid_config_defaults);
tcase_add_test(t, check_read_valid_config_record_retention_delivery);
tcase_add_test(t, check_config_initialised);
// add more TCases here
+261
View File
@@ -0,0 +1,261 @@
/*
* 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.
*/
#include <check.h>
#include <errno.h>
#include <stdio.h>
#include "common.h"
#include "journal/journal.h"
static char *journal_file = "journal.txt";
static char *journal_print_file = "journal.print.txt";
static struct TelemJournal *journal = NULL;
static char *eid = "00007766547776eb7fc478eb0eb43e43";
static int K = 20;
void teardown(void)
{
remove(journal_file);
}
START_TEST(check_open_journal)
{
struct TelemJournal *j = open_journal(journal_file);
ck_assert_ptr_nonnull(j);
ck_assert_ptr_nonnull(j->fptr);
ck_assert_ptr_nonnull(j->boot_id);
ck_assert_int_eq(j->record_count, 0);
close_journal(j);
}
END_TEST
START_TEST(check_unsuccessful_open_journal)
{
char *bad_journal_file = "/sys/firmware/acpi/tables/journal";
struct TelemJournal *j = open_journal(bad_journal_file);
ck_assert_ptr_null(j);
}
END_TEST
void new_entry_setup(void)
{
if (journal) {
return;
}
journal = open_journal(journal_file);
}
START_TEST(check_new_entry)
{
int ret;
ret = new_journal_entry(journal, "t/t/t", 1520054957,
"3bc17766547776eb7fc478eb0eb43e43");
ck_assert_int_eq(ret, 0);
close_journal(journal);
}
END_TEST
START_TEST(check_new_entry_bad_class)
{
int ret = 0;
/* has an invalid class */
ret = new_journal_entry(journal, "t/t", 1520054957,
"3bc17766547776eb7fc478eb0eb43e43");
ck_assert_int_eq(ret, 1);
}
END_TEST
START_TEST(check_new_entry_bad_id)
{
int ret = 0;
/* event_id is invalid */
ret = new_journal_entry(journal, "t/t/t", 1520054957,
"Xbc17766547776eb7fc478eb0eb43e43");
ck_assert_int_eq(ret, 1);
}
END_TEST
START_TEST(check_new_entry_null_class)
{
int ret = 0;
/* param is NULL */
ret = new_journal_entry(journal, NULL, 1520054957,
"3bc17766547776eb7fc478eb0eb43e43");
ck_assert_int_eq(ret, 1);
}
END_TEST
START_TEST(check_new_entry_null_id)
{
int ret = 0;
/* null id */
ret = new_journal_entry(journal, "t/t/t", 1520054957, NULL);
ck_assert_int_eq(ret, 1);
}
END_TEST
void new_entry_teardown(void)
{
if (journal) {
close_journal(journal);
journal = NULL;
}
remove(journal_file);
}
void insert_n_records(int n, struct TelemJournal *j)
{
for (int i = 0; i < n; i++) {
new_journal_entry(j, "t/t/t", 1520054957 + i,
"3bc17766547776eb7fc478eb0eb43e43");
}
}
/*
failing in travis-ci
*/
START_TEST(check_journal_file_prune)
{
int rc = 0;
struct TelemJournal *j = open_journal(journal_file);
insert_n_records(j->record_count_limit * 2, j);
ck_assert_int_gt(j->record_count, j->record_count_limit);
rc = prune_journal(j, "./");
ck_assert(rc == 0);
// Record count should always be below the limit + hysteresis
ck_assert_int_lt(j->record_count, j->record_count_limit + DEVIATION);
close_journal(j);
}
END_TEST
void journal_entry_setup(void)
{
int result = 0;
if (journal) {
close_journal(journal);
}
journal = open_journal(journal_print_file);
insert_n_records(K, journal);
/* Insert recors with specific values */
result = new_journal_entry(journal, "a/b/c", 1520054957, eid);
ck_assert(result == 0);
result = new_journal_entry(journal, "a/b/d", 1520054957, eid);
ck_assert(result == 0);
}
START_TEST(check_journal_print)
{
int count = 0;
count = print_journal(journal, NULL, NULL, NULL, NULL, 0);
ck_assert_int_eq(count, K + 2);
}
END_TEST
START_TEST(check_journal_filter_by_class)
{
int result = print_journal(journal, "a/b/c", NULL, NULL, NULL, 0);
ck_assert_int_eq(result, 1);
}
END_TEST
START_TEST(check_journal_filter_by_class_prefix)
{
int result = print_journal(journal, "a/b/*", NULL, NULL, NULL, 0);
ck_assert_int_eq(result, 2);
}
END_TEST
START_TEST(check_journal_filter_by_event_id)
{
int result = print_journal(journal, NULL, NULL, eid, NULL, 0);
ck_assert(result == 2);
}
END_TEST
void journal_entry_teardown(void)
{
close_journal(journal);
remove(journal_print_file);
}
Suite *config_suite(void)
{
// A suite is comprised of test cases, defined below
Suite *s = suite_create("journal_feature");
// Individual unit tests are added to "test cases"
TCase *t = tcase_create("journal");
tcase_add_unchecked_fixture(t, NULL, teardown);
tcase_add_test(t, check_open_journal);
tcase_add_test(t, check_unsuccessful_open_journal);
suite_add_tcase(s, t);
t = tcase_create("new entry");
tcase_add_unchecked_fixture(t, new_entry_setup, new_entry_teardown);
tcase_add_test(t, check_new_entry);
tcase_add_test(t, check_new_entry_bad_class);
tcase_add_test(t, check_new_entry_bad_id);
tcase_add_test(t, check_new_entry_null_class);
tcase_add_test(t, check_new_entry_null_id);
suite_add_tcase(s, t);
t = tcase_create("prunning journal");
tcase_add_unchecked_fixture(t, NULL, teardown);
tcase_add_test(t, check_journal_file_prune);
suite_add_tcase(s, t);
t = tcase_create("print journal");
tcase_add_unchecked_fixture(t, journal_entry_setup,
journal_entry_teardown);
tcase_add_test(t, check_journal_print);
tcase_add_test(t, check_journal_filter_by_class);
tcase_add_test(t, check_journal_filter_by_class_prefix);
tcase_add_test(t, check_journal_filter_by_event_id);
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: */
+91
View File
@@ -24,6 +24,7 @@
#include "telemetry.h"
static struct telem_ref *ref = NULL;
static char *original_event_id = NULL;
void create_setup(void)
{
@@ -157,6 +158,87 @@ START_TEST(record_create_severity_overflow)
}
END_TEST
void event_id_setup(void)
{
int ret;
if (ref) {
return;
}
ret = tm_create_record(&ref, 1, "t/t/t", 2000);
original_event_id = strdup(ref->record->headers[TM_EVENT_ID]);
if (!original_event_id) {
return;
}
ck_assert_msg(ret != -ECONNREFUSED,
"Opt-out enabled. Opt in to run this test");
}
START_TEST(record_set_event_id)
{
int ret;
char *event_id = "aaaaaa00000033333344444466666622";
char *result;
if (asprintf(&result, "%s: %s\n", TM_EVENT_ID_STR, event_id) < 0) {
return;
}
ck_assert_str_ne(ref->record->headers[TM_EVENT_ID], result);
ret = tm_set_event_id(ref, event_id);
ck_assert_int_eq(ret, 0);
ck_assert_str_eq(ref->record->headers[TM_EVENT_ID], result);
free(result);
}
END_TEST
START_TEST(record_set_event_id_invalid_chars)
{
int ret;
char *event_id = "aaaaaa000000333333444444666666ZZ";
ret = tm_set_event_id(ref, event_id);
ck_assert_int_eq(ret, -1);
ck_assert_str_eq(ref->record->headers[TM_EVENT_ID], original_event_id);
}
END_TEST
START_TEST(record_set_event_id_null)
{
int ret;
char *event_id = NULL;
ret = tm_set_event_id(ref, event_id);
ck_assert_int_eq(ret, -1);
ck_assert_str_eq(ref->record->headers[TM_EVENT_ID], original_event_id);
}
END_TEST
START_TEST(record_set_event_id_short)
{
int ret;
char *event_id = "aaaa";
ret = tm_set_event_id(ref, event_id);
ck_assert_int_eq(ret, -1);
ck_assert_str_eq(ref->record->headers[TM_EVENT_ID], original_event_id);
}
END_TEST
START_TEST(record_set_event_id_long)
{
int ret;
char *event_id = "0000000000000000000000000000000000000000000";
ret = tm_set_event_id(ref, event_id);
ck_assert_int_eq(ret, -1);
ck_assert_str_eq(ref->record->headers[TM_EVENT_ID], original_event_id);
}
END_TEST
void event_id_teardown(void)
{
if (ref) {
free(ref);
}
free(original_event_id);
}
Suite *lib_suite(void)
{
Suite *s = suite_create("libtelemetry");
@@ -180,6 +262,15 @@ Suite *lib_suite(void)
tcase_add_test(t, record_create_severity_overflow);
suite_add_tcase(s, t);
t = tcase_create("event id");
tcase_add_unchecked_fixture(t, event_id_setup, event_id_teardown);
tcase_add_test(t, record_set_event_id);
tcase_add_test(t, record_set_event_id_invalid_chars);
tcase_add_test(t, record_set_event_id_null);
tcase_add_test(t, record_set_event_id_short);
tcase_add_test(t, record_set_event_id_long);
suite_add_tcase(s, t);
return s;
}
+192
View File
@@ -0,0 +1,192 @@
/*
* 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: */
+27 -277
View File
@@ -1,7 +1,7 @@
/*
* This program is part of the Clear Linux Project
*
* Copyright 2015 Intel Corporation
* 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
@@ -22,45 +22,17 @@
#include <unistd.h>
#include "configuration.h"
#include "telemdaemon.h"
#include "telempostdaemon.h"
#include "common.h"
TelemDaemon tdaemon;
static bool post_was_called = false;
TelemPostDaemon tdaemon;
char *get_serialized_record(char *headers, char *post_body, size_t *record_size)
bool dummy_post(char *headers[], char *body)
{
size_t totalsize, headersize, payloadsize, offset;
char *data;
offset = 0;
headersize = strlen(headers);
payloadsize = strlen(post_body);
totalsize = headersize + payloadsize;
*record_size = 2 * sizeof(uint32_t) + totalsize + 1;
data = malloc(*record_size);
memset(data, 0, *record_size);
memcpy(data, &totalsize, sizeof(uint32_t));
offset += sizeof(uint32_t);
memcpy(data + offset, &headersize, sizeof(uint32_t));
offset += sizeof(uint32_t);
memcpy(data + offset, headers, headersize);
offset += headersize;
memcpy(data + offset, post_body, payloadsize + 1);
return data;
return true;
}
bool dummy_post(char *headers[], char *body, bool spool)
{
post_was_called = true;
return post_was_called;
}
bool (*post_record_ptr)(char *[], char *, bool) = dummy_post;
bool (*post_record_ptr)(char *headers[], char *body) = dummy_post;
void setup(void)
{
@@ -74,8 +46,6 @@ START_TEST(check_daemon_is_initialized)
{
setup();
ck_assert(tdaemon.nfds == 0);
ck_assert(tdaemon.pollfds == NULL);
ck_assert(tdaemon.rate_limit_enabled == true);
ck_assert(tdaemon.record_burst_limit == 100);
ck_assert(tdaemon.record_window_length == 15);
@@ -85,107 +55,16 @@ START_TEST(check_daemon_is_initialized)
}
END_TEST
int get_poll_fd(TelemDaemon *tdaemon, nfds_t i)
{
if (i >= 0 && i < tdaemon->nfds) {
return tdaemon->pollfds[i].fd;
}
return -1;
}
START_TEST(check_add_del_poll_fd)
{
setup();
int fd = 1;
int short events = 1;
add_pollfd(&tdaemon, fd, events);
add_pollfd(&tdaemon, ++fd, events);
add_pollfd(&tdaemon, ++fd, events);
ck_assert_msg(tdaemon.nfds == 3, "Failed to add pollfd");
fd = get_poll_fd(&tdaemon, 0);
ck_assert(fd == 1);
fd = get_poll_fd(&tdaemon, 1);
ck_assert(fd == 2);
fd = get_poll_fd(&tdaemon, 2);
ck_assert(fd == 3);
del_pollfd(&tdaemon, 1);
ck_assert_msg(tdaemon.nfds == 2, "Failed to delete pollfd");
fd = get_poll_fd(&tdaemon, 1);
ck_assert(fd == 3);
del_pollfd(&tdaemon, 1);
ck_assert_msg(tdaemon.nfds == 1, "Failed to delete pollfd");
fd = get_poll_fd(&tdaemon, 1);
ck_assert(fd == -1);
del_pollfd(&tdaemon, 0);
ck_assert_msg(tdaemon.nfds == 0, "Failed to delete pollfd");
fd = get_poll_fd(&tdaemon, 0);
ck_assert(fd == -1);
}
END_TEST
START_TEST(check_add_remove_client)
{
setup();
client *cl, *cl1, *cl2, *cl3;
cl1 = add_client(&(tdaemon.client_head), 1);
cl2 = add_client(&(tdaemon.client_head), 2);
cl3 = add_client(&(tdaemon.client_head), 3);
int i = 3;
LIST_FOREACH(cl, &(tdaemon.client_head), client_ptrs) {
ck_assert_msg((cl->fd == i), "Actual value is %d\n", cl->fd);
i--;
}
remove_client(&(tdaemon.client_head), cl1);
remove_client(&(tdaemon.client_head), cl2);
remove_client(&(tdaemon.client_head), cl3);
//ck_assert(tdaemon.client_head.lh_first == NULL);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove clients\n");
}
END_TEST
void set_up_socket_pair(int *client, int *server)
{
int sv[2];
int ret;
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
ck_assert_msg(ret == 0, "Failed to create socket pair\n");
ck_assert_msg((fcntl(sv[0], F_SETFL, O_NONBLOCK) == 0),
"Failed to set socket to non-blocking\n");
ck_assert_msg((fcntl(sv[1], F_SETFL, O_NONBLOCK) == 0),
"Failed to set socket to non-blocking\n");
*client = sv[0];
*server = sv[1];
}
START_TEST(check_handle_client_with_no_data)
{
setup();
client *cl;
bool success;
char *filename = ABSTOPSRCDIR "/tests/telempostd/empty_message";
int server_fd;
int client_fd;
bool processed;
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
success = process_staged_record(filename, false, &tdaemon);
// Return true to remove corrupted record
ck_assert(success == true);
}
END_TEST
@@ -193,92 +72,12 @@ START_TEST(check_handle_client_with_incorrect_data)
{
setup();
client *cl;
char *buf = "test";
int server_fd;
int client_fd;
bool processed;
bool success;
char *filename = ABSTOPSRCDIR "/tests/telempostd/incorrect_message";
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
ssize_t ret = write(server_fd, buf, 2);
ck_assert(ret == 2);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
}
END_TEST
START_TEST(check_handle_client_with_incorrect_size)
{
setup();
client *cl;
int server_fd;
int client_fd;
bool processed;
char *data = "test";
char buf[4096];
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
int size = 10;
memset(buf, 0, 4096);
memcpy(buf, &size, RECORD_SIZE_LEN);
memcpy(buf + RECORD_SIZE_LEN, data, sizeof(uint32_t));
ssize_t ret = write(server_fd, buf, 2);
ck_assert(ret == 2);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
}
END_TEST
START_TEST(check_handle_client_with_correct_size)
{
setup();
client *cl;
int server_fd;
int client_fd;
bool processed;
char *data = "testing hello world message for handle client check";
char buf[256];
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
size_t size = strlen(data);
memset(buf, 0, 256);
memcpy(buf, &size, RECORD_SIZE_LEN);
memcpy(buf + RECORD_SIZE_LEN, &size, sizeof(uint32_t));
memcpy(buf + 2 * sizeof(uint32_t), data, strlen(data) + 1);
ck_assert(strcmp(data, buf + 2 * sizeof(int32_t)) == 0);
post_was_called = false;
ssize_t ret = write(server_fd, buf, 2 * sizeof(uint32_t) + size + 1);
ck_assert(ret != -1);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == true);
ck_assert(post_was_called == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
success = process_staged_record(filename, false, &tdaemon);
// Return true to remove corrupted record
ck_assert(success == true);
}
END_TEST
@@ -286,35 +85,11 @@ START_TEST(check_process_record_with_correct_size_and_data)
{
setup();
client *cl;
int server_fd, client_fd;
bool processed;
char *record;
size_t record_size;
char *headers = "record_format_version: 1\nclassification: crash/kernel/bug\nseverity: 0\n"
"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";
char *post_body = "test message";
bool success;
char *filename = ABSTOPSRCDIR "/tests/telempostd/correct_message";
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
post_was_called = false;
record = get_serialized_record(headers, post_body, &record_size);
ssize_t ret = write(server_fd, record, record_size);
ck_assert(ret == record_size);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == true);
ck_assert(post_was_called == true);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with correct data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with correct data\n");
close(server_fd);
free(record);
success = process_staged_record(filename, false, &tdaemon);
ck_assert(success == true);
}
END_TEST
@@ -322,33 +97,12 @@ START_TEST(check_process_record_with_incorrect_headers)
{
setup();
client *cl;
int server_fd, client_fd;
bool processed;
char *record;
size_t record_size;
char *headers = "record_format_version: 1\nclassificatioooon: crash/kernel/bug\nseverity: 0\n"
"machine_id: 1234\ncreation_timestamp: 1418672344\narch:x86_64\n"
"host_type: macbookpro\nbuild: 200\nkernel_version: 3.15\n";
char *post_body = "test message";
bool success;
char *filename = ABSTOPSRCDIR "/tests/telempostd/incorrect_headers";
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
post_was_called = false;
record = get_serialized_record(headers, post_body, &record_size);
ssize_t ret = write(server_fd, record, record_size);
ck_assert(ret == record_size);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == true);
ck_assert(post_was_called == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with incorrect headers\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with incorrect headers\n");
close(server_fd);
free(record);
success = process_staged_record(filename, false, &tdaemon);
// Return true to remove corrupted record
ck_assert(success == true);
}
END_TEST
@@ -638,17 +392,13 @@ END_TEST
Suite *config_suite(void)
{
// A suite is comprised of test cases, defined below
Suite *s = suite_create("daemon");
Suite *s = suite_create("postd");
// Individual unit tests are added to "test cases"
TCase *t = tcase_create("daemon");
tcase_add_test(t, check_add_del_poll_fd);
TCase *t = tcase_create("postd");
tcase_add_test(t, check_daemon_is_initialized);
tcase_add_test(t, check_add_remove_client);
tcase_add_test(t, check_handle_client_with_no_data);
tcase_add_test(t, check_handle_client_with_incorrect_data);
tcase_add_test(t, check_handle_client_with_incorrect_size);
tcase_add_test(t, check_handle_client_with_correct_size);
tcase_add_test(t, check_process_record_with_correct_size_and_data);
tcase_add_test(t, check_process_record_with_incorrect_headers);
tcase_add_test(t, check_rate_limit_enabled_functions);
+383
View File
@@ -0,0 +1,383 @@
/*
* This program is part of the Clear Linux Project
*
* Copyright 2015 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.
*/
#include <check.h>
#include <sys/socket.h>
#include <sys/fcntl.h>
#include <stdlib.h>
#include <sys/queue.h>
#include <unistd.h>
#include "configuration.h"
#include "telemdaemon.h"
#include "common.h"
TelemDaemon tdaemon;
char *get_serialized_record(char *headers, char *post_body, size_t *record_size)
{
size_t totalsize, headersize, payloadsize, offset;
char *data;
offset = 0;
headersize = strlen(headers);
payloadsize = strlen(post_body);
totalsize = headersize + payloadsize;
*record_size = 2 * sizeof(uint32_t) + totalsize + 1;
data = malloc(*record_size);
memset(data, 0, *record_size);
memcpy(data, &totalsize, sizeof(uint32_t));
offset += sizeof(uint32_t);
memcpy(data + offset, &headersize, sizeof(uint32_t));
offset += sizeof(uint32_t);
memcpy(data + offset, headers, headersize);
offset += headersize;
memcpy(data + offset, post_body, payloadsize + 1);
return data;
}
void setup(void)
{
char *config_file = ABSTOPSRCDIR "/src/data/example.conf";
set_config_file(config_file);
initialize_daemon(&tdaemon);
}
START_TEST(check_daemon_is_initialized)
{
setup();
ck_assert(tdaemon.nfds == 0);
ck_assert(tdaemon.pollfds == NULL);
}
END_TEST
int get_poll_fd(TelemDaemon *tdaemon, nfds_t i)
{
if (i >= 0 && i < tdaemon->nfds) {
return tdaemon->pollfds[i].fd;
}
return -1;
}
START_TEST(check_add_del_poll_fd)
{
setup();
int fd = 1;
int short events = 1;
add_pollfd(&tdaemon, fd, events);
add_pollfd(&tdaemon, ++fd, events);
add_pollfd(&tdaemon, ++fd, events);
ck_assert_msg(tdaemon.nfds == 3, "Failed to add pollfd");
fd = get_poll_fd(&tdaemon, 0);
ck_assert(fd == 1);
fd = get_poll_fd(&tdaemon, 1);
ck_assert(fd == 2);
fd = get_poll_fd(&tdaemon, 2);
ck_assert(fd == 3);
del_pollfd(&tdaemon, 1);
ck_assert_msg(tdaemon.nfds == 2, "Failed to delete pollfd");
fd = get_poll_fd(&tdaemon, 1);
ck_assert(fd == 3);
del_pollfd(&tdaemon, 1);
ck_assert_msg(tdaemon.nfds == 1, "Failed to delete pollfd");
fd = get_poll_fd(&tdaemon, 1);
ck_assert(fd == -1);
del_pollfd(&tdaemon, 0);
ck_assert_msg(tdaemon.nfds == 0, "Failed to delete pollfd");
fd = get_poll_fd(&tdaemon, 0);
ck_assert(fd == -1);
}
END_TEST
START_TEST(check_add_remove_client)
{
setup();
client *cl, *cl1, *cl2, *cl3;
cl1 = add_client(&(tdaemon.client_head), 1);
cl2 = add_client(&(tdaemon.client_head), 2);
cl3 = add_client(&(tdaemon.client_head), 3);
int i = 3;
LIST_FOREACH(cl, &(tdaemon.client_head), client_ptrs) {
ck_assert_msg((cl->fd == i), "Actual value is %d\n", cl->fd);
i--;
}
remove_client(&(tdaemon.client_head), cl1);
remove_client(&(tdaemon.client_head), cl2);
remove_client(&(tdaemon.client_head), cl3);
//ck_assert(tdaemon.client_head.lh_first == NULL);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove clients\n");
}
END_TEST
void set_up_socket_pair(int *client, int *server)
{
int sv[2];
int ret;
ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sv);
ck_assert_msg(ret == 0, "Failed to create socket pair\n");
ck_assert_msg((fcntl(sv[0], F_SETFL, O_NONBLOCK) == 0),
"Failed to set socket to non-blocking\n");
ck_assert_msg((fcntl(sv[1], F_SETFL, O_NONBLOCK) == 0),
"Failed to set socket to non-blocking\n");
*client = sv[0];
*server = sv[1];
}
START_TEST(check_handle_client_with_no_data)
{
setup();
client *cl;
int server_fd;
int client_fd;
bool processed;
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
}
END_TEST
START_TEST(check_handle_client_with_incorrect_data)
{
setup();
client *cl;
char *buf = "test";
int server_fd;
int client_fd;
bool processed;
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
ssize_t ret = write(server_fd, buf, 2);
ck_assert(ret == 2);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
}
END_TEST
START_TEST(check_handle_client_with_incorrect_size)
{
setup();
client *cl;
int server_fd;
int client_fd;
bool processed;
char *data = "test";
char buf[4096];
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
int size = 10;
memset(buf, 0, 4096);
memcpy(buf, &size, RECORD_SIZE_LEN);
memcpy(buf + RECORD_SIZE_LEN, data, sizeof(uint32_t));
ssize_t ret = write(server_fd, buf, 2);
ck_assert(ret == 2);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == false);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
}
END_TEST
START_TEST(check_handle_client_with_correct_size)
{
setup();
client *cl;
int server_fd;
int client_fd;
bool processed;
char *data = "testing hello world message for handle client check";
char buf[256];
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
size_t size = strlen(data);
memset(buf, 0, 256);
memcpy(buf, &size, RECORD_SIZE_LEN);
memcpy(buf + RECORD_SIZE_LEN, &size, sizeof(uint32_t));
memcpy(buf + 2 * sizeof(uint32_t), data, strlen(data) + 1);
ck_assert(strcmp(data, buf + 2 * sizeof(int32_t)) == 0);
ssize_t ret = write(server_fd, buf, 2 * sizeof(uint32_t) + size + 1);
ck_assert(ret != -1);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == true);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with no data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with n data\n");
close(server_fd);
}
END_TEST
START_TEST(check_process_record_with_correct_size_and_data)
{
setup();
client *cl;
int server_fd, client_fd;
bool processed;
char *record;
size_t record_size;
char *headers = "record_format_version: 1\nclassification: crash/kernel/bug\nseverity: 0\n"
"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"
"board_name: Qemu|Intel\n"
"cpu_model: Intel(R) Core(TM) i7-5650U CPU @ 2.20GHz\n"
"bios_version: Qemu\n"
"event_id: 3a2d799826edc6266d72824d2aac6763\n";
char *post_body = "test message";
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
record = get_serialized_record(headers, post_body, &record_size);
ssize_t ret = write(server_fd, record, record_size);
ck_assert(ret == record_size);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == true);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with correct data\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with correct data\n");
close(server_fd);
free(record);
}
END_TEST
START_TEST(check_process_record_with_incorrect_headers)
{
setup();
client *cl;
int server_fd, client_fd;
bool processed;
char *record;
size_t record_size;
char *headers = "record_format_version: 1\nclassificatioooon: crash/kernel/bug\nseverity: 0\n"
"machine_id: 1234\ncreation_timestamp: 1418672344\narch:x86_64\n"
"host_type: macbookpro\nbuild: 200\nkernel_version: 3.15\n";
char *post_body = "test message";
set_up_socket_pair(&client_fd, &server_fd);
cl = add_client(&(tdaemon.client_head), client_fd);
ck_assert_msg(cl != NULL, "failed to malloc client");
add_pollfd(&tdaemon, client_fd, POLLIN | POLLPRI);
record = get_serialized_record(headers, post_body, &record_size);
ssize_t ret = write(server_fd, record, record_size);
ck_assert(ret == record_size);
processed = handle_client(&tdaemon, 0, cl);
ck_assert(processed == true);
ck_assert_msg(is_client_list_empty(&(tdaemon.client_head)), "Failed to remove client with incorrect headers\n");
ck_assert_msg(tdaemon.nfds == 0, "Failed to remove poll fd for client with incorrect headers\n");
close(server_fd);
free(record);
}
END_TEST
Suite *config_suite(void)
{
// A suite is comprised of test cases, defined below
Suite *s = suite_create("probd");
// Individual unit tests are added to "test cases"
TCase *t = tcase_create("probd");
tcase_add_test(t, check_add_del_poll_fd);
tcase_add_test(t, check_daemon_is_initialized);
tcase_add_test(t, check_add_remove_client);
tcase_add_test(t, check_handle_client_with_no_data);
tcase_add_test(t, check_handle_client_with_incorrect_data);
tcase_add_test(t, check_handle_client_with_incorrect_size);
tcase_add_test(t, check_handle_client_with_correct_size);
tcase_add_test(t, check_process_record_with_correct_size_and_data);
tcase_add_test(t, check_process_record_with_incorrect_headers);
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: */
+85 -9
View File
@@ -12,8 +12,11 @@ TESTS = $(check_PROGRAMS) $(dist_check_SCRIPTS)
check_PROGRAMS = \
%D%/check_config \
%D%/check_daemon \
%D%/check_probd \
%D%/check_postd \
%D%/check_probes \
%D%/check_ncb64 \
%D%/check_journal \
%D%/check_libtelemetry
dist_check_SCRIPTS = \
@@ -30,25 +33,63 @@ dist_check_SCRIPTS = \
@CHECK_LIBS@ \
$(top_builddir)/src/libtelem-shared.la
%C%_check_daemon_SOURCES = \
%D%/check_daemon.c \
%C%_check_probd_SOURCES = \
%D%/check_probd.c \
src/telemdaemon.c \
src/telemdaemon.h
src/telemdaemon.h \
src/iorecord.h \
src/iorecord.c \
src/journal/journal.c \
src/journal/journal.h
%C%_check_daemon_CFLAGS = \
%C%_check_probd_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@ \
@CURL_CFLAGS@
%C%_check_daemon_LDADD = \
%C%_check_probd_LDADD = \
@CHECK_LIBS@ \
@CURL_LIBS@ \
$(top_builddir)/src/libtelem-shared.la
if LOG_SYSTEMD
if HAVE_SYSTEMD_JOURNAL
%C%_check_daemon_CFLAGS += \
%C%_check_probd_CFLAGS += \
$(SYSTEMD_JOURNAL_CFLAGS)
%C%_check_daemon_LDADD += \
%C%_check_probd_LDADD += \
$(SYSTEMD_JOURNAL_LIBS)
endif
endif
%C%_check_postd_SOURCES = \
%D%/check_postd.c \
src/spool.c \
src/iorecord.c \
src/retention.c \
src/telempostdaemon.c \
src/telempostdaemon.h \
src/journal/journal.c \
src/journal/journal.h
EXTRA_DIST += \
%D%/telempostd/correct_message \
%D%/telempostd/incorrect_headers \
%D%/telempostd/empty_message \
%D%/telempostd/incorrect_message
%C%_check_postd_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@ \
@CURL_CFLAGS@
%C%_check_postd_LDADD = \
@CHECK_LIBS@ \
@CURL_LIBS@ \
$(top_builddir)/src/libtelem-shared.la
if LOG_SYSTEMD
if HAVE_SYSTEMD_JOURNAL
%C%_check_postd_CFLAGS += \
$(SYSTEMD_JOURNAL_CFLAGS)
%C%_check_postd_LDADD += \
$(SYSTEMD_JOURNAL_LIBS)
endif
endif
@@ -106,9 +147,44 @@ 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_journal_SOURCES = \
%D%/check_journal.c \
src/journal/journal.c \
src/util.h \
src/util.c
%C%_check_journal_CFLAGS = \
$(AM_CFLAGS) \
@CHECK_CFLAGS@
%C%_check_journal_LDADD = \
@CHECK_LIBS@
%C%_check_libtelemetry_SOURCES = \
%D%/check_libtelemetry.c \
src/telemetry.c
src/configuration.h \
src/util.h \
src/nica/hashmap.h \
src/nica/inifile.h
%C%_check_libtelemetry_CFLAGS = \
$(AM_CFLAGS) \
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.
+16
View File
@@ -0,0 +1,16 @@
record_format_version: 1
classification: crash/kernel/bug
severity: 0
machine_id: 1234
creation_timestamp: 1418672344
arch: x86_64
host_type: macbookpro
build: 200
kernel_version: 3.15
payload_format_version: 1
system_name: clear-linux-os
board_name: Qemu|Intel
cpu_model: Intel(R) Core(TM) i7-5650U CPU @ 2.20GHz
bios_version: Qemu
event_id: 3a2d799826edc6266d72824d2aac6763
test message
View File
+10
View File
@@ -0,0 +1,10 @@
record_format_version: 1
nclassificatioooon: crash/kernel/bug
severity: 0
machine_id: 1234
creation_timestamp: 1531420323
arch: x86_64
host_type: macbookpro
build: 23550
kernel_version: 4.17.4-589.native
test message
+1
View File
@@ -0,0 +1 @@
test