Index: uspace/app/bdsh/cmds/modules/ls/ls.c
===================================================================
--- uspace/app/bdsh/cmds/modules/ls/ls.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/bdsh/cmds/modules/ls/ls.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2025 Jiri Svoboda
  * Copyright (c) 2008 Tim Post
  * All rights reserved.
@@ -106,17 +107,9 @@
 		}
 
-		capa_spec_t capa;
-		capa_from_blocks(de->s.size, 1, &capa);
-		capa_simplify(&capa);
-
-		char *rptr;
-		errno_t rc = capa_format(&capa, &rptr);
-		if (rc != EOK) {
-			return rc;
-		}
-
-		char *sep = str_rchr(rptr, ' ');
+		char fsize[CAPA_BLOCKS_BUFSIZE];
+		capa_blocks_format_buf(de->s.size, 1, fsize, sizeof(fsize));
+
+		char *sep = str_rchr(fsize, ' ');
 		if (sep == NULL) {
-			free(rptr);
 			return ENOENT;
 		}
@@ -124,6 +117,5 @@
 		*sep = '\0';
 
-		printf("%-40s\t%*s %2s\n", de->name, width - 3, rptr, sep + 1);
-		free(rptr);
+		printf("%-40s\t%*s %2s\n", de->name, width - 3, fsize, sep + 1);
 	} else if (de->s.is_directory)
 		printf("%-40s\t%*s\n", de->name, width, "<dir>");
Index: uspace/app/df/df.c
===================================================================
--- uspace/app/df/df.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/df/df.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2025 Jiri Svoboda
  * Copyright (c) 2013 Manuele Conti
  * All rights reserved.
@@ -56,7 +57,6 @@
 static bool display_blocks;
 
-static errno_t size_to_human_readable(uint64_t, size_t, char **);
 static void print_header(void);
-static errno_t print_statfs(vfs_statfs_t *, char *, char *);
+static void print_statfs(vfs_statfs_t *, char *, char *);
 static void print_usage(void);
 
@@ -65,5 +65,4 @@
 	int optres, errflg = 0;
 	vfs_statfs_t st;
-	errno_t rc;
 
 	display_blocks = false;
@@ -109,7 +108,5 @@
 	list_foreach(mtab_list, link, mtab_ent_t, mtab_ent) {
 		if (vfs_statfs_path(mtab_ent->mp, &st) == 0) {
-			rc = print_statfs(&st, mtab_ent->fs_name, mtab_ent->mp);
-			if (rc != EOK)
-				return 1;
+			print_statfs(&st, mtab_ent->fs_name, mtab_ent->mp);
 		} else {
 			fprintf(stderr, "Cannot get information for '%s' (%s).\n",
@@ -120,13 +117,4 @@
 	putchar('\n');
 	return 0;
-}
-
-static errno_t size_to_human_readable(uint64_t nblocks, size_t block_size, char **rptr)
-{
-	capa_spec_t capa;
-
-	capa_from_blocks(nblocks, block_size, &capa);
-	capa_simplify(&capa);
-	return capa_format(&capa, rptr);
 }
 
@@ -141,10 +129,9 @@
 }
 
-static errno_t print_statfs(vfs_statfs_t *st, char *name, char *mountpoint)
+static void print_statfs(vfs_statfs_t *st, char *name, char *mountpoint)
 {
 	uint64_t const used_blocks = st->f_blocks - st->f_bfree;
 	unsigned const perc_used = PERCENTAGE(used_blocks, st->f_blocks);
-	char *str;
-	errno_t rc;
+	char str[CAPA_BLOCKS_BUFSIZE];
 
 	printf("%10s", name);
@@ -152,23 +139,17 @@
 	if (!display_blocks) {
 		/* Print size */
-		rc = size_to_human_readable(st->f_blocks, st->f_bsize, &str);
-		if (rc != EOK)
-			goto error;
+		capa_blocks_format_buf(st->f_blocks, st->f_bsize, str,
+		    sizeof(str));
 		printf(" %14s", str);
-		free(str);
 
 		/* Number of used blocks */
-		rc = size_to_human_readable(used_blocks, st->f_bsize, &str);
-		if (rc != EOK)
-			goto error;
+		capa_blocks_format_buf(used_blocks, st->f_bsize, str,
+		    sizeof(str));
 		printf(" %14s", str);
-		free(str);
 
 		/* Number of available blocks */
-		rc = size_to_human_readable(st->f_bfree, st->f_bsize, &str);
-		if (rc != EOK)
-			goto error;
+		capa_blocks_format_buf(st->f_bfree, st->f_bsize, str,
+		    sizeof(str));
 		printf(" %14s", str);
-		free(str);
 
 		/* Percentage of used blocks */
@@ -183,9 +164,4 @@
 		    perc_used, mountpoint);
 	}
-
-	return EOK;
-error:
-	printf("\nError: Out of memory.\n");
-	return ENOMEM;
 }
 
Index: uspace/app/meson.build
===================================================================
--- uspace/app/meson.build	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/meson.build	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,3 +1,4 @@
 #
+# Copyright (c) 2025 Jiri Svoboda
 # Copyright (c) 2019 Jiří Zárevúcky
 # All rights reserved.
@@ -69,4 +70,5 @@
 	'nav',
 	'netecho',
+	'newfile',
 	'nic',
 	'nterm',
Index: uspace/app/nav/dlg/newfiledlg.c
===================================================================
--- uspace/app/nav/dlg/newfiledlg.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/dlg/newfiledlg.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -358,5 +358,5 @@
 
 	(void)window;
