Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

highlevel: detect if spec properly mounted and unmodified #4047

Merged
merged 63 commits into from
Sep 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
a4be8d4
highlevel: Improve documentation
Sep 8, 2021
7d804b6
highlevel: Implement check for proper mount of specification file
Sep 8, 2021
4c8b61d
highlevel: Refactor specProperlyMounted(), implement check for proper…
Sep 8, 2021
4813a72
highlevel: Add documentation for specProperlyMounted()
Sep 8, 2021
44a2476
highlevel: Fix wrong resource management
Sep 8, 2021
96360f4
highlevel: Change name of specProperlyMounted() to checkSpecProperlyM…
Sep 8, 2021
b2140ef
highlevel: Improve documentation
Sep 8, 2021
e461630
highlevel: Add sanity check to elektraClose()
Sep 8, 2021
e602f93
highlevel: Reformat C code using `scripts/dev/reformat-c`
Sep 8, 2021
9263f3a
Add license for asmonier's sha-2
Sep 15, 2021
41296f9
Add source of amosnier's sha-2
Sep 15, 2021
8f22f07
Add commit hash to sha-256.c source files
Sep 15, 2021
b39ce1a
Add function to compute sha256 hash of a key set
Sep 15, 2021
4d3003d
Include meta keys in sha 256 calculation
Sep 15, 2021
d64dfd9
Configure builds to include computeSha256OfKeySetWithoutValues
Sep 15, 2021
38b87a2
Change name of function to computeSha256OfKeySetWithoutValues to be m…
Sep 15, 2021
5220d9d
Fix wrong call order of hash_to_string() and sha_256_close()
Sep 15, 2021
b27740b
Rename variable to easier understand code
Sep 15, 2021
86508d5
Improve token calculation so it works with cascading lookups.
Sep 15, 2021
2343a09
Add inline documentation to gen.cpp
Sep 16, 2021
bffe90d
Fix wrong variable name, addendum to 24fd8e24d248cde8b104f629fbca27c9…
Sep 16, 2021
ba142c2
Fix calculation of specification key.
Sep 16, 2021
a3e33d5
Fix order of declarations in header
Sep 17, 2021
a7d72b3
Add tests for sha-256
Sep 17, 2021
5649bce
Duplicate param ks in calculateSpecificationToken() before ksCut, so …
Sep 17, 2021
e5ae659
Change calculateSpecificationToken() to not take NULL terminator into…
Sep 17, 2021
caf94f2
Add tests for hash.c
Sep 17, 2021
4bcb5aa
Add error handling to hash.c
Sep 17, 2021
d45de58
Implement checking spec token in HL API and modularize elektraOpen().
Sep 17, 2021
0e3775e
Revert type of param that was accidentally changed in a previous commit.
Sep 17, 2021
9185107
Remove "minimal validation" in favor of the new checkSpecProperlyMoun…
Sep 17, 2021
222b62f
Add missing checks for NULL to prevent segfault
Sep 17, 2021
495fcff
Implement token calculation in kdb gen highlevel
Sep 17, 2021
282691a
Update contract in the generated C sources for kdb gen tests
Sep 17, 2021
acdbc54
Replace ksRewind()/ksNext() by ksAtCursor()
Sep 17, 2021
80c8287
Improve checkSpecProperlyMounted and checkSpecToken:
Sep 18, 2021
1e78456
Remove obsolete function specMountExecuted()
Sep 18, 2021
9d824a6
Change specification checks to not use contract passed from applicati…
Sep 18, 2021
04103b8
Improve usability of checkSpecificationMountPoint()'s error messages
Sep 18, 2021
9150eee
Fix token calculation: Set parentKey namespace already for kdbGet() i…
Sep 21, 2021
fb74162
Ignore array parents in token calculation
Sep 21, 2021
82a21ad
Remove debug print statements
Sep 21, 2021
436c7f7
Update token in *.expected.c files of highlevel code generator tests.
Sep 22, 2021
018162c
Execute scripts/dev/reformat-all
Sep 22, 2021
93e93d7
Add details about how to fix errors relating to specification mountin…
Sep 22, 2021
e3217ea
Add note to function documentation about ignored array parent keys.
Sep 22, 2021
c9e0717
Improve usability of error messages for problems with specification.
Sep 22, 2021
9a2e47d
Add release notes
Sep 22, 2021
b8af270
Merge branch 'master' into 3998-detect-spec-mounted
Sep 22, 2021
dbd1f3d
Reformat .c and .md files
Sep 22, 2021
55b9044
Update kdb gen and elektra highlevel documentation to reflect changes…
Sep 22, 2021
e0fa19b
Apply patch to man pages.
Sep 22, 2021
5492b76
Improve key hierarchy of spec/token
Sep 22, 2021
3487313
Improve key hierarchy of spec/mounted
Sep 22, 2021
a0f0bba
Change error message to only advise users to kdb rm -r the spec names…
Sep 22, 2021
85a977f
Remove unused data argument
Sep 22, 2021
8257864
Merge branch 'master' into 3998-detect-spec-mounted
Sep 23, 2021
103474a
Update contract key names ".../spec/token" and ".../spec/mounted" in …
Sep 23, 2021
39f9853
Execute reformat-all script
Sep 24, 2021
bcfe59b
Fix hash.c and highlevel.cpp so that calculateSpecificationToken() do…
Sep 27, 2021
af52c71
Update token value in *.expected.c files to match that now the namesp…
Sep 27, 2021
4150c73
Merge branch 'qwepoizt-master' into 3998-detect-spec-mounted
Sep 27, 2021
39230d5
Simplify detection of array parents
Sep 27, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions doc/THIRD-PARTY-LICENSES
Original file line number Diff line number Diff line change
Expand Up @@ -89,12 +89,25 @@ Files: src/tools/kdb/gen/mustache.hpp
Copyright: 2015-2018 Kevin Wojniak
License: BSL-1.0

