Index: pace/app/contacts/contacts.c
===================================================================
--- uspace/app/contacts/contacts.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ 	(revision )
@@ -1,541 +1,0 @@
-/*
- * Copyright (c) 2018 Jiri Svoboda
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions
- * are met:
- *
- * - Redistributions of source code must retain the above copyright
- *   notice, this list of conditions and the following disclaimer.
- * - Redistributions in binary form must reproduce the above copyright
- *   notice, this list of conditions and the following disclaimer in the
- *   documentation and/or other materials provided with the distribution.
- * - The name of the author may not be used to endorse or promote products
- *   derived from this software without specific prior written permission.
- *
- * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
- * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
- * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
- * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
- * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
- * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
- * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
- * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-
-/** @addtogroup contacts
- * @{
- */
-
-/**
- * @file Contact list application.
- *
- * Maintain a contact list / address book. The main purpose of this
- * trivial application is to serve as an example of using SIF.
- */
-
-#include <adt/list.h>
-#include <errno.h>
-#include <nchoice.h>
-#include <sif.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <str.h>
-
-/** Contacts */
-typedef struct {
-	/** Open SIF repository */
-	sif_sess_t *repo;
-	/** Entries SIF node */
-	sif_node_t *nentries;
-	/** Entries list (of contacts_entry_t) */
-	list_t entries;
-} contacts_t;
-
-/** Contact entry */
-typedef struct {
-	/** Containing contacts */
-	contacts_t *contacts;
-	/** Link to contacts->entries */
-	link_t lentries;
-	/** SIF node for this entry */
-	sif_node_t *nentry;
-	/** Contact name */
-	char *name;
-} contacts_entry_t;
-
-/** Actions in contact menu */
-typedef enum {
-	/** Create new contact */
-	ac_create_contact,
-	/** Delete contact */
-	ac_delete_contact,
-	/** Exit */
-	ac_exit
-} contact_action_t;
-
-static errno_t contacts_load(sif_node_t *, contacts_t *);
-static contacts_entry_t *contacts_first(contacts_t *);
-static contacts_entry_t *contacts_next(contacts_entry_t *);
-static void contacts_entry_delete(contacts_entry_t *);
-
-/** Open contacts repo or create it if it does not exist.
- *
- * @param fname File name
- * @param rcontacts Place to store pointer to contacts object.
- *
- * @return EOK on success or error code
- */
-static errno_t contacts_open(const char *fname, contacts_t **rcontacts)
-{
-	contacts_t *contacts = NULL;
-	sif_sess_t *repo = NULL;
-	sif_trans_t *trans = NULL;
-	sif_node_t *node;
-	const char *ntype;
-	errno_t rc;
-
-	contacts = calloc(1, sizeof(contacts_t));
-	if (contacts == NULL)
-		return ENOMEM;
-
-	list_initialize(&contacts->entries);
-
-	/* Try to open an existing repository. */
-	rc = sif_open(fname, &repo);
-	if (rc != EOK) {
-		/* Failed to open existing, create new repository */
-		rc = sif_create(fname, &repo);
-		if (rc != EOK)
-			goto error;
-
-		/* Start a transaction */
-		rc = sif_trans_begin(repo, &trans);
-		if (rc != EOK)
-			goto error;
-
-		/* Create 'entries' node container for all entries */
-		rc = sif_node_append_child(trans, sif_get_root(repo), "entries",
-		    &contacts->nentries);
-		if (rc != EOK)
-			goto error;
-
-		/* Finish the transaction */
-		rc = sif_trans_end(trans);
-		if (rc != EOK)
-			goto error;
-
-		trans = NULL;
-	} else {
-		/*
-		 * Opened an existing repository. Find the 'entries' node.
-		 * It should be the very first child of the root node.
-		 * This is okay to do in general, as long as we don't
-		 * require forward compatibility (which we don't).
-		 */
-		node = sif_node_first_child(sif_get_root(repo));
-		if (node == NULL) {
-			rc = EIO;
-			goto error;
-		}
-
-		/* Verify it's the correct node type(!) */
-		ntype = sif_node_get_type(node);
-		if (str_cmp(ntype, "entries") != 0) {
-			rc = EIO;
-			goto error;
-		}
-
-		rc = contacts_load(node, contacts);
-		if (rc != EOK)
-			goto error;
-	}
-
-	contacts->repo = repo;
-	*rcontacts = contacts;
-
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	if (repo != NULL)
-		(void) sif_close(repo);
-	if (contacts != NULL)
-		free(contacts);
-	return rc;
-}
-
-/** Load contact entries from SIF repository.
- *
- * @param nentries Entries node
- * @param contacts Contacts object to load to
- * @return EOK on success or error code
- */
-static errno_t contacts_load(sif_node_t *nentries, contacts_t *contacts)
-{
-	sif_node_t *nentry;
-	contacts_entry_t *entry;
-	const char *name;
-
-	contacts->nentries = nentries;
-
-	nentry = sif_node_first_child(nentries);
-	while (nentry != NULL) {
-		if (str_cmp(sif_node_get_type(nentry), "entry") != 0)
-			return EIO;
-
-		entry = calloc(1, sizeof(contacts_entry_t));
-		if (entry == NULL)
-			return ENOMEM;
-
-		name = sif_node_get_attr(nentry, "name");
-		if (name == NULL) {
-			free(entry);
-			return EIO;
-		}
-
-		entry->name = str_dup(name);
-		if (entry->name == NULL) {
-			free(entry);
-			return ENOMEM;
-		}
-
-		entry->contacts = contacts;
-		entry->nentry = nentry;
-		list_append(&entry->lentries, &contacts->entries);
-
-		nentry = sif_node_next_child(nentry);
-	}
-
-	return EOK;
-}
-
-/** Interaction to create new contact.
- *
- * @param contacts Contacts
- */
-static errno_t contacts_create_contact(contacts_t *contacts)
-{
-	tinput_t *tinput;
-	sif_trans_t *trans = NULL;
-	sif_node_t *nentry;
-	contacts_entry_t *entry = NULL;
-	errno_t rc;
-	char *cname = NULL;
-
-	tinput = tinput_new();
-	if (tinput == NULL)
-		return ENOMEM;
-
-	printf("Contact name:\n");
-
-	rc = tinput_set_prompt(tinput, "?> ");
-	if (rc != EOK)
-		goto error;
-
-	rc = tinput_read(tinput, &cname);
-	if (rc != EOK)
-		goto error;
-
-	entry = calloc(1, sizeof(contacts_entry_t));
-	if (entry == NULL) {
-		rc = ENOMEM;
-		goto error;
-	}
-
-	rc = sif_trans_begin(contacts->repo, &trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_append_child(trans, contacts->nentries, "entry", &nentry);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_set_attr(trans, nentry, "name", cname);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
-
-	trans = NULL;
-	entry->contacts = contacts;
-	entry->nentry = nentry;
-	entry->name = cname;
-	list_append(&entry->lentries, &contacts->entries);
-
-	tinput_destroy(tinput);
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	if (cname != NULL)
-		free(cname);
-	if (entry != NULL)
-		free(entry);
-	return rc;
-}
-
-/** Interaction to delete contact.
- *
- * @param contacts Contacts
- */
-static errno_t contacts_delete_contact(contacts_t *contacts)
-{
-	nchoice_t *choice = NULL;
-	contacts_entry_t *entry;
-	sif_trans_t *trans = NULL;
-	errno_t rc;
-	void *sel;
-
-	rc = nchoice_create(&choice);
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	rc = nchoice_set_prompt(choice, "Select contact to delete");
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	entry = contacts_first(contacts);
-	while (entry != NULL) {
-		rc = nchoice_add(choice, entry->name, (void *)entry, 0);
-		if (rc != EOK) {
-			assert(rc == ENOMEM);
-			printf("Out of memory.\n");
-			goto error;
-		}
-
-		entry = contacts_next(entry);
-	}
-
-	rc = nchoice_add(choice, "Cancel", NULL, 0);
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	rc = nchoice_get(choice, &sel);
-	if (rc != EOK) {
-		printf("Error getting user selection.\n");
-		return rc;
-	}
-
-	if (sel != NULL) {
-		entry = (contacts_entry_t *)sel;
-
-		rc = sif_trans_begin(contacts->repo, &trans);
-		if (rc != EOK)
-			goto error;
-
-		sif_node_destroy(trans, entry->nentry);
-
-		rc = sif_trans_end(trans);
-		if (rc != EOK)
-			goto error;
-
-		trans = NULL;
-
-		list_remove(&entry->lentries);
-		contacts_entry_delete(entry);
-	}
-
-	nchoice_destroy(choice);
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	if (choice != NULL)
-		nchoice_destroy(choice);
-	return rc;
-}
-
-/** Close contacts repo.
- *
- * @param contacts Contacts
- */
-static void contacts_close(contacts_t *contacts)
-{
-	contacts_entry_t *entry;
-
-	sif_close(contacts->repo);
-
-	entry = contacts_first(contacts);
-	while (entry != NULL) {
-		list_remove(&entry->lentries);
-		contacts_entry_delete(entry);
-		entry = contacts_first(contacts);
-	}
-
-	free(contacts);
-}
-
-/** Get first contacts entry.
- *
- * @param contacts Contacts
- * @return First entry or @c NULL if there is none
- */
-static contacts_entry_t *contacts_first(contacts_t *contacts)
-{
-	link_t *link;
-
-	link = list_first(&contacts->entries);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, contacts_entry_t, lentries);
-}
-
-/** Get next contacts entry.
- *
- * @param cur Current entry
- * @return Next entry or @c NULL if there is none
- */
-static contacts_entry_t *contacts_next(contacts_entry_t *cur)
-{
-	link_t *link;
-
-	link = list_next(&cur->lentries, &cur->contacts->entries);
-	if (link == NULL)
-		return NULL;
-
-	return list_get_instance(link, contacts_entry_t, lentries);
-}
-
-/** Delete entry structure from memory.
- *
- * @param entry Contacts entry
- */
-static void contacts_entry_delete(contacts_entry_t *entry)
-{
-	if (entry == NULL)
-		return;
-
-	if (entry->name != NULL)
-		free(entry->name);
-
-	free(entry);
-}
-
-/** List all contacts.
- *
- * @param contacts Contacts
- */
-static void contacts_list_all(contacts_t *contacts)
-{
-	contacts_entry_t *entry;
-
-	entry = contacts_first(contacts);
-	while (entry != NULL) {
-		printf(" * %s\n", entry->name);
-		entry = contacts_next(entry);
-	}
-}
-
-/** Run contacts main menu.
- *
- * @param contacts Contacts
- * @return EOK on success or error code
- */
-static errno_t contacts_main(contacts_t *contacts)
-{
-	nchoice_t *choice = NULL;
-	errno_t rc;
-	bool quit = false;
-	void *sel;
-
-	rc = nchoice_create(&choice);
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	rc = nchoice_set_prompt(choice, "Select action");
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	rc = nchoice_add(choice, "Create contact",
-	    (void *)ac_create_contact, 0);
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	rc = nchoice_add(choice, "Delete contact",
-	    (void *)ac_delete_contact, 0);
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	rc = nchoice_add(choice, "Exit",
-	    (void *)ac_exit, 0);
-	if (rc != EOK) {
-		assert(rc == ENOMEM);
-		printf("Out of memory.\n");
-		goto error;
-	}
-
-	while (!quit) {
-		contacts_list_all(contacts);
-
-		rc = nchoice_get(choice, &sel);
-		if (rc != EOK) {
-			printf("Error getting user selection.\n");
-			return rc;
-		}
-
-		switch ((contact_action_t)sel) {
-		case ac_create_contact:
-			(void) contacts_create_contact(contacts);
-			break;
-		case ac_delete_contact:
-			(void) contacts_delete_contact(contacts);
-			break;
-		case ac_exit:
-			quit = true;
-			break;
-		}
-	}
-
-	nchoice_destroy(choice);
-	return EOK;
-error:
-	if (choice != NULL)
-		nchoice_destroy(choice);
-	return rc;
-}
-
-int main(void)
-{
-	errno_t rc;
-	contacts_t *contacts = NULL;
-
-	rc = contacts_open("contacts.sif", &contacts);
-	if (rc != EOK)
-		return 1;
-
-	rc = contacts_main(contacts);
-	contacts_close(contacts);
-
-	if (rc != EOK)
-		return 1;
-
-	return 0;
-}
Index: pace/app/contacts/doc/doxygroups.h
===================================================================
--- uspace/app/contacts/doc/doxygroups.h	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ 	(revision )
@@ -1,4 +1,0 @@
-/** @addtogroup contacts contacts
- * @brief Contact list application
- * @ingroup apps
- */
Index: pace/app/contacts/meson.build
===================================================================
--- uspace/app/contacts/meson.build	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ 	(revision )
@@ -1,30 +1,0 @@
-#
-# Copyright (c) 2018 Jiri Svoboda
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without
-# modification, are permitted provided that the following conditions
-# are met:
-#
-# - Redistributions of source code must retain the above copyright
-#   notice, this list of conditions and the following disclaimer.
-# - Redistributions in binary form must reproduce the above copyright
-#   notice, this list of conditions and the following disclaimer in the
-#   documentation and/or other materials provided with the distribution.
-# - The name of the author may not be used to endorse or promote products
-#   derived from this software without specific prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
-# IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
-# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
-# IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
-# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
-# NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
-# THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-#
-
-deps = [ 'clui', 'sif' ]
-src = files('contacts.c')
Index: uspace/app/meson.build
===================================================================
--- uspace/app/meson.build	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/app/meson.build	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -34,5 +34,4 @@
 	'blkdump',
 	'calculator',