-	if (dialog->cb != NULL && dialog->cb->bcancel != NULL) {
+	if (dialog->cb != NULL && dialog->cb->close != NULL) {
 		dialog->cb->close(dialog, dialog->arg);
 	}
@@ -373,5 +373,6 @@
 {
 	new_file_dlg_t *dialog = (new_file_dlg_t *) arg;
-	const char *text;
+	const char *fname;
+	const char *fsize;
 
 	if (event->type == KEY_PRESS &&
@@ -380,6 +381,8 @@
 			/* Confirm */
 			if (dialog->cb != NULL && dialog->cb->bok != NULL) {
-				text = ui_entry_get_text(dialog->ename);
-				dialog->cb->bok(dialog, dialog->arg, text);
+				fname = ui_entry_get_text(dialog->ename);
+				fsize = ui_entry_get_text(dialog->esize);
+				dialog->cb->bok(dialog, dialog->arg, fname,
+				    fsize);
 				return;
 			}
@@ -404,9 +407,11 @@
 {
 	new_file_dlg_t *dialog = (new_file_dlg_t *) arg;
-	const char *text;
+	const char *fname;
+	const char *fsize;
 
 	if (dialog->cb != NULL && dialog->cb->bok != NULL) {
-		text = ui_entry_get_text(dialog->ename);
-		dialog->cb->bok(dialog, dialog->arg, text);
+		fname = ui_entry_get_text(dialog->ename);
+		fsize = ui_entry_get_text(dialog->esize);
+		dialog->cb->bok(dialog, dialog->arg, fname, fsize);
 	}
 }
Index: uspace/app/nav/dlg/progress.c
===================================================================
--- uspace/app/nav/dlg/progress.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/nav/dlg/progress.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,312 @@
+/*
+ * Copyright (c) 2025 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 nav
+ * @{
+ */
+/**
+ * @file Progress dialog
+ */
+
+#include <errno.h>
+#include <fmgt.h>
+#include <mem.h>
+#include <stdlib.h>
+#include <ui/fixed.h>
+#include <ui/label.h>
+#include <ui/pbutton.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include "progress.h"
+
+static void progress_dlg_wnd_close(ui_window_t *, void *);
+static void progress_dlg_wnd_kbd(ui_window_t *, void *, kbd_event_t *);
+
+ui_window_cb_t progress_dlg_wnd_cb = {
+	.close = progress_dlg_wnd_close,
+	.kbd = progress_dlg_wnd_kbd
+};
+
+static void progress_dlg_babort_clicked(ui_pbutton_t *, void *);
+
+ui_pbutton_cb_t progress_dlg_babort_cb = {
+	.clicked = progress_dlg_babort_clicked
+};
+
+/** Initialize progress dialog parameters structure.
+ *
+ * Progress dialog parameters structure must always be initialized using
+ * this function first.
+ *
+ * @param params Progress dialog parameters structure
+ */
+void progress_dlg_params_init(progress_dlg_params_t *params)
+{
+	memset(params, 0, sizeof(progress_dlg_params_t));
+	params->caption = "";
+}
+
+/** Create progress dialog.
+ *
+ * @param ui User interface
+ * @param params Parameters
+ * @param rdialog Place to store pointer to new dialog
+ * @return EOK on success or an error code
+ */
+errno_t progress_dlg_create(ui_t *ui, progress_dlg_params_t *params,
+    progress_dlg_t **rdialog)
+{
+	errno_t rc;
+	progress_dlg_t *dialog;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t wparams;
+	ui_fixed_t *fixed = NULL;
+	ui_label_t *label = NULL;
+	ui_pbutton_t *babort = NULL;
+	gfx_rect_t rect;
+	ui_resource_t *ui_res;
+	char *name = NULL;
+
+	dialog = calloc(1, sizeof(progress_dlg_t));
+	if (dialog == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ui_wnd_params_init(&wparams);
+	wparams.caption = params->caption;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		wparams.rect.p0.x = 0;
+		wparams.rect.p0.y = 0;
+		wparams.rect.p1.x = 50;
+		wparams.rect.p1.y = 11;
+	} else {
+		wparams.rect.p0.x = 0;
+		wparams.rect.p0.y = 0;
+		wparams.rect.p1.x = 400;
+		wparams.rect.p1.y = 135;
+	}
+
+	rc = ui_window_create(ui, &wparams, &window);
+	if (rc != EOK)
+		goto error;
+
+	ui_window_set_cb(window, &progress_dlg_wnd_cb, dialog);
+
+	ui_res = ui_window_get_res(window);
+
+	rc = ui_fixed_create(&fixed);
+	if (rc != EOK)
+		goto error;
+
+	rc = ui_label_create(ui_res, "XXX of XXX (XXX%)", &label);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 2;
+		rect.p1.x = 47;
+		rect.p1.y = 3;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 35;
+		rect.p1.x = 390;
+		rect.p1.y = 50;
+	}
+
+	ui_label_set_rect(label, &rect);
+	ui_label_set_halign(label, gfx_halign_center);
+
+	rc = ui_fixed_add(fixed, ui_label_ctl(label));
+	if (rc != EOK)
+		goto error;
+
+	dialog->lcurf_prog = label;
+	label = NULL;
+
+	rc = ui_pbutton_create(ui_res, "Abort", &babort);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(babort, &progress_dlg_babort_cb, dialog);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 20;
+		rect.p0.y = 8;
+		rect.p1.x = 30;
+		rect.p1.y = 9;
+	} else {
+		rect.p0.x = 205;
+		rect.p0.y = 90;
+		rect.p1.x = 295;
+		rect.p1.y = 118;
+	}
+
+	ui_pbutton_set_rect(babort, &rect);
+
+	ui_pbutton_set_default(babort, true);
+
+	rc = ui_fixed_add(fixed, ui_pbutton_ctl(babort));
+	if (rc != EOK)
+		goto error;
+
+	dialog->babort = babort;
+	babort = NULL;
+
+	ui_window_add(window, ui_fixed_ctl(fixed));
+	fixed = NULL;
+
+	rc = ui_window_paint(window);
+	if (rc != EOK)
+		goto error;
+
+	dialog->window = window;
+	*rdialog = dialog;
+	return EOK;
+error:
+	if (name != NULL)
+		free(name);
+	if (babort != NULL)
+		ui_pbutton_destroy(babort);
+	if (label != NULL)
+		ui_label_destroy(label);
+	if (fixed != NULL)
+		ui_fixed_destroy(fixed);
+	if (window != NULL)
+		ui_window_destroy(window);
+	if (dialog != NULL)
+		free(dialog);
+	return rc;
+}
+
+/** Destroy progress dialog.
+ *
+ * @param dialog Progress dialog or @c NULL
+ */
+void progress_dlg_destroy(progress_dlg_t *dialog)
+{
+	if (dialog == NULL)
+		return;
+
+	ui_window_destroy(dialog->window);
+	free(dialog);
+}
+
+/** Set mesage dialog callback.
+ *
+ * @param dialog Progress dialog
+ * @param cb Progress dialog callbacks
+ * @param arg Callback argument
+ */
+void progress_dlg_set_cb(progress_dlg_t *dialog, progress_dlg_cb_t *cb,
+    void *arg)
+{
+	dialog->cb = cb;
+	dialog->arg = arg;
+}
+
+/** Set current file progress text.
+ *
+ * @param dialog Progress dialog
+ * @param text New text for current file progress
+ *
+ * @return EOK on success or an error code
+ */
+errno_t progress_dlg_set_curf_prog(progress_dlg_t *dialog, const char *text)
+{
+	errno_t rc;
+
+	rc = ui_label_set_text(dialog->lcurf_prog, text);
+	if (rc != EOK)
+		return rc;
+
+	return ui_label_paint(dialog->lcurf_prog);
+}
+
+/** Progress dialog window close handler.
+ *
+ * @param window Window
+ * @param arg Argument (progress_dlg_t *)
+ */
+static void progress_dlg_wnd_close(ui_window_t *window, void *arg)
+{
+	progress_dlg_t *dialog = (progress_dlg_t *) arg;
+
+	(void)window;
+	if (dialog->cb != NULL && dialog->cb->close != NULL) {
+		dialog->cb->close(dialog, dialog->arg);
+	}
+}
+
+/** Progress dialog window keyboard event handler.
+ *
+ * @param window Window
+ * @param arg Argument (progress_dlg_t *)
+ * @param event Keyboard event
+ */
+static void progress_dlg_wnd_kbd(ui_window_t *window, void *arg,
+    kbd_event_t *event)
+{
+	progress_dlg_t *dialog = (progress_dlg_t *) arg;
+
+	if (event->type == KEY_PRESS &&
+	    (event->mods & (KM_CTRL | KM_SHIFT | KM_ALT)) == 0) {
+		if (event->key == KC_ENTER || event->key == KC_ESCAPE) {
+			/* Abort */
+			if (dialog->cb != NULL && dialog->cb->babort != NULL) {
+				dialog->cb->babort(dialog, dialog->arg);
+				return;
+			}
+		}
+	}
+
+	ui_window_def_kbd(window, event);
+}
+
+/** Progress dialog Abort button click handler.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (progress_dlg_t *)
+ */
+static void progress_dlg_babort_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	progress_dlg_t *dialog = (progress_dlg_t *) arg;
+
+	if (dialog->cb != NULL && dialog->cb->babort != NULL) {
+		dialog->cb->babort(dialog, dialog->arg);
+	}
+}
+
+/** @}
+ */
Index: uspace/app/nav/dlg/progress.h
===================================================================
--- uspace/app/nav/dlg/progress.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/nav/dlg/progress.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2025 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 nav
+ * @{
+ */
+/**
+ * @file Progress dialog
+ */
+
+#ifndef DLG_PROGRESS_H
+#define DLG_PROGRESS_H
+
+#include <errno.h>
+#include <types/ui/ui.h>
+#include "../types/dlg/progress.h"
+
+extern void progress_dlg_params_init(progress_dlg_params_t *);
+extern errno_t progress_dlg_create(ui_t *, progress_dlg_params_t *,
+    progress_dlg_t **);
+extern void progress_dlg_set_cb(progress_dlg_t *, progress_dlg_cb_t *,
+    void *);
+extern errno_t progress_dlg_set_curf_prog(progress_dlg_t *, const char *);
+extern void progress_dlg_destroy(progress_dlg_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/meson.build
===================================================================
--- uspace/app/nav/meson.build	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/meson.build	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -30,4 +30,5 @@
 src = files(
 	'dlg/newfiledlg.c',
+	'dlg/progress.c',
 	'main.c',
 	'menu.c',
@@ -39,4 +40,5 @@
 test_src = files(
 	'dlg/newfiledlg.c',
+	'dlg/progress.c',
 	'menu.c',
 	'nav.c',
Index: uspace/app/nav/nav.c
===================================================================
--- uspace/app/nav/nav.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/nav.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -35,4 +35,5 @@
  */
 
+#include <fibril.h>
 #include <gfx/coord.h>
 #include <stdio.h>
@@ -535,4 +536,64 @@
 }
 
+/** Wrapper fibril function for worker function.
+ *
+ * This is the main fibril function for the worker fibril. It executes
+ * the worker function, then clears worker FID to indicate the worker
+ * is finished.
+ *
+ * @param arg Argument (navigator_worker_job_t *)
+ * @return EOK
+ */
+static errno_t navigator_worker_func(void *arg)
+{
+	navigator_worker_job_t *job = (navigator_worker_job_t *)arg;
+
+	job->wfunc(job->arg);
+	job->navigator->worker_fid = 0;
+	free(job);
+	return EOK;
+}
+
+/** Start long-time work in a worker fibril.
+ *
+ * Actions which can take time (file operations) cannot block the main UI
+ * fibril. This function will start an action in the worker fibril, i.e.,
+ * in the background. At the same time the caller should create a modal
+ * progress dialog that will be shown until the work is completed.
+ *
+ * (Only a single worker can execute at any given time).
+ *
+ * @param nav Navigator
+ * @param wfunc Worker main function
+ * @param arg Argument to worker function
+ *
+ * @return EOK on success or an error code
+ */
+errno_t navigator_worker_start(navigator_t *nav, void (*wfunc)(void *),
+    void *arg)
+{
+	navigator_worker_job_t *job;
+
+	if (nav->worker_fid != 0)
+		return EBUSY;
+
+	job = calloc(1, sizeof(navigator_worker_job_t));
+	if (job == NULL)
+		return ENOMEM;
+
+	job->navigator = nav;
+	job->wfunc = wfunc;
+	job->arg = arg;
+
+	nav->worker_fid = fibril_create(navigator_worker_func, (void *)job);
+	if (nav->worker_fid == 0) {
+		free(job);
+		return ENOMEM;
+	}
+
+	fibril_add_ready(nav->worker_fid);
+	return EOK;
+}
+
 /** @}
  */
Index: uspace/app/nav/nav.h
===================================================================
--- uspace/app/nav/nav.h	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/nav.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -47,4 +47,6 @@
 extern void navigator_switch_panel(navigator_t *);
 extern void navigator_refresh_panels(navigator_t *);
+extern errno_t navigator_worker_start(navigator_t *, void (*)(void *),
+    void *);
 
 #endif
Index: uspace/app/nav/newfile.c
===================================================================
--- uspace/app/nav/newfile.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/newfile.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -34,4 +34,6 @@
  */
 
+#include <capa.h>
+#include <fmgt.h>
 #include <stdlib.h>
 #include <str_error.h>
@@ -42,10 +44,13 @@
 #include <ui/ui.h>
 #include <ui/window.h>
+#include <str.h>
 #include "dlg/newfiledlg.h"
+#include "dlg/progress.h"
 #include "menu.h"
 #include "newfile.h"
 #include "nav.h"
-
-static void new_file_bok(new_file_dlg_t *, void *, const char *);
+#include "types/newfile.h"
+
+static void new_file_bok(new_file_dlg_t *, void *, const char *, const char *);
 static void new_file_bcancel(new_file_dlg_t *, void *);
 static void new_file_close(new_file_dlg_t *, void *);
@@ -57,4 +62,10 @@
 };
 
+static void new_file_progress(void *, fmgt_progress_t *);
+
+static fmgt_cb_t new_file_fmgt_cb = {
+	.progress = new_file_progress
+};
+
 /** Open New File dialog.
  *
@@ -69,4 +80,55 @@
 }
 
+/** New file worker function.
+ *
+ * @param arg Argument (navigator_new_file_job_t)
+ */
+static void new_file_wfunc(void *arg)
+{
+	fmgt_t *fmgt = NULL;
+	navigator_new_file_job_t *job = (navigator_new_file_job_t *)arg;
+	char *msg = NULL;
+	navigator_t *nav = job->navigator;
+	ui_msg_dialog_t *dialog = NULL;
+	ui_msg_dialog_params_t params;
+	errno_t rc;
+	int rv;
+
+	rc = fmgt_create(&fmgt);
+	if (rc != EOK) {
+		/* out of memory */
+		return;
+	}
+
+	fmgt_set_cb(fmgt, &new_file_fmgt_cb, (void *)nav);
+	fmgt_set_init_update(fmgt, true);
+
+	rc = fmgt_new_file(fmgt, job->fname, job->nbytes);
+	if (rc != EOK) {
+		rv = asprintf(&msg, "Error creating file (%s).",
+		    str_error(rc));
+		if (rv < 0)
+			return;
+		goto error;
+	}
+
+	fmgt_destroy(fmgt);
+	ui_lock(nav->ui);
+	progress_dlg_destroy(nav->progress_dlg);
+	navigator_refresh_panels(nav);
+	ui_unlock(nav->ui);
+	free(job);
+	return;
+error:
+	ui_lock(nav->ui);
+	progress_dlg_destroy(nav->progress_dlg);
+	ui_msg_dialog_params_init(&params);
+	params.caption = "Error";
+	params.text = msg;
+	(void) ui_msg_dialog_create(nav->ui, &params, &dialog);
+	ui_unlock(nav->ui);
+	free(msg);
+}
+
 /** New file dialog confirmed.
  *
@@ -74,32 +136,70 @@
  * @param arg Argument (navigator_t *)
  * @param fname New file name
- */
-static void new_file_bok(new_file_dlg_t *dlg, void *arg, const char *fname)
+ * @param fsize New file size
+ */
+static void new_file_bok(new_file_dlg_t *dlg, void *arg, const char *fname,
+    const char *fsize)
 {
 	navigator_t *nav = (navigator_t *)arg;
 	ui_msg_dialog_t *dialog = NULL;
+	navigator_new_file_job_t *job;
 	ui_msg_dialog_params_t params;
+	progress_dlg_params_t pd_params;
+	capa_spec_t fcap;
 	char *msg = NULL;
+	errno_t rc;
+	uint64_t nbytes;
 	int rv;
-	FILE *f;
+
+	rc = capa_parse(fsize, &fcap);
+	if (rc != EOK) {
+		/* invalid file size */
+		return;
+	}
 
 	new_file_dlg_destroy(dlg);
-	f = fopen(fname, "wx");
-	if (f == NULL) {
-		rv = asprintf(&msg, "Error creating file (%s).",
-		    str_error(errno));
+
+	rc = capa_to_blocks(&fcap, cv_nom, 1, &nbytes);
+	if (rc != EOK) {
+		rv = asprintf(&msg, "File size too large (%s).", fsize);
 		if (rv < 0)
 			return;
-
-		ui_msg_dialog_params_init(&params);
-		params.caption = "Error";
-		params.text = msg;
-		(void) ui_msg_dialog_create(nav->ui, &params, &dialog);
-		free(msg);
+		goto error;
+	}
+
+	job = calloc(1, sizeof(navigator_new_file_job_t));
+	if (job == NULL)
 		return;
-	}
-
-	fclose(f);
-	navigator_refresh_panels(nav);
+
+	job->navigator = nav;
+	job->fname = fname;
+	job->nbytes = nbytes;
+
+	progress_dlg_params_init(&pd_params);
+	pd_params.caption = "Creating new file";
+
+	rc = progress_dlg_create(nav->ui, &pd_params, &nav->progress_dlg);
+	if (rc != EOK) {
+		msg = str_dup("Out of memory.");
+		if (msg == NULL)
+			return;
+		goto error;
+	}
+
+	rc = navigator_worker_start(nav, new_file_wfunc, (void *)job);
+	if (rc != EOK) {
+		msg = str_dup("Out of memory.");
+		if (msg == NULL)
+			return;
+		goto error;
+	}
+
+	return;
+error:
+	ui_msg_dialog_params_init(&params);
+	params.caption = "Error";
+	params.text = msg;
+	(void) ui_msg_dialog_create(nav->ui, &params, &dialog);
+	free(msg);
 }
 
@@ -126,4 +226,20 @@
 }
 
+/** New file progress update.
+ *
+ * @param arg Argument (navigator_t *)
+ * @param progress Progress update
+ */
+static void new_file_progress(void *arg, fmgt_progress_t *progress)
+{
+	navigator_t *nav = (navigator_t *)arg;
+	char buf[128];
+
+	snprintf(buf, sizeof(buf), "Written %s of %s (%s done).",
+	    progress->curf_procb, progress->curf_totalb,
+	    progress->curf_percent);
+	progress_dlg_set_curf_prog(nav->progress_dlg, buf);
+}
+
 /** @}
  */
Index: uspace/app/nav/types/dlg/newfiledlg.h
===================================================================
--- uspace/app/nav/types/dlg/newfiledlg.h	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/types/dlg/newfiledlg.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -60,8 +60,8 @@
 } new_file_dlg_t;
 