Files: src/libs/ease/sha-256.c
src/libs/ease/sha-256.h
Copyright: 2021 Alain Mosnier
License: BSD-0-clause

Files: src/bindings/jna/gradlew
src/bindings/jna/gradlew.bat
src/bindings/jna/gradle/wrapper*
Copyright: 2015 the original author or authors
License: Apache-2.0

License: BSD-0-clause
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES
OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

License: BSD-3-clause
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Expand Down
6 changes: 2 additions & 4 deletions doc/help/elektra-highlevel-gen.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ with the basic features explained in [`kdb-gen-highlevel(1)`](kdb-gen-highlevel.
The parameters that are relevant to the concepts described here are (for the rest see [`kdb-gen-highlevel(1)](kdb-gen-highlevel.md)):

- `embeddedSpec`: allowed values: `full` (default), `defaults`, `none`
- `specValidation`: allowed values: `none` (default), `minimal`
- `enumConv`: allowed values: `strcmp`, `switch`, `auto` (default)

Using `embeddedSpec` you can configure how much of the specification is embedded into your application. By default we use `full`. This means
Expand All @@ -25,9 +24,8 @@ Setting `embeddedSpec=none` is only recommended, if you must have the minimal bi
defaults are passed to `elektraOpen` and defaults are only handled via the `spec` plugin. If the specification/configuration isn't mounted,
the getter functions may fail.

To avoid this case of a misconfigured mountpoint, you can use `specValidation=minimal`. It is by far not a perfect solution, but it will
cause the initialization function (by default named `loadConfiguration`) to fail, if the specification is not mounted at the expected
mountpoint or if the specification was not `spec-mount`ed.
The case of a misconfigured mountpoint will be detected automatically and reported as an error. It will
cause the initialization function (by default named `loadConfiguration`) to fail, if the specification is not mounted at the expected mountpoint or if the specification was not `spec-mount`ed.

## Enums

Expand Down
7 changes: 2 additions & 5 deletions doc/help/kdb-gen-highlevel.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,6 @@ For detailed information about the contents of the header file see [elektra-high
- `embeddedSpec`:
Changes how much of the specification is embedded into the application; allowed values: `full` (default), `defaults`, `none`.
see [elektra-highlevel-gen(7)](elektra-highlevel-gen.md)
- `specValidation`:
Changes how the specification will be validated on application start-up; allowed values: `none` (default), `minimal`.
see [elektra-highlevel-gen(7)](elektra-highlevel-gen.md)

## EXAMPLES

Expand All @@ -93,11 +90,11 @@ However, it is not recommended to have the code-generator read from the KDB, so

If you don't want to embed the full specification in your binary, we recommend:

`kdb gen -F ni=spec.ini highlevel /sw/org/app/#0/current config embeddedSpec=defaults specValidation=minimal`
`kdb gen -F ni=spec.ini highlevel /sw/org/app/#0/current config embeddedSpec=defaults`

For the minimal binary size you may use (this comes with its own drawbacks, see [elektra-highlevel-gen(7)](elektra-highlevel-gen.md)):

`kdb gen -F ni=spec.ini highlevel /sw/org/app/#0/current config embeddedSpec=none specValidation=minimal`
`kdb gen -F ni=spec.ini highlevel /sw/org/app/#0/current config embeddedSpec=none`

## SEE ALSO

Expand Down
8 changes: 3 additions & 5 deletions doc/man/man1/kdb-gen-highlevel.1
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1.pre1
.TH "KDB\-GEN\-HIGHLEVEL" "1" "September 2019" ""
.TH "KDB\-GEN\-HIGHLEVEL" "1" "September 2021" ""
.SH "NAME"
\fBkdb\-gen\-highlevel\fR \- High\-level API code\-generator template
.SH "SYNOPSIS"
Expand Down Expand Up @@ -70,8 +70,6 @@ For detailed information about the contents of the header file see elektra\-high
\fBgenSetters\fR: Switches whether setters should be generated at all; allowed values: \fB1\fR (default), \fB0\fR
.IP "\(bu" 4
\fBembeddedSpec\fR: Changes how much of the specification is embedded into the application; allowed values: \fBfull\fR (default), \fBdefaults\fR, \fBnone\fR\. see elektra\-highlevel\-gen(7) \fIelektra\-highlevel\-gen\.md\fR
.IP "\(bu" 4
\fBspecValidation\fR: Changes how the specification will be validated on application start\-up; allowed values: \fBnone\fR (default), \fBminimal\fR\. see elektra\-highlevel\-gen(7) \fIelektra\-highlevel\-gen\.md\fR
.IP "" 0
.SH "EXAMPLES"
The simplest invocation is:
Expand All @@ -84,11 +82,11 @@ However, it is not recommended to have the code\-generator read from the KDB, so
.P
If you don't want to embed the full specification in your binary, we recommend:
.P
\fBkdb gen \-F ni=spec\.ini highlevel /sw/org/app/#0/current config embeddedSpec=defaults specValidation=minimal\fR
\fBkdb gen \-F ni=spec\.ini highlevel /sw/org/app/#0/current config embeddedSpec=defaults\fR
.P
For the minimal binary size you may use (this comes with its own drawbacks, see elektra\-highlevel\-gen(7) \fIelektra\-highlevel\-gen\.md\fR):
.P
\fBkdb gen \-F ni=spec\.ini highlevel /sw/org/app/#0/current config embeddedSpec=none specValidation=minimal\fR
\fBkdb gen \-F ni=spec\.ini highlevel /sw/org/app/#0/current config embeddedSpec=none\fR
.SH "SEE ALSO"
.IP "\(bu" 4
kdb(1) \fIkdb\.md\fR for general information about the \fBkdb\fR tool\.
Expand Down
6 changes: 2 additions & 4 deletions doc/man/man7/elektra-highlevel-gen.7
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.\" generated with Ronn-NG/v0.10.1
.\" http://github.com/apjanke/ronn-ng/tree/0.10.1.pre1
.TH "ELEKTRA\-HIGHLEVEL\-GEN" "7" "September 2019" ""
.TH "ELEKTRA\-HIGHLEVEL\-GEN" "7" "September 2021" ""
.SH "NAME"
\fBelektra\-highlevel\-gen\fR \- High\-level API code\-generation advanced features
.P
Expand All @@ -10,8 +10,6 @@ The parameters that are relevant to the concepts described here are (for the res
.IP "\(bu" 4
\fBembeddedSpec\fR: allowed values: \fBfull\fR (default), \fBdefaults\fR, \fBnone\fR
.IP "\(bu" 4
\fBspecValidation\fR: allowed values: \fBnone\fR (default), \fBminimal\fR
.IP "\(bu" 4
\fBenumConv\fR: allowed values: \fBstrcmp\fR, \fBswitch\fR, \fBauto\fR (default)
.IP "" 0
.P
Expand All @@ -21,7 +19,7 @@ The advantage of using \fBfull\fR is that your application is contained in a sin
.P
Setting \fBembeddedSpec=none\fR is only recommended, if you must have the minimal binary size and you know what you are doing\. In this case no defaults are passed to \fBelektraOpen\fR and defaults are only handled via the \fBspec\fR plugin\. If the specification/configuration isn't mounted, the getter functions may fail\.
.P
To avoid this case of a misconfigured mountpoint, you can use \fBspecValidation=minimal\fR\. It is by far not a perfect solution, but it will cause the initialization function (by default named \fBloadConfiguration\fR) to fail, if the specification is not mounted at the expected mountpoint or if the specification was not \fBspec\-mount\fRed\.
The case of a misconfigured mountpoint will be detected automatically and reported as an error\. It will cause the initialization function (by default named \fBloadConfiguration\fR) to fail, if the specification is not mounted at the expected mountpoint or if the specification was not \fBspec\-mount\fRed\.
.SH "Enums"
We support the mapping of a set of string values to a native C \fBenum\fR\. To use this feature, you need to write your specification the same way that the enum part of the \fBtype\fR plugin expects\.
.IP "" 4
Expand Down
22 changes: 21 additions & 1 deletion doc/news/_preparation_next_release.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,20 @@ _(Michael Tucek)_
- Remove obsolete `ksNeedSync` function. _(Mihael Pranjić)_
- Replace various occurences of `sprintf` by `snprintf` and fix out of bounds array access in markdownlinkconverter. _(Mihael Pranjić)_

### High-level API

- Implement a check to detect whether an application's specification was properly `mount`ed and `spec-mount`ed. _(Tobias Schubert @qwepoizt)_
- Implement a check to detect whether an application's specification was changed after installation. _(Tobias Schubert @qwepoizt)_
- Add sanity-checks to resource management. _(Tobias Schubert @qwepoizt)_
- Refactor and modularize code. _(Tobias Schubert @qwepoizt)_
- Update and improve inline documentation. _(Tobias Schubert @qwepoizt)_
- Remove "minimal validation" in favor of the new checks (see above). _(Tobias Schubert @qwepoizt)_

### Ease

- Implement calculation of a specification token (=sha-256 hash). _(Tobias Schubert @qwepoizt)_
- Add [asmonier's sha-2](https://github.com/amosnier/sha-2) for sha-256 hash calculation. _(Tobias Schubert @qwepoizt)_

### <<Library1>>

- <<TODO>>
Expand Down Expand Up @@ -201,6 +215,8 @@ _(Michael Tucek)_
- `kdb editor/import`: Disable the use of cascading names (and the 'validate' strategy operating on cascading keys) entirely. _(Alexander Firbas)_
- Update numerous tests to comply with changes above. _(Alexander Firbas)_
- Add a new subsection on cascading writes to the [tutorial](/doc/tutorials/cascading.md) on cascading keys. _(Alexander Firbas)_
- `kdb gen`: Generate specification token during code-generation and add it to generated contract. _(Tobias Schubert @qwepoizt)_
- `kdb gen`: Improve naming of variables to make code easier to understand. _(Tobias Schubert @qwepoizt)_
- `kdb spec-mount`: Improve usability by failing with helpful error messages, if the specification contains errors. _(Tobias Schubert @qwepoizt)_

- <<TODO>>
Expand All @@ -223,10 +239,11 @@ _(Michael Tucek)_
- Remove previous authors. _(Markus Raab)_
- add pre/postconditions and invariants to module keytest _(@lawli3t)_
- Updated the news template. _(Mihael Pranjić)_
- Update tutorial and in-code comments for high-level API _(Tobias Schubert @qwepoizt)_
- Update and improve tutorial and in-code comments for high-level API _(Tobias Schubert @qwepoizt)_
- Improve documentation of opts library _(Tobias Schubert @qwepoizt)_
- Update tutorial "High-level API (with code-generation)" to reflect change of `loadConfiguration()`'s signature in release 0.9.5 _(Tobias Schubert @qwepoizt)_
- add pre/postconditions and invariants to module keyvalue _(@lawli3t)_
- Update and improve inline documentation of `kdb gen`. _(Tobias Schubert @qwepoizt)_
- Fix broken links. _(Robert Sowula)_
- <<TODO>>

Expand All @@ -237,6 +254,9 @@ _(Michael Tucek)_
- Use clang-format 12 for Restyled and update Restyled version. _(Mihael Pranjić)_
- Update all Restyled formatters to current versions. _(Mihael Pranjić)_
- Add additional test cases for module `keytest` _(@lawli3t)_
- Update tests for high-level API to work with new specification token mechanism. _(Tobias Schubert @qwepoizt)_
- Add tests for libease's sha-256. _(Tobias Schubert @qwepoizt)_
- Add tests for sha-256 hash calculation of a KeySet. _(Tobias Schubert @qwepoizt)_
- Add additional test cases for module `keymeta` _(@lawli3t)_
- <<TODO>>
- <<TODO>>
Expand Down
2 changes: 2 additions & 0 deletions src/include/kdbease.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ char * elektraLongDoubleToString (kdb_long_double_t value);

#endif // ELEKTRA_HAVE_KDB_LONG_DOUBLE

kdb_boolean_t calculateSpecificationToken (char hash_string[65], KeySet * ks, Key * parentKey);

#ifdef __cplusplus
}
}
Expand Down
1 change: 0 additions & 1 deletion src/include/kdbprivate.h
Original file line number Diff line number Diff line change
Expand Up @@ -667,7 +667,6 @@ ElektraError * elektraErrorKeyNotFound (const char * keyname);
ElektraError * elektraErrorWrongType (const char * keyname, KDBType expectedType, KDBType actualType);
ElektraError * elektraErrorNullError (const char * function);
ElektraError * elektraErrorEnsureFailed (const char * reason);
ElektraError * elektraErrorMinimalValidationFailed (const char * function);

#ifdef __cplusplus
}
Expand Down
3 changes: 2 additions & 1 deletion src/libs/ease/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
file (GLOB SOURCES *.c)
add_lib (ease SOURCES ${SOURCES} COMPONENT libelektra${SO_VERSION})
file (GLOB HEADERS *.h)
add_lib (ease SOURCES ${SOURCES} ${HEADERS} COMPONENT libelektra${SO_VERSION})
120 changes: 120 additions & 0 deletions src/libs/ease/hash.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/**
* @file
*
* @brief Provides functions to hash Elektra data structures.
*
* @copyright BSD License (see LICENSE.md or https://www.libelektra.org)
*/

#include <kdb.h>
#include <kdbease.h>
#include <kdberrors.h>
#include <kdbtypes.h>

#include <stdio.h>
#include <string.h>

#include "sha-256.h"

static void hash_to_string (char string[65], const uint8_t hash[32]);

/**
* Calculate a specification token for the KeySet of an application.
*
* The KeySet of an application is identified as all keys below the applications root key.
*
* @pre The parentKey must have the correct namespace. E.g. If only keys from the spec:/ should be considered for the token calculation,
* pass a key with KEY_NS_SPEC.
*
* @note Array parent key's (e.g., `/format/#`) are ignored for the token. See inline documentation below for rationale.
*
* @param hash_string A string. After successful execution this will contain the hash as hex-string.
* @param ks The KeySet for the application.
* @param parentKey The Key below which all the relevant keys are. Keys that are not below @p parentKey are ignored. The key's namespace is
* important (see preconditions)
* @retval false If an error occurred.
* @retval true If the computation was successful.
*/
kdb_boolean_t calculateSpecificationToken (char hash_string[65], KeySet * ks, Key * parentKey)
{
if (parentKey == NULL)
{
// Can't set error to parentKey when it is null.
return false;
}
if (hash_string == NULL)
{
ELEKTRA_SET_INTERNAL_ERROR (parentKey, "Param hash_string was NULL");
return false;
}
if (ks == NULL)
{
ELEKTRA_SET_INTERNAL_ERROR (parentKey, "Param ks was NULL");
return false;
}

// Initialize sha_256 for streaming
uint8_t hash[SIZE_OF_SHA_256_HASH];
struct Sha_256 sha_256;
sha_256_init (&sha_256, hash);

// Duplicate ks, then cut out parentKey and all keys below. These are the ones we take into account for token calculation.
KeySet * dupKs = ksDup (ks);
KeySet * cutKs = ksCut (dupKs, parentKey);

/**
* Loop through all keys relevant for token calculation.
*/
for (elektraCursor it = 0; it < ksGetSize (cutKs); ++it)
{
Key * currentKey = ksAtCursor (cutKs, it);

/**
* Ignore array parents for token calculation.
* Rationale: There is a bug in the spec plugin that is triggered on changing the size of an array.
* It leads to array parents vanishing from the spec namespace and thus a different token.
* See https://github.com/ElektraInitiative/libelektra/issues/4061
*/
if (strcmp (keyBaseName (currentKey), "#") == 0)
{
continue;
}

// Feed key name into sha_256_write() without NULL terminator (hence -1).
// This makes it easier to compare expected results with other sha256 tools.
sha_256_write (&sha_256, keyName (currentKey), keyGetNameSize (currentKey) - 1);
// Note: The value of the key itself is not relevant / part of specification. Only the key's name + its metadata!

KeySet * currentMetaKeys = keyMeta (currentKey);
// Feed name + values from meta keys into sha_256_write().
for (elektraCursor metaIt = 0; metaIt < ksGetSize (currentMetaKeys); metaIt++)
{
Key * currentMetaKey = ksAtCursor (currentMetaKeys, metaIt);
sha_256_write (&sha_256, keyName (currentMetaKey), keyGetNameSize (currentMetaKey) - 1);
sha_256_write (&sha_256, keyString (currentMetaKey), keyGetValueSize (currentMetaKey) - 1);
}
}

sha_256_close (&sha_256);
hash_to_string (hash_string, hash);

ksDel (dupKs);
ksDel (cutKs);

return true;
}

/**
* Convert hash array to hex string
* Copied from https://github.com/amosnier/sha-2/blob/f0d7baf076207b943649e68946049059f018c10b/test.c
* @param string A string array of length 65 (64 characters + \0)
* @param hash The input hash array
*/
static void hash_to_string (char string[65], const uint8_t hash[32])
{
size_t i;
for (i = 0; i < 32; i++)
{
string += sprintf (string, "%02x", hash[i]);
}
}
Loading