-	'contacts',
 	'corecfg',
 	'cpptest',
Index: uspace/app/taskbar-cfg/smeedit.c
===================================================================
--- uspace/app/taskbar-cfg/smeedit.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/app/taskbar-cfg/smeedit.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -403,4 +403,5 @@
 
 		startmenu_repaint(smee->startmenu);
+		(void)tbarcfg_sync(smee->startmenu->tbarcfg->tbarcfg);
 		(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 	} else {
@@ -418,6 +419,7 @@
 			return;
 
-		(void)smenu_entry_save(smee->smentry->entry);
+		(void)tbarcfg_sync(smee->startmenu->tbarcfg->tbarcfg);
 		startmenu_entry_update(smee->smentry);
+		(void)tbarcfg_sync(smee->startmenu->tbarcfg->tbarcfg);
 		(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 	}
Index: uspace/app/taskbar-cfg/startmenu.c
===================================================================
--- uspace/app/taskbar-cfg/startmenu.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/app/taskbar-cfg/startmenu.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -515,4 +515,5 @@
 
 	(void)smee;
+	(void)tbarcfg_sync(smenu->tbarcfg->tbarcfg);
 	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
@@ -534,4 +535,5 @@
 	(void)startmenu_insert(smenu, entry, &smentry);
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_sync(smenu->tbarcfg->tbarcfg);
 	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
@@ -620,5 +622,4 @@
 	startmenu_t *smenu = (startmenu_t *)arg;
 	startmenu_entry_t *smentry;
-	errno_t rc;
 
 	(void)pbutton;
@@ -628,11 +629,10 @@
 		return;
 
-	rc = smenu_entry_destroy(smentry->entry);
-	if (rc != EOK)
-		return;
-
+	smenu_entry_destroy(smentry->entry);
 	ui_list_entry_delete(smentry->lentry);
 	free(smentry);
+
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_sync(smenu->tbarcfg->tbarcfg);
 	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
@@ -673,5 +673,4 @@
 	startmenu_t *smenu = (startmenu_t *)arg;
 	startmenu_entry_t *smentry;
-	errno_t rc;
 
 	(void)pbutton;
@@ -681,11 +680,9 @@
 		return;
 
-	rc = smenu_entry_move_up(smentry->entry);
-	if (rc != EOK)
-		return;
-
+	smenu_entry_move_up(smentry->entry);
 	ui_list_entry_move_up(smentry->lentry);
 
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_sync(smenu->tbarcfg->tbarcfg);
 	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
@@ -700,5 +697,4 @@
 	startmenu_t *smenu = (startmenu_t *)arg;
 	startmenu_entry_t *smentry;
-	errno_t rc;
 
 	(void)pbutton;
@@ -708,11 +704,9 @@
 		return;
 
-	rc = smenu_entry_move_down(smentry->entry);
-	if (rc != EOK)
-		return;
-
+	smenu_entry_move_down(smentry->entry);
 	ui_list_entry_move_down(smentry->lentry);
 
 	(void)ui_control_paint(ui_list_ctl(smenu->entries_list));
+	(void)tbarcfg_sync(smenu->tbarcfg->tbarcfg);
 	(void)tbarcfg_notify(TBARCFG_NOTIFY_DEFAULT);
 }