-/** Prompt dialog callback */
+/** New File dialog callbacks */
 typedef struct new_file_dlg_cb {
 	/** OK button was pressed */
-	void (*bok)(new_file_dlg_t *, void *, const char *);
+	void (*bok)(new_file_dlg_t *, void *, const char *, const char *);
 	/** Cancel button was pressed */
 	void (*bcancel)(new_file_dlg_t *, void *);
Index: uspace/app/nav/types/dlg/progress.h
===================================================================
--- uspace/app/nav/types/dlg/progress.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/nav/types/dlg/progress.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,75 @@
+/*
+ * Copyright (c) 2025 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 nav
+ * @{
+ */
+/**
+ * @file Progress dialog
+ */
+
+#ifndef TYPES_DLG_PROGRESS_H
+#define TYPES_DLG_PROGRESS_H
+
+#include <errno.h>
+#include <ui/label.h>
+#include <ui/pbutton.h>
+#include <ui/window.h>
+
+/** Progress dialog */
+typedef struct progress_dlg {
+	/** Dialog window */
+	ui_window_t *window;
+	/** Label with current file progress */
+	ui_label_t *lcurf_prog;
+	/** Abort button */
+	ui_pbutton_t *babort;
+	/** New file dialog callbacks */
+	struct progress_dlg_cb *cb;
+	/** Callback argument */
+	void *arg;
+} progress_dlg_t;
+
+/** Progress dialog callbacks */
+typedef struct progress_dlg_cb {
+	/** Abort button was pressed */
+	void (*babort)(progress_dlg_t *, void *);
+	/** Window closure requested (e.g. via close button) */
+	void (*close)(progress_dlg_t *, void *);
+} progress_dlg_cb_t;
+
+/** Progress dialog parameters */
+typedef struct {
+	/** Window caption */
+	const char *caption;
+} progress_dlg_params_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/types/nav.h
===================================================================
--- uspace/app/nav/types/nav.h	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/app/nav/types/nav.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2021 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -37,4 +37,5 @@
 #define TYPES_NAV_H
 
+#include <fibril.h>
 #include <ui/fixed.h>
 #include <ui/ui.h>
@@ -57,5 +58,19 @@
 	/** Panels */
 	struct panel *panel[navigator_panels];
+	/** Progress dialog */
+	struct progress_dlg *progress_dlg;
+	/** Worker fibril ID */
+	fid_t worker_fid;
 } navigator_t;
