Skip to content
4 changes: 4 additions & 0 deletions doc/releases/release-notes-4.4.rst
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ New APIs and options

.. zephyr-keep-sorted-start re(^\* \w)

* Settings

* :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION`
* :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION_VALUE_SIZE`

.. zephyr-keep-sorted-stop

Expand Down
10 changes: 6 additions & 4 deletions doc/services/device_mgmt/smp_groups/smp_group_3.rst
Original file line number Diff line number Diff line change
Expand Up @@ -492,10 +492,12 @@ where:
.. table::
:align: center

+--------+--------------------------------------------------------------------------------+
| "name" | If provided, contains the settings subtree name to save, if not then will save |
| | all settings. |
+--------+--------------------------------------------------------------------------------+
+--------+---------------------------------------------------------------------------------------+
| "name" | If provided, contains the settings subtree name to save (can also be a single setting |
| | name if :kconfig:option:`CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION` is |
| | enabled), if not then will save all settings. |
+--------+---------------------------------------------------------------------------------------+


Save settings response
======================
Expand Down
7 changes: 7 additions & 0 deletions include/zephyr/mgmt/mcumgr/grp/settings_mgmt/settings_mgmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@ enum settings_mgmt_ret_code_t {

/** The provided key name does not support being saved. */
SETTINGS_MGMT_ERR_SAVE_NOT_SUPPORTED,

/**
* The provided key cannot be saved before the value is longer than the size of the
* largest value that can safely be read
* (CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION_VALUE_SIZE).
*/
SETTINGS_MGMT_ERR_SAVE_FAILED_VALUE_TOO_LONG_TO_READ,
};

#ifdef __cplusplus
Expand Down
18 changes: 18 additions & 0 deletions include/zephyr/settings/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,24 @@ int settings_commit(void);
*/
int settings_commit_subtree(const char *subtree);

#if defined(CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION) || defined(__DOXYGEN__)
/**
* Save a single currently running serialized value to persisted storage (if it has changed
* value) by reading the value using the get function, or save a whole subtree's currently
* running serialized items out.
*
* @param name Name/key of the settings item or subtree.
* @param save_if_subtree Set to true if the item should be save and it is a subtree.
* @param save_if_single_setting Set to true if the item should be save and it is a single
* setting.
*
* @return 0 on success, non-zero on failure.
*/
int settings_save_subtree_or_single_without_modification(const char *name,
bool save_if_subtree,
bool save_if_single_setting);
#endif

/**
* @} settings
*/
Expand Down
15 changes: 15 additions & 0 deletions subsys/mgmt/mcumgr/grp/settings_mgmt/src/settings_mgmt.c
Original file line number Diff line number Diff line change
Expand Up @@ -515,13 +515,28 @@ static int settings_mgmt_save(struct smp_streamer *ctxt)
}

if (save_subtree) {
#ifdef CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION
/*
* This allows saving either a subtree or single setting, if a full setting name
* is provided then the single setting will be saved, otherwise it will save the
* subtree (assuming it is a valid setting subtree).
*/
rc = settings_save_subtree_or_single_without_modification(key_name, true, true);
#else
rc = settings_save_subtree(key_name);
#endif
} else {
rc = settings_save();
}

if (rc != 0) {
switch (rc) {
#ifdef CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION
case -EDOM:
rc = SETTINGS_MGMT_ERR_SAVE_FAILED_VALUE_TOO_LONG_TO_READ;
break;
case -ENOSYS:
#endif
case -ENOENT:
case -ENOTSUP:
rc = SETTINGS_MGMT_ERR_SAVE_NOT_SUPPORTED;
Expand Down
18 changes: 18 additions & 0 deletions subsys/settings/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,24 @@ config SETTINGS_DYNAMIC_HANDLERS
help
Enables the use of dynamic settings handlers

config SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION
bool "Save single or subtree (without modification) function"
help
Includes the `settings_save_subtree_or_single_without_modification()` function which
allows saving an item that might be a subtree or a single setting, without changing the
value. To save a single setting, the setting must support reading and writing of it
using the `h_get()` function pointer and must support reading it into a large buffer
size.

config SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION_VALUE_SIZE
int "Save single or subtree (without modification) maximum value size"
default 65
depends on SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION
help
The maximum size of a single setting that can be saved using the
`settings_save_subtree_or_single_without_modification()` function - note that this will
use stack memory.

# Hidden option to enable encoding length into settings entry
config SETTINGS_ENCODE_LEN
bool
Expand Down
73 changes: 73 additions & 0 deletions subsys/settings/src/settings_store.c
Original file line number Diff line number Diff line change
Expand Up @@ -303,3 +303,76 @@ void settings_store_init(void)
{
sys_slist_init(&settings_load_srcs);
}

#ifdef CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION
int settings_save_subtree_or_single_without_modification(const char *name,
bool save_if_subtree,
bool save_if_single_setting)
{
int rc;
int value_size;
uint8_t read_buffer[CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION_VALUE_SIZE];
const char *next = NULL;
struct settings_handler_static *handler;

if (save_if_subtree == false && save_if_single_setting == false) {
return -EINVAL;
}

handler = settings_parse_and_lookup(name, &next);

if (next == NULL) {
/* This is a subtree of settings, bail if user did not request saving it. */
if (save_if_subtree == false) {
return -EPERM;
}

return settings_save_subtree(name);
} else if (save_if_single_setting == false) {
return -EPERM;
}

/*
* For single settings, we need to be able to retrieve the value of the setting, if this
* is not supported then single saving cannot be done with this key.
*/
if (handler->h_get == NULL) {
return -ENOSYS;
}

settings_lock_take();

/*
* Settings does not support getting the size of a setting, therefore attempt to read the
* full buffer size, if that returns that amount of data then we must abort as we cannot
* get the full data with this buffer and would save a truncated value.
*/
value_size = handler->h_get(next, read_buffer, sizeof(read_buffer));

if (value_size < 0) {
rc = value_size;
goto exit;
} else if (value_size == sizeof(read_buffer)) {
rc = -EDOM;
goto exit;
}

rc = settings_save_one(name, read_buffer, value_size);

/*
* Caller just needs to know that it was successful, not the length of the data that was
* saved.
*/
if (rc >= 0) {
rc = 0;
} else if (rc < 0) {
LOG_ERR("Saving single setting '%s' of length %d failed: %d", name, value_size,
rc);
}

exit:
settings_lock_release();

return rc;
}
#endif /* CONFIG_SETTINGS_SAVE_SINGLE_SUBTREE_WITHOUT_MODIFICATION */
Loading
Loading