Index: uspace/lib/sif/include/sif.h
===================================================================
--- uspace/lib/sif/include/sif.h	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/sif/include/sif.h	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,16 +41,14 @@
 
 struct sif_sess;
-typedef struct sif_sess sif_sess_t;
-
-struct sif_trans;
-typedef struct sif_trans sif_trans_t;
+typedef struct sif_sess sif_doc_t;
 
 struct sif_node;
 typedef struct sif_node sif_node_t;
 
-errno_t sif_create(const char *, sif_sess_t **);
-errno_t sif_open(const char *, sif_sess_t **);
-errno_t sif_close(sif_sess_t *);
-sif_node_t *sif_get_root(sif_sess_t *);
+errno_t sif_new(sif_doc_t **);
+errno_t sif_load(const char *, sif_doc_t **);
+errno_t sif_save(sif_doc_t *, const char *);
+void sif_delete(sif_doc_t *);
+sif_node_t *sif_get_root(sif_doc_t *);
 
 sif_node_t *sif_node_first_child(sif_node_t *);
@@ -59,20 +57,12 @@
 const char *sif_node_get_attr(sif_node_t *, const char *);
 
-errno_t sif_trans_begin(sif_sess_t *, sif_trans_t **);
-void sif_trans_abort(sif_trans_t *);
-errno_t sif_trans_end(sif_trans_t *);
-
-errno_t sif_node_prepend_child(sif_trans_t *, sif_node_t *, const char *,
-    sif_node_t **);
-errno_t sif_node_append_child(sif_trans_t *, sif_node_t *, const char *,
-    sif_node_t **);
-errno_t sif_node_insert_before(sif_trans_t *, sif_node_t *, const char *,
-    sif_node_t **);
-errno_t sif_node_insert_after(sif_trans_t *, sif_node_t *, const char *,
-    sif_node_t **);
-void sif_node_destroy(sif_trans_t *, sif_node_t *);
-errno_t sif_node_set_attr(sif_trans_t *, sif_node_t *, const char *,
+errno_t sif_node_prepend_child(sif_node_t *, const char *, sif_node_t **);
+errno_t sif_node_append_child(sif_node_t *, const char *, sif_node_t **);
+errno_t sif_node_insert_before(sif_node_t *, const char *, sif_node_t **);
+errno_t sif_node_insert_after(sif_node_t *, const char *, sif_node_t **);
+void sif_node_destroy(sif_node_t *);
+errno_t sif_node_set_attr(sif_node_t *, const char *,
     const char *);
-void sif_node_unset_attr(sif_trans_t *, sif_node_t *, const char *);
+void sif_node_unset_attr(sif_node_t *, const char *);
 
 #endif
Index: uspace/lib/sif/src/sif.c
===================================================================
--- uspace/lib/sif/src/sif.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/sif/src/sif.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2023 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -33,9 +33,6 @@
  *
  * Structured Information Format (SIF) is an API that allows an application
- * to maintain data in a persistent repository in a format that
- *
- *  - is structured (and hence extensible)
- *  - allows atomic (transactional) updates
- *  - allows efficient updates
+ * to maintain data in a persistent repository in a format that is
+ * structured (and hence extensible).
  *
  * SIF is meant to be used as the basis for the storage backend used to
@@ -44,18 +41,4 @@
  * similar to an XML document that contains just tags with attributes
  * (but no text).
- *
- * SIF can thus naturally express ordered lists (unlike a relational database).
- * When contrasted to a (relational) database, SIF is much more primitive.
- *
- * In particular, SIF
- *
- *  - does not run on a separate server
- *  - des not handle concurrency
- *  - does not have a notion of types or data validation
- *  - does not understand relations
- *  - does not implement any kind of search/queries
- *  - does not deal with data sets large enough not to fit in primary memory
- *
- * any kind of structure data validation is left up to the application.
  */
 
@@ -172,28 +155,19 @@
 }
 
-/** Create and open a SIF repository.
- *
- * @param fname File name
- * @param rsess Place to store pointer to new session.
+/** Create SIF document.
+ *
+ * @param rdoc Place to store pointer to new document.
  *
  * @return EOK on success or error code
  */