+
+/** Navigator worker job */
+typedef struct {
+	/** Navigator */
+	navigator_t *navigator;
+	/** Worker function */
+	void (*wfunc)(void *);
+	/** Worker argument */
+	void *arg;
+} navigator_worker_job_t;
 
 #endif
Index: uspace/app/nav/types/newfile.h
===================================================================
--- uspace/app/nav/types/newfile.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/nav/types/newfile.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2025 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 nav
+ * @{
+ */
+/**
+ * @file Navigator New File types
+ */
+
+#ifndef TYPES_NEWFILE_H
+#define TYPES_NEWFILE_H
+
+#include <stdint.h>
+
+/** Navigator New File job */
+typedef struct {
+	/** Navigator */
+	struct navigator *navigator;
+	/** New file name */
+	const char *fname;
+	/** New file size */
+	uint64_t nbytes;
+} navigator_new_file_job_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/newfile/doc/doxygroups.h
===================================================================
--- uspace/app/newfile/doc/doxygroups.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/newfile/doc/doxygroups.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,4 @@
+/** @addtogroup newfile newfile
+ * @brief Create new file
+ * @ingroup apps
+ */
Index: uspace/app/newfile/meson.build
===================================================================
--- uspace/app/newfile/meson.build	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/newfile/meson.build	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,30 @@
+#
+# Copyright (c) 2025 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 = [ 'fmgt' ]
+src = files('newfile.c')
Index: uspace/app/newfile/newfile.c
===================================================================
--- uspace/app/newfile/newfile.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/app/newfile/newfile.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,189 @@
+/*
+ * Copyright (c) 2025 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 newfile
+ * @{
+ */
+/** @file Create new file.
+ */
+
+#include <capa.h>
+#include <errno.h>
+#include <fmgt.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <str_error.h>
+
+#define NAME  "newfile"
+
+static void newfile_progress(void *, fmgt_progress_t *);
+
+static bool prog_upd = false;
+static bool quiet = false;
+
+static fmgt_cb_t newfile_fmgt_cb = {
+	.progress = newfile_progress
+};
+
+static void print_syntax(void)
+{
+	printf("Create new file.\n");
+	printf("Syntax: %s [<options] [<file-name>]\n", NAME);
+	printf("\t-h           help\n");
+	printf("\t-n           non-interactive\n");
+	printf("\t-p           create sparse file\n");
+	printf("\t-q           quiet\n");
+	printf("\t-size=<cap>  file size (<number>[<kB>|<MB>|...])\n");
+}
+
+/** Called by fmgt to give the user progress update.
+ *
+ * @param arg Argument (not used)
+ * @param progress Progress report
+ */
+static void newfile_progress(void *arg, fmgt_progress_t *progress)
+{
+	(void)arg;
+
+	if (!quiet) {
+		printf("\rWritten %s of %s (%s done).", progress->curf_procb,
+		    progress->curf_totalb, progress->curf_percent);
+		fflush(stdout);
+		prog_upd = true;
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	fmgt_t *fmgt = NULL;
+	errno_t rc;
+	int i;
+	bool nonint = false;
+	bool sparse = false;
+	char *fsize = NULL;
+	char *fname = NULL;
+	capa_spec_t fcap;
+	uint64_t nbytes = 0;
+
+	i = 1;
+	while (i < argc && argv[i][0] == '-') {
+		if (str_cmp(argv[i], "-h") == 0) {
+			print_syntax();
+			return 0;
+		} else if (str_cmp(argv[i], "-n") == 0) {
+			++i;
+			nonint = true;
+		} else if (str_cmp(argv[i], "-p") == 0) {
+			++i;
+			sparse = true;
+		} else if (str_cmp(argv[i], "-q") == 0) {
+			++i;
+			quiet = true;
+		} else if (str_lcmp(argv[i], "-size=",
+		    str_length("-size=")) == 0) {
+			fsize = argv[i] + str_length("-size=");
+			++i;
+		} else {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			goto error;
+		}
+	}
+
+	if (i < argc) {
+		fname = str_dup(argv[i++]);
+		if (fname == NULL) {
+			printf("Out of memory.\n");
+			goto error;
+		}
+	}
+
+	if (i < argc) {
+		printf("Unexpected argument.\n");
+		print_syntax();
+		goto error;
+	}
+
+	if (fname == NULL) {
+		rc = fmgt_new_file_suggest(&fname);
+		if (rc != EOK) {
+			printf("Out of memory.\n");
+			goto error;
+		}
+	}
+
+	(void)nonint;
+	(void)quiet;
+	(void)sparse;
+
+	if (fsize != NULL) {
+		rc = capa_parse(fsize, &fcap);
+		if (rc != EOK) {
+			printf("Invalid file size '%s'.\n", fsize);
+			goto error;
+		}
+
+		rc = capa_to_blocks(&fcap, cv_nom, 1, &nbytes);
+		if (rc != EOK) {
+			printf("File size too large '%s'.\n", fsize);
+			goto error;
+		}
+	}
+
+	rc = fmgt_create(&fmgt);
+	if (rc != EOK) {
+		printf("Out of memory.\n");
+		goto error;
+	}
+
+	fmgt_set_cb(fmgt, &newfile_fmgt_cb, NULL);
+
+	rc = fmgt_new_file(fmgt, fname, nbytes);
+	if (rc != EOK) {
+		printf("Error creating file: %s.\n", str_error(rc));
+		goto error;
+	}
+
+	if (prog_upd)
+		printf("\n");
+
+	free(fname);
+	fmgt_destroy(fmgt);
+	return 0;
+error:
+	if (fname != NULL)
+		free(fname);
+	if (fmgt != NULL)
+		fmgt_destroy(fmgt);
+	return 1;
+}
+
+/** @}
+ */
Index: uspace/lib/c/generic/capa.c
===================================================================
--- uspace/lib/c/generic/capa.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/c/generic/capa.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -233,4 +233,59 @@
 }
 
+errno_t capa_format_buf(capa_spec_t *capa, char *buf, size_t bufsize)
+{
+	errno_t rc;
+	const char *sunit;
+	uint64_t ipart;
+	uint64_t fpart;
+	uint64_t div;
+
+	sunit = NULL;
+
+	assert(capa->cunit < CU_LIMIT);
+
+	rc = ipow10_u64(capa->dp, &div);
+	if (rc != EOK)
+		return rc;
+
+	ipart = capa->m / div;
+	fpart = capa->m % div;
+
+	sunit = cu_str[capa->cunit];
+	if (capa->dp > 0) {
+		snprintf(buf, bufsize, "%" PRIu64 ".%0*" PRIu64 " %s", ipart,
+		    (int)capa->dp, fpart, sunit);
+	} else {
+		snprintf(buf, bufsize, "%" PRIu64 " %s", ipart, sunit);
+	}
+
+	return EOK;
+}
+
+errno_t capa_blocks_format(uint64_t nblocks, size_t block_size,
+    char **rptr)
+{
+	capa_spec_t capa;
+
+	capa_from_blocks(nblocks, block_size, &capa);
+	capa_simplify(&capa);
+	return capa_format(&capa, rptr);
+}
+
+void capa_blocks_format_buf(uint64_t nblocks, size_t block_size,
+    char *buf, size_t bsize)
+{
+	capa_spec_t capa;
+	errno_t rc;
+
+	capa_from_blocks(nblocks, block_size, &capa);
+	capa_simplify(&capa);
+
+	/* Should not get range error because of nblocks * block_size limits */
+	rc = capa_format_buf(&capa, buf, bsize);
+	assert(rc == EOK);
+	(void)rc;
+}
+
 static errno_t capa_digit_val(char c, int *val)
 {
Index: uspace/lib/c/include/capa.h
===================================================================
--- uspace/lib/c/include/capa.h	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/c/include/capa.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,4 +1,4 @@
 /*
- * Copyright (c) 2015 Jiri Svoboda
+ * Copyright (c) 2025 Jiri Svoboda
  * All rights reserved.
  *
@@ -87,5 +87,11 @@
 } capa_spec_t;
 
+/** Size of buffer large enough for capa_blocks_format_buf */
+#define CAPA_BLOCKS_BUFSIZE 16
+
 extern errno_t capa_format(capa_spec_t *, char **);
+extern errno_t capa_format_buf(capa_spec_t *, char *, size_t);
+extern errno_t capa_blocks_format(uint64_t, size_t, char **);
+extern void capa_blocks_format_buf(uint64_t, size_t, char *, size_t);
 extern errno_t capa_parse(const char *, capa_spec_t *);
 extern void capa_simplify(capa_spec_t *);
Index: uspace/lib/c/test/capa.c
===================================================================
--- uspace/lib/c/test/capa.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/c/test/capa.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -1,3 +1,4 @@
 /*
+ * Copyright (c) 2025 Jiri Svoboda
  * Copyright (c) 2019 Matthieu Riolo
  * All rights reserved.
@@ -285,3 +286,22 @@
 }
 
+PCUT_TEST(capa_blocks_format)
+{
+	errno_t rc;
+	char *str;
+
+	rc = capa_blocks_format(42, 1, &str);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_STR_EQUALS("42 B", str);
+	free(str);
+}
+
+PCUT_TEST(capa_blocks_format_buf)
+{
+	char str[CAPA_BLOCKS_BUFSIZE];
+
+	capa_blocks_format_buf(42, 1, str, sizeof(str));
+	PCUT_ASSERT_STR_EQUALS("42 B", str);
+}
+
 PCUT_EXPORT(capa);
Index: uspace/lib/fmgt/include/fmgt.h
===================================================================
--- uspace/lib/fmgt/include/fmgt.h	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/fmgt/include/fmgt.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -39,5 +39,7 @@
 
 #include <errno.h>
+#include <stdbool.h>
 #include <stddef.h>
+#include <stdint.h>
 #include "types/fmgt.h"
 
@@ -45,5 +47,7 @@
 extern void fmgt_set_cb(fmgt_t *, fmgt_cb_t *, void *);
 extern void fmgt_destroy(fmgt_t *);
+extern void fmgt_set_init_update(fmgt_t *, bool);
 extern errno_t fmgt_new_file_suggest(char **);
+extern errno_t fmgt_new_file(fmgt_t *, const char *, uint64_t);
 
 #endif
Index: uspace/lib/fmgt/include/types/fmgt.h
===================================================================
--- uspace/lib/fmgt/include/types/fmgt.h	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/fmgt/include/types/fmgt.h	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -38,13 +38,39 @@
 #define TYPES_FMGT_H
 
+#include <capa.h>
+#include <fibril_synch.h>
+
+/** File management progress update */
+typedef struct {
+	/** Current file processed bytes */
+	char curf_procb[CAPA_BLOCKS_BUFSIZE];
+	/** Total bytes to process for current file */
+	char curf_totalb[CAPA_BLOCKS_BUFSIZE];
+	/** Percent of current file processed */
+	char curf_percent[5];
+} fmgt_progress_t;
+
 /** File management callbacks */
 typedef struct {
+	void (*progress)(void *, fmgt_progress_t *);
 } fmgt_cb_t;
 
 typedef struct {
+	/** Lock */
+	fibril_mutex_t lock;
+	/** Progress update timer */
+	fibril_timer_t *timer;
 	/** Callback functions */
 	fmgt_cb_t *cb;
 	/** Argument to callback functions */
 	void *cb_arg;
+	/** Bytes processed from current file */
+	uint64_t curf_procb;
+	/** Total size of current file */
+	uint64_t curf_totalb;
+	/** Progress was displayed for current file */
+	bool curf_progr;
+	/** Post an immediate initial progress update */
+	bool do_init_update;
 } fmgt_t;
 
Index: uspace/lib/fmgt/meson.build
===================================================================
--- uspace/lib/fmgt/meson.build	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/fmgt/meson.build	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -28,4 +28,9 @@
 
 src = files(
-	'src/fmgt.c'
+	'src/fmgt.c',
 )
+
+test_src = files(
+	'test/fmgt.c',
+	'test/main.c',
+)
Index: uspace/lib/fmgt/src/fmgt.c
===================================================================
--- uspace/lib/fmgt/src/fmgt.c	(revision 1a96db9b57f6dc5fbbe2f9708c1251717b9868bd)
+++ uspace/lib/fmgt/src/fmgt.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -46,4 +46,5 @@
 
 #define NEWNAME_LEN 64
+#define BUFFER_SIZE 16384
 
 /** Create file management library instance.
@@ -59,4 +60,11 @@
 	if (fmgt == NULL)
 		return ENOMEM;
+
+	fibril_mutex_initialize(&fmgt->lock);
+	fmgt->timer = fibril_timer_create(&fmgt->lock);
+	if (fmgt->timer == NULL) {
+		free(fmgt);
+		return ENOMEM;
+	}
 
 	*rfmgt = fmgt;
@@ -77,4 +85,14 @@
 }
 
+/** Configure whether to give immediate initial progress update.
+ *
+ * @param fmgt File management object
+ * @param enabled @c true to post and immediate initial progress update
+ */
+void fmgt_set_init_update(fmgt_t *fmgt, bool enabled)
+{
+	fmgt->do_init_update = enabled;
+}
+
 /** Destroy file management library instance.
  *
@@ -83,4 +101,6 @@
 void fmgt_destroy(fmgt_t *fmgt)
 {
+	(void)fibril_timer_clear(fmgt->timer);
+	fibril_timer_destroy(fmgt->timer);
 	free(fmgt);
 }
@@ -116,4 +136,145 @@
 }
 
+/** Get progress update report.
+ *
+ * @param fmgt File management object
+ * @param progress Place to store progress update
+ */
+static void fmgt_get_progress(fmgt_t *fmgt, fmgt_progress_t *progress)
+{
+	unsigned percent;
+
+	if (fmgt->curf_totalb > 0)
+		percent = fmgt->curf_procb * 100 / fmgt->curf_totalb;
+	else
+		percent = 100;
+
+	capa_blocks_format_buf(fmgt->curf_procb, 1, progress->curf_procb,
+	    sizeof(progress->curf_procb));
+	capa_blocks_format_buf(fmgt->curf_totalb, 1, progress->curf_totalb,
+	    sizeof(progress->curf_totalb));
+	snprintf(progress->curf_percent, sizeof(progress->curf_percent), "%u%%",
+	    percent);
+}
+
+/** Give the caller progress update.
+ *
+ * @param fmgt File management object
+ */
+static void fmgt_progress_update(fmgt_t *fmgt)
+{
+	fmgt_progress_t progress;
+
+	if (fmgt->cb != NULL && fmgt->cb->progress != NULL) {
+		fmgt_get_progress(fmgt, &progress);
+		fmgt->curf_progr = true;
+		fmgt->cb->progress(fmgt->cb_arg, &progress);
+	}
+}
+
+/** Provide initial progress update (if required).
+ *
+ * The caller configures the file management object regarding whether
+ * initial updates are required.
+ *
+ * @param fmgt File management object
+ */
+static void fmgt_initial_progress_update(fmgt_t *fmgt)
+{
+	if (fmgt->do_init_update)
+		fmgt_progress_update(fmgt);
+}
+
+/** Provide final progress update (if required).
+ *
+ * Final update is provided only if a previous progress update was given.
+ *
+ * @param fmgt File management object
+ */
+static void fmgt_final_progress_update(fmgt_t *fmgt)
+{
+	if (fmgt->curf_progr)
+		fmgt_progress_update(fmgt);
+}
+
+/** Progress timer function.
+ *
+ * Periodically called to provide progress updates.
+ *
+ * @param arg Argument (fmgt_t *)
+ */
+static void fmgt_timer_fun(void *arg)
+{
+	fmgt_t *fmgt = (fmgt_t *)arg;
+
+	fmgt_progress_update(fmgt);
+	fibril_timer_set(fmgt->timer, 500000, fmgt_timer_fun, (void *)fmgt);
+}
+
+/** Start progress update timer.
+ *
+ * @param fmgt File management object
+ */
+static void fmgt_timer_start(fmgt_t *fmgt)
+{
+	fibril_timer_set(fmgt->timer, 500000, fmgt_timer_fun, (void *)fmgt);
+}
+
+/** Create new file.
+ *
+ * @param fmgt File management object
+ * @param fname File name
+ * @param fsize Size of new file (number of zero bytes to fill in)
+ * @return EOK on success or an error code
+ */
+errno_t fmgt_new_file(fmgt_t *fmgt, const char *fname, uint64_t fsize)
+{
+	int fd;
+	size_t nw;
+	aoff64_t pos = 0;
+	uint64_t now;
+	char *buffer;
+	errno_t rc;
+
+	buffer = calloc(BUFFER_SIZE, 1);
+	if (buffer == NULL)
+		return ENOMEM;
+
+	rc = vfs_lookup_open(fname, WALK_REGULAR | WALK_MUST_CREATE,
+	    MODE_WRITE, &fd);
+	if (rc != EOK) {
+		free(buffer);
+		return rc;
+	}
+
+	fmgt->curf_procb = 0;
+	fmgt->curf_totalb = fsize;
+	fmgt->curf_progr = false;
+	fmgt_timer_start(fmgt);
+
+	fmgt_initial_progress_update(fmgt);
+
+	while (fmgt->curf_procb < fsize) {
+		now = fsize - fmgt->curf_procb;
+		if (now > BUFFER_SIZE)
+			now = BUFFER_SIZE;
+
+		rc = vfs_write(fd, &pos, buffer, now, &nw);
+		if (rc != EOK) {
+			free(buffer);
+			vfs_put(fd);
+			fmgt_final_progress_update(fmgt);
+			return rc;
+		}
+
+		fmgt->curf_procb += nw;
+	}
+
+	free(buffer);
+	vfs_put(fd);
+	fmgt_final_progress_update(fmgt);
+	return EOK;
+}
+
 /** @}
  */
Index: uspace/lib/fmgt/test/fmgt.c
===================================================================
--- uspace/lib/fmgt/test/fmgt.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/lib/fmgt/test/fmgt.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2025 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.
+ */
+
+#include <fmgt.h>
+#include <pcut/pcut.h>
+#include <stdio.h>
+#include <str.h>
+#include <vfs/vfs.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(fmgt);
+
+#define TEMP_DIR "/tmp"
+
+static void test_fmgt_progress(void *, fmgt_progress_t *);
+
+static fmgt_cb_t test_fmgt_cb = {
+	.progress = test_fmgt_progress
+};
+
+typedef struct {
+	unsigned nupdates;
+} test_resp_t;
+
+/** Create and destroy file management object succeeds. */
+PCUT_TEST(create_destroy)
+{
+	fmgt_t *fmgt = NULL;
+	errno_t rc;
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(fmgt);
+
+	fmgt_destroy(fmgt);
+}
+
+/** Suggesting new file name succeeds and returns unique name. */
+PCUT_TEST(new_file_suggest)
+{
+	FILE *f = NULL;
+	char *fname1 = NULL;
+	char *fname2 = NULL;
+	errno_t rc;
+	int rv;
+
+	rc = vfs_cwd_set(TEMP_DIR);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Suggest unique file name. */
+	rc = fmgt_new_file_suggest(&fname1);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* See if we can actually create the file. */
+	f = fopen(fname1, "wx");
+	PCUT_ASSERT_NOT_NULL(f);
+	(void) fclose(f);
+
+	/* Now suggest another unique file name. */
+	rc = fmgt_new_file_suggest(&fname2);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* They should be different. */
+	PCUT_ASSERT_FALSE(str_cmp(fname1, fname2) == 0);
+
+	/* Remove the file. */
+	rv = remove(fname1);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname1);
+	free(fname2);
+}
+
+/** New empty file can be created. */
+PCUT_TEST(new_file_empty)
+{
+	fmgt_t *fmgt = NULL;
+	char *fname = NULL;
+	errno_t rc;
+	int rv;
+
+	rc = vfs_cwd_set(TEMP_DIR);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Suggest unique file name. */
+	rc = fmgt_new_file_suggest(&fname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_new_file(fmgt, fname, 0);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Remove the file [this also verifies the file exists]. */
+	rv = remove(fname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname);
+	fmgt_destroy(fmgt);
+}
+
+/** New zero-filled file can be created. */
+PCUT_TEST(new_file_zerofill)
+{
+	FILE *f = NULL;
+	fmgt_t *fmgt = NULL;
+	char *fname = NULL;
+	char buf[128];
+	errno_t rc;
+	size_t nr;
+	size_t total_rd;
+	size_t i;
+	int rv;
+
+	rc = vfs_cwd_set(TEMP_DIR);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Suggest unique file name. */
+	rc = fmgt_new_file_suggest(&fname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_new_file(fmgt, fname, 20000);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Verify the file. */
+	f = fopen(fname, "rb");
+	PCUT_ASSERT_NOT_NULL(f);
+	total_rd = 0;
+	do {
+		nr = fread(buf, 1, sizeof(buf), f);
+		for (i = 0; i < nr; i++)
+			PCUT_ASSERT_INT_EQUALS(0, buf[i]);
+
+		total_rd += nr;
+	} while (nr > 0);
+
+	PCUT_ASSERT_INT_EQUALS(20000, total_rd);
+
+	(void)fclose(f);
+
+	/* Remove the file. */
+	rv = remove(fname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname);
+	fmgt_destroy(fmgt);
+}
+
+/** Initial update provided when requested while creating new file. */
+PCUT_TEST(new_file_init_upd)
+{
+	FILE *f = NULL;
+	fmgt_t *fmgt = NULL;
+	char *fname = NULL;
+	char buf[128];
+	errno_t rc;
+	size_t nr;
+	size_t total_rd;
+	size_t i;
+	test_resp_t resp;
+	int rv;
+
+	rc = vfs_cwd_set(TEMP_DIR);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	fmgt_set_cb(fmgt, &test_fmgt_cb, (void *)&resp);
+	resp.nupdates = 0;
+
+	fmgt_set_init_update(fmgt, true);
+
+	/* Suggest unique file name. */
+	rc = fmgt_new_file_suggest(&fname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_new_file(fmgt, fname, 20000);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	/* Verify the file. */
+	f = fopen(fname, "rb");
+	PCUT_ASSERT_NOT_NULL(f);
+	total_rd = 0;
+	do {
+		nr = fread(buf, 1, sizeof(buf), f);
+		for (i = 0; i < nr; i++)
+			PCUT_ASSERT_INT_EQUALS(0, buf[i]);
+
+		total_rd += nr;
+	} while (nr > 0);
+
+	PCUT_ASSERT_INT_EQUALS(20000, total_rd);
+
+	(void)fclose(f);
+
+	PCUT_ASSERT_TRUE(resp.nupdates > 0);
+
+	/* Remove the file. */
+	rv = remove(fname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname);
+	fmgt_destroy(fmgt);
+}
+
+static void test_fmgt_progress(void *arg, fmgt_progress_t *progress)
+{
+	test_resp_t *resp = (test_resp_t *)arg;
+
+	(void)progress;
+	++resp->nupdates;
+}
+
+PCUT_EXPORT(fmgt);
Index: uspace/lib/fmgt/test/main.c
===================================================================
--- uspace/lib/fmgt/test/main.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
+++ uspace/lib/fmgt/test/main.c	(revision c111da2e5d8d220f8936c4d80df74d461f2e0866)
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2025 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.
+ */
+
+#include <pcut/pcut.h>
+
+PCUT_INIT;
+
+PCUT_IMPORT(fmgt);
+
+PCUT_MAIN();