-errno_t sif_create(const char *fname, sif_sess_t **rsess)
-{
-	sif_sess_t *sess;
+errno_t sif_new(sif_doc_t **rdoc)
+{
+	sif_doc_t *doc;
 	sif_node_t *root = NULL;
-	sif_trans_t *trans = NULL;
 	errno_t rc;
-	FILE *f;
-
-	sess = calloc(1, sizeof(sif_sess_t));
-	if (sess == NULL)
-		return ENOMEM;
-
-	sess->fname = str_dup(fname);
-	if (sess->fname == NULL) {
-		rc = ENOMEM;
-		goto error;
-	}
+
+	doc = calloc(1, sizeof(sif_doc_t));
+	if (doc == NULL)
+		return ENOMEM;
 
 	root = sif_node_new(NULL);
@@ -209,54 +183,34 @@
 	}
 
-	f = fopen(fname, "wx");
-	if (f == NULL) {
-		rc = EIO;
-		goto error;
-	}
-
-	sess->f = f;
-	sess->root = root;
-
-	/* Run a dummy trasaction to marshall initial repo state to file */
-	rc = sif_trans_begin(sess, &trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
-
-	*rsess = sess;
+	doc->root = root;
+
+	*rdoc = doc;
 	return EOK;
 error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
 	sif_node_delete(root);
-	if (sess->fname != NULL)
-		free(sess->fname);
-	free(sess);
+	free(doc);
 	return rc;
 }
 
-/** Open an existing SIF repository.
+/** Load SIF document.
  *
  * @param fname File name
- * @param rsess Place to store pointer to new session.
+ * @param rdoc Place to store pointer to new document.
  *
  * @return EOK on success or error code
  */
-errno_t sif_open(const char *fname, sif_sess_t **rsess)
-{
-	sif_sess_t *sess;
+errno_t sif_load(const char *fname, sif_doc_t **rdoc)
+{
+	sif_doc_t *doc;
 	sif_node_t *root = NULL;
 	errno_t rc;
 	FILE *f;
 
-	sess = calloc(1, sizeof(sif_sess_t));
-	if (sess == NULL)
-		return ENOMEM;
-
-	sess->fname = str_dup(fname);
-	if (sess->fname == NULL) {
+	doc = calloc(1, sizeof(sif_doc_t));
+	if (doc == NULL)
+		return ENOMEM;
+
+	doc->fname = str_dup(fname);
+	if (doc->fname == NULL) {
 		rc = ENOMEM;
 		goto error;
@@ -278,45 +232,31 @@
 	}
 
-	sess->root = root;
-
-	sess->f = f;
-	sess->root = root;
-	*rsess = sess;
+	doc->root = root;
+	*rdoc = doc;
 	return EOK;
 error:
 	sif_node_delete(root);
-	if (sess->fname != NULL)
-		free(sess->fname);
-	free(sess);
+	free(doc);
 	return rc;
 }
 
-/** Close SIF session.
- *
- * @param sess SIF session
+/** Delete SIF document.
+ *
+ * @param doc SIF document
  * @return EOK on success or error code
  */
-errno_t sif_close(sif_sess_t *sess)
-{
-	sif_node_delete(sess->root);
-
-	if (fclose(sess->f) < 0) {
-		free(sess);
-		return EIO;
-	}
-
-	if (sess->fname != NULL)
-		free(sess->fname);
-	free(sess);
-	return EOK;
+void sif_delete(sif_doc_t *doc)
+{
+	sif_node_delete(doc->root);
+	free(doc);
 }
 
 /** Return root node.
  *
- * @param sess SIF session
- */
-sif_node_t *sif_get_root(sif_sess_t *sess)
-{
-	return sess->root;
+ * @param doc SIF document
+ */
+sif_node_t *sif_get_root(sif_doc_t *doc)
+{
+	return doc->root;
 }
 
@@ -383,61 +323,40 @@
 }
 
-/** Begin SIF transaction.
- *
- * @param trans Transaction
+/** Save SIF document to file.
+ * *
+ * @param doc SIF document
+ * @param fname File name
  * @return EOK on success or error code
  */
-errno_t sif_trans_begin(sif_sess_t *sess, sif_trans_t **rtrans)
-{
-	sif_trans_t *trans;
-
-	trans = calloc(1, sizeof(sif_trans_t));
-	if (trans == NULL)
-		return ENOMEM;
-
-	trans->sess = sess;
-	*rtrans = trans;
-	return EOK;
-}
-
-/** Commit SIF transaction.
- *
- * Commit and free the transaction. If an error is returned, that means
- * the transaction has not been freed (and sif_trans_abort() must be used).
- *
- * @param trans Transaction
- * @return EOK on success or error code
- */
-errno_t sif_trans_end(sif_trans_t *trans)
-{
+errno_t sif_save(sif_doc_t *doc, const char *fname)
+{
+	FILE *f = NULL;
 	errno_t rc;
 
-	(void) fclose(trans->sess->f);
-
-	trans->sess->f = fopen(trans->sess->fname, "w");
-	if (trans->sess->f == NULL)
-		return EIO;
-
-	rc = sif_export_node(trans->sess->root, trans->sess->f);
+	f = fopen(fname, "w");
+	if (f == NULL) {
+		rc = EIO;
+		goto error;
+	}
+
+	rc = sif_export_node(doc->root, f);
 	if (rc != EOK)
-		return rc;
-
-	if (fputc('\n', trans->sess->f) == EOF)
-		return EIO;
-
-	if (fflush(trans->sess->f) == EOF)
-		return EIO;
-
-	free(trans);
-	return EOK;
-}
-
-/** Abort SIF transaction.
- *
- * @param trans Transaction
- */
-void sif_trans_abort(sif_trans_t *trans)
-{
-	free(trans);
+		goto error;
+
+	if (fputc('\n', f) == EOF) {
+		rc = EIO;
+		goto error;
+	}
+
+	if (fflush(f) == EOF) {
+		rc = EIO;
+		goto error;
+	}
+
+	return EOK;
+error:
+	if (f != NULL)
+		fclose(f);
+	return rc;
 }
 
@@ -447,5 +366,4 @@
  * @a parent.
  *
- * @param trans Transaction
  * @param parent Parent node
  * @param ctype Child type
@@ -454,6 +372,6 @@
  * @return EOK on success or ENOMEM if out of memory
  */
-errno_t sif_node_prepend_child(sif_trans_t *trans, sif_node_t *parent,
-    const char *ctype, sif_node_t **rchild)
+errno_t sif_node_prepend_child(sif_node_t *parent, const char *ctype,
+    sif_node_t **rchild)
 {
 	sif_node_t *child;
@@ -479,5 +397,4 @@
  * Create a new child and append it at the end of children list of @a parent.
  *
- * @param trans Transaction
  * @param parent Parent node
  * @param ctype Child type
@@ -486,6 +403,6 @@
  * @return EOK on success or ENOMEM if out of memory
  */
-errno_t sif_node_append_child(sif_trans_t *trans, sif_node_t *parent,
-    const char *ctype, sif_node_t **rchild)
+errno_t sif_node_append_child(sif_node_t *parent, const char *ctype,
+    sif_node_t **rchild)
 {
 	sif_node_t *child;
@@ -511,5 +428,4 @@
  * Create a new child and insert it before an existing child.
  *
- * @param trans Transaction
  * @param sibling Sibling before which to insert
  * @param ctype Child type
@@ -518,6 +434,6 @@
  * @return EOK on success or ENOMEM if out of memory
  */
-errno_t sif_node_insert_before(sif_trans_t *trans, sif_node_t *sibling,
-    const char *ctype, sif_node_t **rchild)
+errno_t sif_node_insert_before(sif_node_t *sibling, const char *ctype,
+    sif_node_t **rchild)
 {
 	sif_node_t *child;
@@ -543,5 +459,4 @@
  * Create a new child and insert it after an existing child.
  *
- * @param trans Transaction
  * @param sibling Sibling after which to insert
  * @param ctype Child type
@@ -550,6 +465,6 @@
  * @return EOK on success or ENOMEM if out of memory
  */
-errno_t sif_node_insert_after(sif_trans_t *trans, sif_node_t *sibling,
-    const char *ctype, sif_node_t **rchild)
+errno_t sif_node_insert_after(sif_node_t *sibling, const char *ctype,
+    sif_node_t **rchild)
 {
 	sif_node_t *child;
@@ -573,11 +488,7 @@
 /** Destroy SIF node.
  *
- * This function does not return an error, but the transaction may still
- * fail to complete.
- *
- * @param trans Transaction
  * @param node Node to destroy
  */
-void sif_node_destroy(sif_trans_t *trans, sif_node_t *node)
+void sif_node_destroy(sif_node_t *node)
 {
 	list_remove(&node->lparent);
@@ -587,5 +498,4 @@
 /** Set node attribute.
  *
- * @param trans Transaction
  * @param node SIF node
  * @param aname Attribute name
@@ -594,6 +504,6 @@
  * @return EOK on success, ENOMEM if out of memory
  */
-errno_t sif_node_set_attr(sif_trans_t *trans, sif_node_t *node,
-    const char *aname, const char *avalue)
+errno_t sif_node_set_attr(sif_node_t *node, const char *aname,
+    const char *avalue)
 {
 	odlink_t *link;
@@ -636,13 +546,8 @@
 /** Unset node attribute.
  *
- * This function does not return an error, but the transaction may still
- * fail to complete.
- *
- * @param trans Transaction
  * @param node Node
  * @param aname Attribute name
  */
-void sif_node_unset_attr(sif_trans_t *trans, sif_node_t *node,
-    const char *aname)
+void sif_node_unset_attr(sif_node_t *node, const char *aname)
 {
 	odlink_t *link;
Index: uspace/lib/sif/test/sif.c
===================================================================
--- uspace/lib/sif/test/sif.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/sif/test/sif.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -36,32 +36,19 @@
 
 PCUT_TEST_SUITE(sif);
-
-/** Test sif_create. */
+#if 0
+/** Test sif_new and sif_delete. */
 PCUT_TEST(sif_create)
 {
-	sif_sess_t *sess;
-	errno_t rc;
-	int rv;
-	char *fname;
-	char *p;
-
-	fname = calloc(L_tmpnam, 1);
-	PCUT_ASSERT_NOT_NULL(fname);
-
-	p = tmpnam(fname);
-	PCUT_ASSERT_TRUE(p == fname);
-
-	rc = sif_create(fname, &sess);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rc = sif_close(sess);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
-
-	rv = remove(fname);
-	PCUT_ASSERT_INT_EQUALS(0, rv);
-}
-
-/** Test sif_open. */
-PCUT_TEST(sif_open)
+	sif_doc_t *doc;
+	errno_t rc;
+
+	rc = sif_new(&doc);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	sif_delete(doc);
+}
+
+/** Test sif_load. */
+PCUT_TEST(sif_load)
 {
 	sif_sess_t *sess;
@@ -601,4 +588,4 @@
 	PCUT_ASSERT_INT_EQUALS(0, rv);
 }
-
+#endif
 PCUT_EXPORT(sif);
Index: uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/tbarcfg/include/tbarcfg/tbarcfg.h	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -47,4 +47,5 @@
 extern errno_t tbarcfg_open(const char *, tbarcfg_t **);
 extern void tbarcfg_close(tbarcfg_t *);
+extern errno_t tbarcfg_sync(tbarcfg_t *);
 extern smenu_entry_t *tbarcfg_smenu_first(tbarcfg_t *);
 extern smenu_entry_t *tbarcfg_smenu_next(smenu_entry_t *);
@@ -58,11 +59,10 @@
 extern errno_t smenu_entry_set_cmd(smenu_entry_t *, const char *);
 extern void smenu_entry_set_terminal(smenu_entry_t *, bool);
-extern errno_t smenu_entry_save(smenu_entry_t *);
 extern errno_t smenu_entry_create(tbarcfg_t *, const char *, const char *,
     bool, smenu_entry_t **);
 extern errno_t smenu_entry_sep_create(tbarcfg_t *, smenu_entry_t **);
-extern errno_t smenu_entry_destroy(smenu_entry_t *);
-extern errno_t smenu_entry_move_up(smenu_entry_t *);
-extern errno_t smenu_entry_move_down(smenu_entry_t *);
+extern void smenu_entry_destroy(smenu_entry_t *);
+extern void smenu_entry_move_up(smenu_entry_t *);
+extern void smenu_entry_move_down(smenu_entry_t *);
 extern errno_t tbarcfg_listener_create(const char *, void (*)(void *),
     void *, tbarcfg_listener_t **);
Index: uspace/lib/tbarcfg/private/tbarcfg.h
===================================================================
--- uspace/lib/tbarcfg/private/tbarcfg.h	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/tbarcfg/private/tbarcfg.h	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -39,5 +39,4 @@
 
 #include <adt/list.h>
-#include <sif.h>
 #include <stdbool.h>
 #include <types/tbarcfg/tbarcfg.h>
@@ -45,10 +44,8 @@
 /** Taskbar configuration */
 struct tbarcfg {
-	/** Repository session */
-	sif_sess_t *repo;
+	/** Configuration file path */
+	char *cfgpath;
 	/** List of start menu entries (smenu_entry_t) */
 	list_t entries;
-	/** Entries SIF node */
-	sif_node_t *nentries;
 };
 
@@ -59,6 +56,4 @@
 	/** Link to @c smenu->entries */
 	link_t lentries;
-	/** SIF node (persistent storage) */
-	sif_node_t *nentry;
 	/** Is this a separator entry */
 	bool separator;
@@ -79,9 +74,4 @@
 } tbarcfg_listener_t;
 
-extern errno_t smenu_entry_new(tbarcfg_t *, sif_node_t *, const char *,
-    const char *, bool, smenu_entry_t **);
-extern errno_t smenu_entry_sep_new(tbarcfg_t *, sif_node_t *, smenu_entry_t **);
-extern void smenu_entry_delete(smenu_entry_t *);
-
 #endif
 
Index: uspace/lib/tbarcfg/src/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/src/tbarcfg.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/tbarcfg/src/tbarcfg.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -47,4 +47,5 @@
 
 static void tbarcfg_notify_conn(ipc_call_t *, void *);
+static errno_t smenu_entry_save(smenu_entry_t *, sif_node_t *);
 
 /** Create taskbar configuration.
@@ -57,8 +58,8 @@
 {
 	tbarcfg_t *tbcfg;
-	sif_sess_t *repo = NULL;
+	sif_doc_t *doc = NULL;
 	sif_node_t *rnode;
-	errno_t rc;
-	sif_trans_t *trans = NULL;
+	sif_node_t *nentries;
+	errno_t rc;
 
 	tbcfg = calloc(1, sizeof(tbarcfg_t));
@@ -69,32 +70,35 @@
 
 	list_initialize(&tbcfg->entries);
-
-	rc = sif_create(repopath, &repo);
-	if (rc != EOK)
-		goto error;
-
-	tbcfg->repo = repo;
-
-	rnode = sif_get_root(repo);
-
-	rc = sif_trans_begin(repo, &trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_append_child(trans, rnode, "entries", &tbcfg->nentries);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
+	tbcfg->cfgpath = str_dup(repopath);
+	if (tbcfg->cfgpath == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = sif_new(&doc);
+	if (rc != EOK)
+		goto error;
+
+	rnode = sif_get_root(doc);
+
+	rc = sif_node_append_child(rnode, "entries", &nentries);
+	if (rc != EOK)
+		goto error;
+
+	(void)nentries;
+
+	rc = sif_save(doc, repopath);
+	if (rc != EOK)
+		goto error;
+
+	sif_delete(doc);
 
 	*rtbcfg = tbcfg;
 	return EOK;
 error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	if (repo != NULL)
-		sif_close(repo);
+	if (doc != NULL)
+		sif_delete(doc);
+	if (tbcfg != NULL && tbcfg->cfgpath != NULL)
+		free(tbcfg->cfgpath);
 	if (tbcfg != NULL)
 		free(tbcfg);
@@ -111,5 +115,6 @@
 {
 	tbarcfg_t *tbcfg;
-	sif_sess_t *repo = NULL;
+	sif_doc_t *doc = NULL;
+	sif_node_t *nentries;
 	sif_node_t *rnode;
 	sif_node_t *nentry;
@@ -128,14 +133,17 @@
 
 	list_initialize(&tbcfg->entries);
-
-	rc = sif_open(repopath, &repo);
-	if (rc != EOK)
-		goto error;
-
-	tbcfg->repo = repo;
-
-	rnode = sif_get_root(repo);
-	tbcfg->nentries = sif_node_first_child(rnode);
-	ntype = sif_node_get_type(tbcfg->nentries);
+	tbcfg->cfgpath = str_dup(repopath);
+	if (tbcfg->cfgpath == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	rc = sif_load(repopath, &doc);
+	if (rc != EOK)
+		goto error;
+
+	rnode = sif_get_root(doc);
+	nentries = sif_node_first_child(rnode);
+	ntype = sif_node_get_type(nentries);
 	if (str_cmp(ntype, "entries") != 0) {
 		rc = EIO;
@@ -143,5 +151,5 @@
 	}
 
-	nentry = sif_node_first_child(tbcfg->nentries);
+	nentry = sif_node_first_child(nentries);
 	while (nentry != NULL) {
 		ntype = sif_node_get_type(nentry);
@@ -174,10 +182,10 @@
 				terminal = "n";
 
-			rc = smenu_entry_new(tbcfg, nentry, caption, cmd,
+			rc = smenu_entry_create(tbcfg, caption, cmd,
 			    str_cmp(terminal, "y") == 0, NULL);
 			if (rc != EOK)
 				goto error;
 		} else {
-			rc = smenu_entry_sep_new(tbcfg, nentry, NULL);
+			rc = smenu_entry_sep_create(tbcfg, NULL);
 			if (rc != EOK)
 				goto error;
@@ -187,9 +195,12 @@
 	}
 
+	sif_delete(doc);
 	*rtbcfg = tbcfg;
 	return EOK;
 error:
-	if (repo != NULL)
-		sif_close(repo);
+	if (doc != NULL)
+		sif_delete(doc);
+	if (tbcfg != NULL && tbcfg->cfgpath != NULL)
+		free(tbcfg->cfgpath);
 	if (tbcfg != NULL)
 		free(tbcfg);
@@ -207,10 +218,59 @@
 	entry = tbarcfg_smenu_first(tbcfg);
 	while (entry != NULL) {
-		smenu_entry_delete(entry);
+		smenu_entry_destroy(entry);
 		entry = tbarcfg_smenu_first(tbcfg);
 	}
 
-	(void)sif_close(tbcfg->repo);
+	free(tbcfg->cfgpath);
 	free(tbcfg);
+}
+
+/** Synchronize taskbar configuration to config file.
+ *
+ * @param repopath Pathname of the menu repository
+ * @param rtbcfg Place to store pointer to taskbar configuration
+ * @return EOK on success or an error code
+ */
+errno_t tbarcfg_sync(tbarcfg_t *tbcfg)
+{
+	sif_doc_t *doc = NULL;
+	sif_node_t *nentries;
+	sif_node_t *rnode;
+	smenu_entry_t *entry;
+	errno_t rc;
+
+	rc = sif_new(&doc);
+	if (rc != EOK)
+		goto error;
+
+	rnode = sif_get_root(doc);
+
+	rc = sif_node_append_child(rnode, "entries", &nentries);
+	if (rc != EOK)
+		goto error;
+
+	entry = tbarcfg_smenu_first(tbcfg);
+	while (entry != NULL) {
+		rc = smenu_entry_save(entry, nentries);
+		if (rc != EOK)
+			goto error;
+
+		entry = tbarcfg_smenu_next(entry);
+	}
+
+	rc = sif_save(doc, tbcfg->cfgpath);
+	if (rc != EOK)
+		goto error;
+
+	sif_delete(doc);
+	return EOK;
+error:
+	if (doc != NULL)
+		sif_delete(doc);
+	if (tbcfg != NULL && tbcfg->cfgpath != NULL)
+		free(tbcfg->cfgpath);
+	if (tbcfg != NULL)
+		free(tbcfg);
+	return rc;
 }
 
@@ -325,5 +385,5 @@
  *
  * Note: To make the change visible to others and persistent,
- * you must call @c smenu_entry_save()
+ * you must call @c tbarcfg_sync()
  *
  * @param entry Start menu entry
@@ -349,5 +409,5 @@
  *
  * Note: To make the change visible to others and persistent,
- * you must call @c smenu_entry_save()
+ * you must call @c tbarcfg_sync()
  *
  * @param entry Start menu entry
@@ -373,5 +433,5 @@
  *
  * Note: To make the change visible to others and persistent,
- * you must call @c smenu_entry_save()
+ * you must call @c tbarcfg_sync()
  *
  * @param entry Start menu entry
@@ -387,27 +447,30 @@
  *
  * @param entry Start menu entry
- * @param trans Transaction
- */
-static errno_t smenu_entry_save_trans(smenu_entry_t *entry, sif_trans_t *trans)
-{
-	errno_t rc;
+ * @param nentries Entries node
+ */
+static errno_t smenu_entry_save(smenu_entry_t *entry, sif_node_t *nentries)
+{
+	sif_node_t *nentry = NULL;
+	errno_t rc;
+
+	rc = sif_node_append_child(nentries, "entry", &nentry);
+	if (rc != EOK)
+		goto error;
 
 	if (entry->separator) {
-		rc = sif_node_set_attr(trans, entry->nentry, "separator", "y");
+		rc = sif_node_set_attr(nentry, "separator", "y");
 		if (rc != EOK)
 			goto error;
 	} else {
-		sif_node_unset_attr(trans, entry->nentry, "separator");
-
-		rc = sif_node_set_attr(trans, entry->nentry, "cmd", entry->cmd);
+		rc = sif_node_set_attr(nentry, "cmd", entry->cmd);
 		if (rc != EOK)
 			goto error;
 
-		rc = sif_node_set_attr(trans, entry->nentry, "caption",
+		rc = sif_node_set_attr(nentry, "caption",
 		    entry->caption);
 		if (rc != EOK)
 			goto error;
 
-		rc = sif_node_set_attr(trans, entry->nentry, "terminal",
+		rc = sif_node_set_attr(nentry, "terminal",
 		    entry->terminal ? "y" : "n");
 		if (rc != EOK)
@@ -417,41 +480,14 @@
 	return EOK;
 error:
+	if (nentry != NULL)
+		sif_node_destroy(nentry);
 	return rc;
 }
 
-/** Save any changes to start menu entry.
- *
- * @param entry Start menu entry
- */
-errno_t smenu_entry_save(smenu_entry_t *entry)
-{
-	sif_trans_t *trans = NULL;
-	errno_t rc;
-
-	rc = sif_trans_begin(entry->smenu->repo, &trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = smenu_entry_save_trans(entry, trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
-
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
-}
-
-/** Allocate a start menu entry and append it to the start menu (internal).
+/** Create new start menu entry and append it to the start menu (internal).
  *
  * This only creates the entry in memory, but does not update the repository.
  *
  * @param smenu Start menu
- * @param nentry Backing SIF node
  * @param caption Caption
  * @param cmd Command to run
@@ -459,6 +495,6 @@
  * @param rentry Place to store pointer to new entry or @c NULL
  */
-errno_t smenu_entry_new(tbarcfg_t *smenu, sif_node_t *nentry,
-    const char *caption, const char *cmd, bool terminal, smenu_entry_t **rentry)
+errno_t smenu_entry_create(tbarcfg_t *smenu, const char *caption,
+    const char *cmd, bool terminal, smenu_entry_t **rentry)
 {
 	smenu_entry_t *entry;
@@ -470,6 +506,4 @@
 		goto error;
 	}
-
-	entry->nentry = nentry;
 
 	entry->caption = str_dup(caption);
@@ -504,5 +538,5 @@
 }
 
-/** Allocate a start menu separator entry and append it to the start menu
+/** Create new start menu separator entry and append it to the start menu
  * (internal).
  *
@@ -510,9 +544,7 @@
  *
  * @param smenu Start menu
- * @param nentry Backing SIF node
  * @param rentry Place to store pointer to new entry or @c NULL
  */
-errno_t smenu_entry_sep_new(tbarcfg_t *smenu, sif_node_t *nentry,
-    smenu_entry_t **rentry)
+errno_t smenu_entry_sep_create(tbarcfg_t *smenu, smenu_entry_t **rentry)
 {
 	smenu_entry_t *entry;
@@ -525,5 +557,4 @@
 	}
 
-	entry->nentry = nentry;
 	entry->separator = true;
 
@@ -538,5 +569,5 @@
 }
 
-/** Delete start menu entry.
+/** Destroy start menu entry.
  *
  * This only deletes the entry from, but does not update the
@@ -545,5 +576,5 @@
  * @param entry Start menu entry
  */
-void smenu_entry_delete(smenu_entry_t *entry)
+void smenu_entry_destroy(smenu_entry_t *entry)
 {
 	list_remove(&entry->lentries);
@@ -555,183 +586,20 @@
 }
 
-/** Create new start menu entry.
- *
- * @param smenu Start menu
- * @param nentry Backing SIF node
- * @param caption Caption
- * @param cmd Command to run
- * @param terminal Start in terminal
- * @param rentry Place to store pointer to new entry or @c NULL
- */
-errno_t smenu_entry_create(tbarcfg_t *smenu, const char *caption,
-    const char *cmd, bool terminal, smenu_entry_t **rentry)
-{
-	sif_node_t *nentry;
-	smenu_entry_t *entry;
-	errno_t rc;
-	sif_trans_t *trans = NULL;
-
-	rc = sif_trans_begin(smenu->repo, &trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_append_child(trans, smenu->nentries, "entry",
-	    &nentry);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_set_attr(trans, nentry, "cmd", cmd);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_set_attr(trans, nentry, "caption", caption);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_set_attr(trans, nentry, "terminal", terminal ? "y" : "n");
-	if (rc != EOK)
-		goto error;
-
-	rc = smenu_entry_new(smenu, nentry, caption, cmd, terminal, &entry);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
-
-	if (rentry != NULL)
-		*rentry = entry;
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
-}
-
-/** Create new start menu separator entry.
- *
- * @param smenu Start menu
- * @param nentry Backing SIF node
- * @param rentry Place to store pointer to new entry or @c NULL
- */
-errno_t smenu_entry_sep_create(tbarcfg_t *smenu, smenu_entry_t **rentry)
-{
-	sif_node_t *nentry;
-	smenu_entry_t *entry;
-	errno_t rc;
-	sif_trans_t *trans = NULL;
-
-	rc = sif_trans_begin(smenu->repo, &trans);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_append_child(trans, smenu->nentries, "entry",
-	    &nentry);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_node_set_attr(trans, nentry, "separator", "y");
-	if (rc != EOK)
-		goto error;
-
-	rc = smenu_entry_sep_new(smenu, nentry, &entry);
-	if (rc != EOK)
-		goto error;
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
-
-	if (rentry != NULL)
-		*rentry = entry;
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
-}
-
-/** Destroy start menu entry.
- *
- * @param entry Start menu entry
- * @return EOK on success or an error code
- */
-errno_t smenu_entry_destroy(smenu_entry_t *entry)
-{
-	errno_t rc;
-	sif_trans_t *trans = NULL;
-
-	rc = sif_trans_begin(entry->smenu->repo, &trans);
-	if (rc != EOK)
-		goto error;
-
-	sif_node_destroy(trans, entry->nentry);
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK)
-		goto error;
-
-	smenu_entry_delete(entry);
-	return EOK;
-error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
-}
-
 /** Move start menu entry up.
  *
  * @param entry Start menu entry
- * @return EOK on success or an error code
- */
-errno_t smenu_entry_move_up(smenu_entry_t *entry)
-{
-	errno_t rc;
-	sif_trans_t *trans = NULL;
-	sif_node_t *nnode = NULL;
-	sif_node_t *old_node;
+ */
+void smenu_entry_move_up(smenu_entry_t *entry)
+{
 	smenu_entry_t *prev;
-
-	rc = sif_trans_begin(entry->smenu->repo, &trans);
-	if (rc != EOK)
-		goto error;
 
 	prev = tbarcfg_smenu_prev(entry);
 	if (prev == NULL) {
 		/* Entry is already at first position, nothing to do. */
-		return EOK;
-	}
-
-	rc = sif_node_insert_before(trans, prev->nentry, "entry", &nnode);
-	if (rc != EOK)
-		goto error;
-
-	old_node = entry->nentry;
-	entry->nentry = nnode;
-
-	rc = smenu_entry_save_trans(entry, trans);
-	if (rc != EOK) {
-		entry->nentry = old_node;
-		goto error;
-	}
-
-	sif_node_destroy(trans, old_node);
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK) {
-		entry->nentry = old_node;
-		goto error;
+		return;
 	}
 
 	list_remove(&entry->lentries);
 	list_insert_before(&entry->lentries, &prev->lentries);
-	return EOK;
-error:
-	if (nnode != NULL)
-		sif_node_destroy(trans, nnode);
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
 }
 
@@ -739,54 +607,17 @@
  *
  * @param entry Start menu entry
- * @return EOK on success or an error code
- */
-errno_t smenu_entry_move_down(smenu_entry_t *entry)
-{
-	errno_t rc;
-	sif_trans_t *trans = NULL;
-	sif_node_t *nnode = NULL;
-	sif_node_t *old_node;
+ */
+void smenu_entry_move_down(smenu_entry_t *entry)
+{
 	smenu_entry_t *next;
-
-	rc = sif_trans_begin(entry->smenu->repo, &trans);
-	if (rc != EOK)
-		goto error;
 
 	next = tbarcfg_smenu_next(entry);
 	if (next == NULL) {
 		/* Entry is already at last position, nothing to do. */
-		return EOK;
-	}
-
-	rc = sif_node_insert_after(trans, next->nentry, "entry", &nnode);
-	if (rc != EOK)
-		goto error;
-
-	old_node = entry->nentry;
-	entry->nentry = nnode;
-
-	rc = smenu_entry_save_trans(entry, trans);
-	if (rc != EOK) {
-		entry->nentry = old_node;
-		goto error;
-	}
-
-	sif_node_destroy(trans, old_node);
-
-	rc = sif_trans_end(trans);
-	if (rc != EOK) {
-		entry->nentry = old_node;
-		goto error;
+		return;
 	}
 
 	list_remove(&entry->lentries);
 	list_insert_after(&entry->lentries, &next->lentries);
-	return EOK;
-error:
-	if (nnode != NULL)
-		sif_node_destroy(trans, nnode);
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
 }
 
Index: uspace/lib/tbarcfg/test/tbarcfg.c
===================================================================
--- uspace/lib/tbarcfg/test/tbarcfg.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/lib/tbarcfg/test/tbarcfg.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -57,4 +57,5 @@
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
+	tbarcfg_sync(tbcfg);
 	tbarcfg_close(tbcfg);
 
@@ -170,4 +171,5 @@
 	PCUT_ASSERT_TRUE(smenu_entry_get_separator(e2));
 
+	tbarcfg_sync(tbcfg);
 	tbarcfg_close(tbcfg);
 
@@ -261,5 +263,5 @@
 	smenu_entry_set_terminal(e, true);
 
-	rc = smenu_entry_save(e);
+	rc = tbarcfg_sync(tbcfg);
 	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
 
@@ -272,4 +274,5 @@
 	PCUT_ASSERT_TRUE(terminal);
 
+	tbarcfg_sync(tbcfg);
 	tbarcfg_close(tbcfg);
 
@@ -359,6 +362,5 @@
 	PCUT_ASSERT_EQUALS(e, f);
 
-	rc = smenu_entry_destroy(e);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_destroy(e);
 
 	f = tbarcfg_smenu_first(tbcfg);
@@ -400,6 +402,5 @@
 	/* Moving the first entry up should have no effect */
 
-	rc = smenu_entry_move_up(e1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_move_up(e1);
 
 	f = tbarcfg_smenu_first(tbcfg);
@@ -408,6 +409,5 @@
 	/* Moving the second entry up should move it to first position */
 
-	rc = smenu_entry_move_up(e2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_move_up(e2);
 
 	f = tbarcfg_smenu_first(tbcfg);
@@ -416,6 +416,5 @@
 	/* Moving the last entry up should move it to second position */
 
-	rc = smenu_entry_move_up(e3);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_move_up(e3);
 
 	f = tbarcfg_smenu_first(tbcfg);
@@ -428,4 +427,5 @@
 	PCUT_ASSERT_EQUALS(e1, f);
 
+	tbarcfg_sync(tbcfg);
 	tbarcfg_close(tbcfg);
 
@@ -496,6 +496,5 @@
 	/* Moving the last entry down should have no effect */
 
-	rc = smenu_entry_move_down(e3);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_move_down(e3);
 
 	f = tbarcfg_smenu_last(tbcfg);
@@ -504,6 +503,5 @@
 	/* Moving the second entry down should move it to last position */
 
-	rc = smenu_entry_move_down(e2);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_move_down(e2);
 
 	f = tbarcfg_smenu_last(tbcfg);
@@ -512,6 +510,5 @@
 	/* Moving the first entry down should move it to second position */
 
-	rc = smenu_entry_move_down(e1);
-	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	smenu_entry_move_down(e1);
 
 	f = tbarcfg_smenu_last(tbcfg);
@@ -524,4 +521,5 @@
 	PCUT_ASSERT_EQUALS(e3, f);
 
+	tbarcfg_sync(tbcfg);
 	tbarcfg_close(tbcfg);
 
Index: uspace/srv/volsrv/types/volume.h
===================================================================
--- uspace/srv/volsrv/types/volume.h	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/srv/volsrv/types/volume.h	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -41,5 +41,4 @@
 #include <refcount.h>
 #include <fibril_synch.h>
-#include <sif.h>
 
 /** Volume */
@@ -57,6 +56,4 @@
 	/** Mount point */
 	char *mountp;
-	/** SIF node for this volume */
-	sif_node_t *nvolume;
 } vol_volume_t;
 
@@ -67,10 +64,8 @@
 	/** Volumes (list of vol_volume_t) */
 	list_t volumes;
-	/** Cconfiguration repo session */
-	sif_sess_t *repo;
-	/** Volumes SIF node */
-	sif_node_t *nvolumes;
 	/** Next ID */
 	sysarg_t next_id;
+	/** Configuration file path */
+	char *cfg_path;
 } vol_volumes_t;
 
Index: uspace/srv/volsrv/volume.c
===================================================================
--- uspace/srv/volsrv/volume.c	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/srv/volsrv/volume.c	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -48,4 +48,5 @@
 #include <fibril_synch.h>
 #include <io/log.h>
+#include <sif.h>
 #include <stdbool.h>
 #include <stdlib.h>
@@ -60,4 +61,5 @@
     vol_volume_t **);
 static errno_t vol_volumes_load(sif_node_t *, vol_volumes_t *);
+static errno_t vol_volumes_save(vol_volumes_t *, sif_node_t *);
 
 /** Allocate new volume structure.
@@ -113,7 +115,7 @@
 {
 	vol_volumes_t *volumes;
-	sif_sess_t *repo = NULL;
-	sif_trans_t *trans = NULL;
+	sif_doc_t *doc = NULL;
 	sif_node_t *node;
+	sif_node_t *nvolumes;
 	const char *ntype;
 	errno_t rc;
@@ -123,4 +125,10 @@
 		return ENOMEM;
 
+	volumes->cfg_path = str_dup(cfg_path);
+	if (volumes->cfg_path == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
 	fibril_mutex_initialize(&volumes->lock);
 	list_initialize(&volumes->volumes);
@@ -128,32 +136,28 @@
 
 	/* Try opening existing repository */
-	rc = sif_open(cfg_path, &repo);
+	rc = sif_load(cfg_path, &doc);
 	if (rc != EOK) {
 		/* Failed to open existing, create new repository */
-		rc = sif_create(cfg_path, &repo);
+		rc = sif_new(&doc);
 		if (rc != EOK)
 			goto error;
 
-		rc = sif_trans_begin(repo, &trans);
+		/* Create 'volumes' node. */
+		rc = sif_node_append_child(sif_get_root(doc), "volumes",
+		    &nvolumes);
 		if (rc != EOK)
 			goto error;
 
-		/* Create 'volumes' node. */
-		rc = sif_node_append_child(trans, sif_get_root(repo),
-		    "volumes", &volumes->nvolumes);
+		rc = sif_save(doc, cfg_path);
 		if (rc != EOK)
 			goto error;
 
-		rc = sif_trans_end(trans);
-		if (rc != EOK)
-			goto error;
-
-		trans = NULL;
+		sif_delete(doc);
 	} else {
 		/*
-		 * Opened existing repo. Find 'volumes' node, should be
-		 * the first child of the root node.
+		 * Loaded existing configuration. Find 'volumes' node, should
+		 * be the first child of the root node.
 		 */
-		node = sif_node_first_child(sif_get_root(repo));
+		node = sif_node_first_child(sif_get_root(doc));
 
 		/* Verify it's the correct node type */
@@ -167,18 +171,48 @@
 		if (rc != EOK)
 			goto error;
-	}
-
-	volumes->repo = repo;
+
+		sif_delete(doc);
+	}
+
 	*rvolumes = volumes;
-
 	return EOK;
 error:
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	if (repo != NULL)
-		(void) sif_close(repo);
+	if (doc != NULL)
+		(void) sif_delete(doc);
+	if (volumes != NULL && volumes->cfg_path != NULL)
+		free(volumes->cfg_path);
 	if (volumes != NULL)
 		free(volumes);
 
+	return rc;
+}
+
+/** Sync volume configuration to config file.
+ *
+ * @param volumes List of volumes
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t vol_volumes_sync(vol_volumes_t *volumes)
+{
+	sif_doc_t *doc = NULL;
+	errno_t rc;
+
+	rc = sif_new(&doc);
+	if (rc != EOK)
+		goto error;
+
+	rc = vol_volumes_save(volumes, sif_get_root(doc));
+	if (rc != EOK)
+		goto error;
+
+	rc = sif_save(doc, volumes->cfg_path);
+	if (rc != EOK)
+		goto error;
+
+	sif_delete(doc);
+	return EOK;
+error:
+	if (doc != NULL)
+		(void) sif_delete(doc);
 	return rc;
 }
@@ -206,5 +240,5 @@
 	}
 
-	(void) sif_close(volumes->repo);
+	free(volumes->cfg_path);
 	free(volumes);
 }
@@ -383,7 +417,5 @@
 	char *mp;
 	char *old_mp;
-	errno_t rc;
-	sif_trans_t *trans = NULL;
-	sif_node_t *nvolume;
+	bool was_persist;
 
 	mp = str_dup(mountp);
@@ -391,86 +423,30 @@
 		return ENOMEM;
 
+	was_persist = vol_volume_is_persist(volume);
+
 	old_mp = volume->mountp;
 	volume->mountp = mp;
 
 	if (vol_volume_is_persist(volume)) {
-		/* Volume is now persistent */
-		if (volume->nvolume == NULL) {
-			/* Prevent volume from being freed */
+		if (!was_persist) {
+			/*
+			 * Volume is now persistent. Prevent it from being
+			 * freed.
+			 */
 			refcount_up(&volume->refcnt);
-
-			/* Create volume node */
-			rc = sif_trans_begin(volume->volumes->repo, &trans);
-			if (rc != EOK)
-				goto error;
-
-			rc = sif_node_append_child(trans,
-			    volume->volumes->nvolumes, "volume", &nvolume);
-			if (rc != EOK)
-				goto error;
-
-			rc = sif_node_set_attr(trans, nvolume, "label",
-			    volume->label);
-			if (rc != EOK)
-				goto error;
-
-			rc = sif_node_set_attr(trans, nvolume, "mountp",
-			    volume->mountp);
-			if (rc != EOK)
-				goto error;
-
-			rc = sif_trans_end(trans);
-			if (rc != EOK)
-				goto error;
-
-			trans = NULL;
-			volume->nvolume = nvolume;
-		} else {
-			/* Allow volume to be freed */
+		}
+	} else {
+		if (was_persist) {
+			/*
+			 * Volume is now non-persistent
+			 * Allow volume to be freed.
+			 */
 			vol_volume_del_ref(volume);
-
-			/* Update volume node */
-			rc = sif_trans_begin(volume->volumes->repo, &trans);
-			if (rc != EOK)
-				goto error;
-
-			rc = sif_node_set_attr(trans, volume->nvolume,
-			    "mountp", volume->mountp);
-			if (rc != EOK)
-				goto error;
-
-			rc = sif_trans_end(trans);
-			if (rc != EOK)
-				goto error;
-
-			trans = NULL;
-		}
-	} else {
-		/* Volume is now non-persistent */
-		if (volume->nvolume != NULL) {
-			/* Delete volume node */
-			rc = sif_trans_begin(volume->volumes->repo, &trans);
-			if (rc != EOK)
-				goto error;
-
-			sif_node_destroy(trans, volume->nvolume);
-
-			rc = sif_trans_end(trans);
-			if (rc != EOK)
-				goto error;
-
-			volume->nvolume = NULL;
-		}
-	}
-
+		}
+	}
+
+	vol_volumes_sync(volume->volumes);
 	free(old_mp);
 	return EOK;
-error:
-	free(mp);
-	volume->mountp = old_mp;
-
-	if (trans != NULL)
-		sif_trans_abort(trans);
-	return rc;
 }
 
@@ -521,5 +497,5 @@
 }
 
-/** Load volumes from SIF repository.
+/** Load volumes from SIF document.
  *
  * @param nvolumes Volumes node
@@ -536,6 +512,4 @@
 	errno_t rc;
 
-	volumes->nvolumes = nvolumes;
-
 	nvolume = sif_node_first_child(nvolumes);
 	while (nvolume != NULL) {
@@ -565,5 +539,4 @@
 		volume->mountp = str_dup(mountp);
 
-		volume->nvolume = nvolume;
 		fibril_mutex_lock(&volumes->lock);
 		vol_volume_add_locked(volumes, volume);
@@ -579,4 +552,50 @@
 }
 
+/** Save volumes to SIF document.
+ *
+ * @param volumes List of volumes
+ * @param rnode Configuration root node
+ * @return EOK on success, ENOMEM if out of memory
+ */
+errno_t vol_volumes_save(vol_volumes_t *volumes, sif_node_t *rnode)
+{
+	sif_node_t *nvolumes;
+	sif_node_t *node;
+	link_t *link;
+	vol_volume_t *volume;
+	errno_t rc;
+
+	/* Create 'volumes' node. */
+	rc = sif_node_append_child(rnode, "volumes", &nvolumes);
+	if (rc != EOK)
+		goto error;
+
+	link = list_first(&volumes->volumes);
+	while (link != NULL) {
+		volume = list_get_instance(link, vol_volume_t, lvolumes);
+
+		if (vol_volume_is_persist(volume)) {
+			/* Create 'volume' node. */
+			rc = sif_node_append_child(rnode, "volume", &node);
+			if (rc != EOK)
+				goto error;
+
+			rc = sif_node_set_attr(node, "label", volume->label);
+			if (rc != EOK)
+				goto error;
+
+			rc = sif_node_set_attr(node, "mountp", volume->mountp);
+			if (rc != EOK)
+				goto error;
+		}
+
+		link = list_next(&volume->lvolumes, &volumes->volumes);
+	}
+
+	return EOK;
+error:
+	return rc;
+}
+
 /** Get volume information.
  *
Index: uspace/srv/volsrv/volume.h
===================================================================
--- uspace/srv/volsrv/volume.h	(revision a3ba37d0f8f1126e1221ca6aa51ae7ddb06dc457)
+++ uspace/srv/volsrv/volume.h	(revision bff8619cdacb5b15f795cd78a330c58648be779b)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2018 Jiri Svoboda
+ * Copyright (c) 2024 Jiri Svoboda
  * All rights reserved.
  *
@@ -42,4 +42,5 @@
 
 extern errno_t vol_volumes_create(const char *, vol_volumes_t **);
+extern errno_t vol_volumes_sync(vol_volumes_t *);
 extern void vol_volumes_destroy(vol_volumes_t *);
 extern errno_t vol_volume_lookup_ref(vol_volumes_t *, const char *,
