Index: uspace/app/copy/copy.c
===================================================================
--- uspace/app/copy/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/copy/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,260 @@
+/*
+ * 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 copy
+ * @{
+ */
+/** @file Copy files and directories.
+ */
+
+#include <errno.h>
+#include <fmgt.h>
+#include <io/console.h>
+#include <io/cons_event.h>
+#include <io/kbd_event.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <str.h>
+#include <str_error.h>
+
+#define NAME  "copy"
+
+static bool copy_abort_query(void *);
+static void copy_progress(void *, fmgt_progress_t *);
+static fmgt_error_action_t copy_io_error_query(void *, fmgt_io_error_t *);
+
+static bool prog_upd = false;
+static bool nonint = false;
+static bool quiet = false;
+
+static console_ctrl_t *con;
+
+static fmgt_cb_t copy_fmgt_cb = {
+	.abort_query = copy_abort_query,
+	.io_error_query = copy_io_error_query,
+	.progress = copy_progress,
+};
+
+static void print_syntax(void)
+{
+	printf("Copy files and directories.\n");
+	printf("Syntax: %s [<options] <source>... <dest>\n", NAME);
+	printf("\t-h    help\n");
+	printf("\t-n    non-interactive\n");
+	printf("\t-q    quiet\n");
+}
+
+/** Called by fmgt to query for user abort.
+ *
+ * @param arg Argument (not used)
+ * @return @c true iff user requested abort
+ */
+static bool copy_abort_query(void *arg)
+{
+	cons_event_t event;
+	kbd_event_t *ev;
+	errno_t rc;
+	usec_t timeout;
+
+	if (con == NULL)
+		return false;
+
+	timeout = 0;
+	rc = console_get_event_timeout(con, &event, &timeout);
+	if (rc != EOK)
+		return false;
+
+	if (event.type == CEV_KEY && event.ev.key.type == KEY_PRESS) {
+		ev = &event.ev.key;
+		if ((ev->mods & KM_ALT) == 0 &&
+		    (ev->mods & KM_SHIFT) == 0 &&
+		    (ev->mods & KM_CTRL) != 0) {
+			if (ev->key == KC_C)
+				return true;
+		}
+	}
+
+	return false;
+}
+
+/** Called by fmgt to give the user progress update.
+ *
+ * @param arg Argument (not used)
+ * @param progress Progress report
+ */
+static void copy_progress(void *arg, fmgt_progress_t *progress)
+{
+	(void)arg;
+
+	if (!quiet) {
+		printf("\rCopied %s files, %s; current file: %s done.  "
+		    "\b\b", progress->total_procf, progress->total_procb,
+		    progress->curf_percent);
+		fflush(stdout);
+		prog_upd = true;
+	}
+}
+
+/** Called by fmgt to let user choose I/O error recovery action.
+ *
+ * @param arg Argument (not used)
+ * @param err I/O error report
+ * @return Error recovery action.
+ */
+static fmgt_error_action_t copy_io_error_query(void *arg,
+    fmgt_io_error_t *err)
+{
+	cons_event_t event;
+	kbd_event_t *ev;
+	errno_t rc;
+
+	(void)arg;
+
+	if (nonint)
+		return fmgt_er_abort;
+
+	if (prog_upd)
+		putchar('\n');
+
+	fprintf(stderr, "I/O error %s file '%s' (%s).\n",
+	    err->optype == fmgt_io_write ? "writing" : "reading",
+	    err->fname, str_error(err->rc));
+	fprintf(stderr, "[A]bort or [R]etry?\n");
+
+	if (con == NULL)
+		return fmgt_er_abort;
+
+	while (true) {
+		rc = console_get_event(con, &event);
+		if (rc != EOK)
+			return fmgt_er_abort;
+
+		if (event.type == CEV_KEY && event.ev.key.type == KEY_PRESS) {
+			ev = &event.ev.key;
+			if ((ev->mods & KM_ALT) == 0 &&
+			    (ev->mods & KM_CTRL) == 0) {
+				if (ev->c == 'r' || ev->c == 'R')
+					return fmgt_er_retry;
+				if (ev->c == 'a' || ev->c == 'A')
+					return fmgt_er_abort;
+			}
+		}
+
+		if (event.type == CEV_KEY && event.ev.key.type == KEY_PRESS) {
+			ev = &event.ev.key;
+			if ((ev->mods & KM_ALT) == 0 &&
+			    (ev->mods & KM_SHIFT) == 0 &&
+			    (ev->mods & KM_CTRL) != 0) {
+				if (ev->key == KC_C)
+					return fmgt_er_abort;
+			}
+		}
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	fmgt_t *fmgt = NULL;
+	errno_t rc;
+	int i;
+	fmgt_flist_t *flist = NULL;
+	const char *dest;
+
+	rc = fmgt_flist_create(&flist);
+	if (rc != EOK) {
+		printf("Out of memory.\n");
+		goto error;
+	}
+
+	con = console_init(stdin, stdout);
+
+	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], "-q") == 0) {
+			++i;
+			quiet = true;
+		} else {
+			printf("Invalid option '%s'.\n", argv[i]);
+			print_syntax();
+			goto error;
+		}
+	}
+
+	/* Need at least one source and one destination. */
+	if (i + 1 >= argc) {
+		print_syntax();
+		goto error;
+	}
+
+	/* All arguments except the last one are sources. */
+	do {
+		rc = fmgt_flist_append(flist, argv[i++]);
+		if (rc != EOK) {
+			printf("Out of memory.\n");
+			goto error;
+		}
+	} while (i + 1 < argc);
+
+	dest = argv[i];
+
+	rc = fmgt_create(&fmgt);
+	if (rc != EOK) {
+		printf("Out of memory.\n");
+		goto error;
+	}
+
+	fmgt_set_cb(fmgt, &copy_fmgt_cb, NULL);
+
+	rc = fmgt_copy(fmgt, flist, dest);
+	if (prog_upd)
+		putchar('\n');
+	if (rc != EOK) {
+		printf("Error creating file: %s.\n", str_error(rc));
+		goto error;
+	}
+
+	fmgt_flist_destroy(flist);
+	fmgt_destroy(fmgt);
+	return 0;
+error:
+	if (flist != NULL)
+		fmgt_flist_destroy(flist);
+	if (fmgt != NULL)
+		fmgt_destroy(fmgt);
+	return 1;
+}
+
+/** @}
+ */
Index: uspace/app/copy/doc/doxygroups.h
===================================================================
--- uspace/app/copy/doc/doxygroups.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/copy/doc/doxygroups.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,4 @@
+/** @addtogroup copy copy
+ * @brief Copy files and directories.
+ * @ingroup apps
+ */
Index: uspace/app/copy/meson.build
===================================================================
--- uspace/app/copy/meson.build	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/copy/meson.build	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -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 = [ 'console', 'fmgt', 'input' ]
+src = files('copy.c')
Index: uspace/app/meson.build
===================================================================
--- uspace/app/meson.build	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/meson.build	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -35,4 +35,5 @@
 	'blkdump',
 	'calculator',
+	'copy',
 	'corecfg',
 	'cpptest',
Index: uspace/app/nav/copy.c
===================================================================
--- uspace/app/nav/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,272 @@
+/*
+ * 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 Copy Files.
+ */
+
+#include <fmgt.h>
+#include <stdlib.h>
+#include <str_error.h>
+#include <ui/fixed.h>
+#include <ui/filelist.h>
+#include <ui/msgdialog.h>
+#include <ui/resource.h>
+#include <ui/ui.h>
+#include <ui/window.h>
+#include <stdbool.h>
+#include <str.h>
+#include "copy.h"
+#include "dlg/progress.h"
+#include "dlg/copydlg.h"
+#include "menu.h"
+#include "nav.h"
+#include "types/copy.h"
+#include "panel.h"
+
+static void copy_bok(copy_dlg_t *, void *);
+static void copy_bcancel(copy_dlg_t *, void *);
+static void copy_close(copy_dlg_t *, void *);
+
+static copy_dlg_cb_t copy_cb = {
+	.bok = copy_bok,
+	.bcancel = copy_bcancel,
+	.close = copy_close
+};
+
+static bool copy_abort_query(void *);
+static void copy_progress(void *, fmgt_progress_t *);
+
+static fmgt_cb_t copy_fmgt_cb = {
+	.abort_query = copy_abort_query,
+	.io_error_query = navigator_io_error_query,
+	.progress = copy_progress,
+};
+
+/** Open Copy dialog.
+ *
+ * @param navigator Navigator
+ * @param flist File list
+ */
+void navigator_copy_dlg(navigator_t *navigator, fmgt_flist_t *flist)
+{
+	copy_dlg_t *dlg;
+	panel_t *dpanel;
+	char *dest;
+	errno_t rc;
+
+	/* Get destination panel. */
+	dpanel = navigator_get_inactive_panel(navigator);
+	if (dpanel == NULL) {
+		/* out of memory */
+		return;
+	}
+
+	/* Get destination path from destination panel. */
+	dest = panel_get_dir(dpanel);
+	if (dest == NULL) {
+		/* out of memory */
+		return;
+	}
+
+	rc = copy_dlg_create(navigator->ui, flist, dest, &dlg);
+	if (rc != EOK) {
+		free(dest);
+		return;
+	}
+
+	copy_dlg_set_cb(dlg, &copy_cb, (void *)navigator);
+	free(dest);
+}
+
+/** Copy worker function.
+ *
+ * @param arg Argument (navigator_copy_job_t)
+ */
+static void copy_wfunc(void *arg)
+{
+	fmgt_t *fmgt = NULL;
+	navigator_copy_job_t *job = (navigator_copy_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, &copy_fmgt_cb, (void *)nav);
+	fmgt_set_init_update(fmgt, true);
+
+	rc = fmgt_copy(fmgt, job->flist, job->dest);
+	if (rc != EOK) {
+		rv = asprintf(&msg, "Error copying file(s) (%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);
+	fmgt_flist_destroy(job->flist);
+	free(job);
+	return;
+error:
+	fmgt_destroy(fmgt);
+	ui_lock(nav->ui);
+	progress_dlg_destroy(nav->progress_dlg);
+	navigator_refresh_panels(nav);
+	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);
+}
+
+/** Copy dialog confirmed.
+ *
+ * @param dlg Copy dialog
+ * @param arg Argument (navigator_t *)
+ */
+static void copy_bok(copy_dlg_t *dlg, void *arg)
+{
+	navigator_t *nav = (navigator_t *)arg;
+	ui_msg_dialog_t *dialog = NULL;
+	navigator_copy_job_t *job;
+	ui_msg_dialog_params_t params;
+	progress_dlg_params_t pd_params;
+	char *msg = NULL;
+	errno_t rc;
+
+	job = calloc(1, sizeof(navigator_copy_job_t));
+	if (job == NULL)
+		return;
+
+	job->navigator = nav;
+	job->flist = dlg->flist;
+	job->dest = str_dup(ui_entry_get_text(dlg->edest));
+	if (job->dest == NULL) {
+		free(job);
+		return;
+	}
+
+	copy_dlg_destroy(dlg);
+	dlg->flist = NULL;
+
+	progress_dlg_params_init(&pd_params);
+	pd_params.caption = "Copying";
+
+	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;
+	}
+
+	progress_dlg_set_cb(nav->progress_dlg, &navigator_progress_cb,
+	    (void *)nav);
+
+	rc = navigator_worker_start(nav, copy_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);
+}
+
+/** New file dialog cancelled.
+ *
+ * @param dlg New file dialog
+ * @param arg Argument (navigator_t *)
+ */
+static void copy_bcancel(copy_dlg_t *dlg, void *arg)
+{
+	(void)arg;
+	copy_dlg_destroy(dlg);
+}
+
+/** New file dialog closed.
+ *
+ * @param dlg New file dialog
+ * @param arg Argument (navigator_t *)
+ */
+static void copy_close(copy_dlg_t *dlg, void *arg)
+{
+	(void)arg;
+	copy_dlg_destroy(dlg);
+}
+
+/** New file abort query.
+ *
+ * @param arg Argument (navigator_t *)
+ * @return @c true iff abort is requested
+ */
+static bool copy_abort_query(void *arg)
+{
+	navigator_t *nav = (navigator_t *)arg;
+
+	return nav->abort_op;
+}
+
+/** New file progress update.
+ *
+ * @param arg Argument (navigator_t *)
+ * @param progress Progress update
+ */
+static void copy_progress(void *arg, fmgt_progress_t *progress)
+{
+	navigator_t *nav = (navigator_t *)arg;
+
+	progress_dlg_set_progress(nav->progress_dlg, progress);
+}
+
+/** @}
+ */
Index: uspace/app/nav/copy.h
===================================================================
--- uspace/app/nav/copy.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/copy.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,47 @@
+/*
+ * 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 Copy Files
+ */
+
+#ifndef COPY_H
+#define COPY_H
+
+#include <fmgt.h>
+#include "types/nav.h"
+
+extern void navigator_copy_dlg(navigator_t *, fmgt_flist_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/dlg/copydlg.c
===================================================================
--- uspace/app/nav/dlg/copydlg.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/dlg/copydlg.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,381 @@
+/*
+ * 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 Copy dialog
+ */
+
+#include <errno.h>
+#include <fmgt.h>
+#include <mem.h>
+#include <stdbool.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 "copydlg.h"
+
+static void copy_dlg_wnd_close(ui_window_t *, void *);
+static void copy_dlg_wnd_kbd(ui_window_t *, void *, kbd_event_t *);
+
+ui_window_cb_t copy_dlg_wnd_cb = {
+	.close = copy_dlg_wnd_close,
+	.kbd = copy_dlg_wnd_kbd
+};
+
+static void copy_dlg_bok_clicked(ui_pbutton_t *, void *);
+static void copy_dlg_bcancel_clicked(ui_pbutton_t *, void *);
+
+ui_pbutton_cb_t copy_dlg_bok_cb = {
+	.clicked = copy_dlg_bok_clicked
+};
+
+ui_pbutton_cb_t copy_dlg_bcancel_cb = {
+	.clicked = copy_dlg_bcancel_clicked
+};
+
+/** Create Copy dialog.
+ *
+ * @param ui User interface
+ * @param flist List of files to copy (ownership transferred)
+ * @param dest Pre-filled dstination path
+ * @param rdialog Place to store pointer to new dialog
+ * @return EOK on success or an error code
+ */
+errno_t copy_dlg_create(ui_t *ui, fmgt_flist_t *flist, const char *dest,
+    copy_dlg_t **rdialog)
+{
+	errno_t rc;
+	copy_dlg_t *dialog;
+	ui_window_t *window = NULL;
+	ui_wnd_params_t wparams;
+	ui_fixed_t *fixed = NULL;
+	ui_label_t *label = NULL;
+	ui_entry_t *edest = NULL;
+	ui_pbutton_t *bok = NULL;
+	ui_pbutton_t *bcancel = NULL;
+	gfx_rect_t rect;
+	ui_resource_t *ui_res;
+	fmgt_flist_entry_t *entry;
+	char *tcopy = NULL;
+	unsigned count;
+
+	dialog = calloc(1, sizeof(copy_dlg_t));
+	if (dialog == NULL) {
+		rc = ENOMEM;
+		goto error;
+	}
+
+	ui_wnd_params_init(&wparams);
+	wparams.caption = "Copy";
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		wparams.rect.p0.x = 0;
+		wparams.rect.p0.y = 0;
+		wparams.rect.p1.x = 40;
+		wparams.rect.p1.y = 9;
+	} else {
+		wparams.rect.p0.x = 0;
+		wparams.rect.p0.y = 0;
+		wparams.rect.p1.x = 300;
+		wparams.rect.p1.y = 155;
+	}
+
+	rc = ui_window_create(ui, &wparams, &window);
+	if (rc != EOK)
+		goto error;
+
+	ui_window_set_cb(window, &copy_dlg_wnd_cb, dialog);
+
+	ui_res = ui_window_get_res(window);
+
+	rc = ui_fixed_create(&fixed);
+	if (rc != EOK)
+		goto error;
+
+	count = fmgt_flist_count(flist);
+	if (count == 1) {
+		entry = fmgt_flist_first(flist);
+		rc = asprintf(&tcopy, "Copy \"%s\" to:",
+		    entry->fname);
+	} else {
+		rc = asprintf(&tcopy, "Copy %u files/directories to:",
+		    count);
+	}
+
+	rc = ui_label_create(ui_res, tcopy, &label);
+	if (rc != EOK)
+		goto error;
+
+	free(tcopy);
+	tcopy = NULL;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 2;
+		rect.p1.x = 17;
+		rect.p1.y = 3;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 35;
+		rect.p1.x = 190;
+		rect.p1.y = 50;
+	}
+
+	ui_label_set_rect(label, &rect);
+
+	rc = ui_fixed_add(fixed, ui_label_ctl(label));
+	if (rc != EOK)
+		goto error;
+
+	label = NULL;
+
+	rc = ui_entry_create(window, dest, &edest);
+	if (rc != EOK)
+		goto error;
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 3;
+		rect.p0.y = 3;
+		rect.p1.x = 37;
+		rect.p1.y = 4;
+	} else {
+		rect.p0.x = 10;
+		rect.p0.y = 55;
+		rect.p1.x = 290;
+		rect.p1.y = 80;
+	}
+
+	ui_entry_set_rect(edest, &rect);
+
+	rc = ui_fixed_add(fixed, ui_entry_ctl(edest));
+	if (rc != EOK)
+		goto error;
+
+	dialog->edest = edest;
+	edest = NULL;
+
+	rc = ui_pbutton_create(ui_res, "OK", &bok);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(bok, &copy_dlg_bok_cb, dialog);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 10;
+		rect.p0.y = 6;
+		rect.p1.x = 20;
+		rect.p1.y = 7;
+	} else {
+		rect.p0.x = 55;
+		rect.p0.y = 120;
+		rect.p1.x = 145;
+		rect.p1.y = 148;
+	}
+
+	ui_pbutton_set_rect(bok, &rect);
+
+	ui_pbutton_set_default(bok, true);
+
+	rc = ui_fixed_add(fixed, ui_pbutton_ctl(bok));
+	if (rc != EOK)
+		goto error;
+
+	dialog->bok = bok;
+	bok = NULL;
+
+	rc = ui_pbutton_create(ui_res, "Cancel", &bcancel);
+	if (rc != EOK)
+		goto error;
+
+	ui_pbutton_set_cb(bcancel, &copy_dlg_bcancel_cb, dialog);
+
+	/* FIXME: Auto layout */
+	if (ui_is_textmode(ui)) {
+		rect.p0.x = 22;
+		rect.p0.y = 6;
+		rect.p1.x = 32;
+		rect.p1.y = 7;
+	} else {
+		rect.p0.x = 155;
+		rect.p0.y = 120;
+		rect.p1.x = 245;
+		rect.p1.y = 148;
+	}
+
+	ui_pbutton_set_rect(bcancel, &rect);
+
+	rc = ui_fixed_add(fixed, ui_pbutton_ctl(bcancel));
+	if (rc != EOK)
+		goto error;
+
+	dialog->bcancel = bcancel;
+	bcancel = NULL;
+
+	ui_window_add(window, ui_fixed_ctl(fixed));
+	fixed = NULL;
+
+	rc = ui_window_paint(window);
+	if (rc != EOK)
+		goto error;
+
+	dialog->window = window;
+	dialog->flist = flist;
+	*rdialog = dialog;
+	return EOK;
+error:
+	if (tcopy != NULL)
+		free(tcopy);
+	if (bok != NULL)
+		ui_pbutton_destroy(bok);
+	if (bcancel != NULL)
+		ui_pbutton_destroy(bcancel);
+	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 new file dialog.
+ *
+ * @param dialog New file dialog or @c NULL
+ */
+void copy_dlg_destroy(copy_dlg_t *dialog)
+{
+	if (dialog == NULL)
+		return;
+
+	ui_window_destroy(dialog->window);
+	free(dialog);
+}
+
+/** Set new file dialog callback.
+ *
+ * @param dialog new file dialog
+ * @param cb New file dialog callbacks
+ * @param arg Callback argument
+ */
+void copy_dlg_set_cb(copy_dlg_t *dialog, copy_dlg_cb_t *cb,
+    void *arg)
+{
+	dialog->cb = cb;
+	dialog->arg = arg;
+}
+
+/** New file dialog window close handler.
+ *
+ * @param window Window
+ * @param arg Argument (copy_dlg_t *)
+ */
+static void copy_dlg_wnd_close(ui_window_t *window, void *arg)
+{
+	copy_dlg_t *dialog = (copy_dlg_t *) arg;
+
+	(void)window;
+	if (dialog->cb != NULL && dialog->cb->close != NULL) {
+		dialog->cb->close(dialog, dialog->arg);
+	}
+}
+
+/** New file dialog window keyboard event handler.
+ *
+ * @param window Window
+ * @param arg Argument (copy_dlg_t *)
+ * @param event Keyboard event
+ */
+static void copy_dlg_wnd_kbd(ui_window_t *window, void *arg,
+    kbd_event_t *event)
+{
+	copy_dlg_t *dialog = (copy_dlg_t *) arg;
+
+	if (event->type == KEY_PRESS &&
+	    (event->mods & (KM_CTRL | KM_SHIFT | KM_ALT)) == 0) {
+		if (event->key == KC_ENTER) {
+			/* Confirm */
+			if (dialog->cb != NULL && dialog->cb->bok != NULL) {
+				dialog->cb->bok(dialog, dialog->arg);
+				return;
+			}
+		} else if (event->key == KC_ESCAPE) {
+			/* Cancel */
+			if (dialog->cb != NULL && dialog->cb->bcancel != NULL) {
+				dialog->cb->bcancel(dialog, dialog->arg);
+				return;
+			}
+		}
+	}
+
+	ui_window_def_kbd(window, event);
+}
+
+/** New file dialog OK button click handler.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (copy_dlg_t *)
+ */
+static void copy_dlg_bok_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	copy_dlg_t *dialog = (copy_dlg_t *) arg;
+
+	if (dialog->cb != NULL && dialog->cb->bok != NULL) {
+		dialog->cb->bok(dialog, dialog->arg);
+	}
+}
+
+/** New file dialog cancel button click handler.
+ *
+ * @param pbutton Push button
+ * @param arg Argument (copy_dlg_t *)
+ */
+static void copy_dlg_bcancel_clicked(ui_pbutton_t *pbutton, void *arg)
+{
+	copy_dlg_t *dialog = (copy_dlg_t *) arg;
+
+	(void)pbutton;
+	if (dialog->cb != NULL && dialog->cb->bcancel != NULL) {
+		dialog->cb->bcancel(dialog, dialog->arg);
+	}
+}
+
+/** @}
+ */
Index: uspace/app/nav/dlg/copydlg.h
===================================================================
--- uspace/app/nav/dlg/copydlg.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/dlg/copydlg.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,53 @@
+/*
+ * 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 Copy dialog
+ */
+
+#ifndef DLG_COPYDLG_H
+#define DLG_COPYDLG_H
+
+#include <errno.h>
+#include <fmgt.h>
+#include <types/ui/ui.h>
+#include "../types/dlg/copydlg.h"
+
+extern errno_t copy_dlg_create(ui_t *, fmgt_flist_t *, const char *,
+    copy_dlg_t **);
+extern void copy_dlg_set_cb(copy_dlg_t *, copy_dlg_cb_t *,
+    void *);
+extern void copy_dlg_destroy(copy_dlg_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/menu.c
===================================================================
--- uspace/app/nav/menu.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/menu.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -58,4 +58,5 @@
 	ui_menu_entry_t *medit;
 	ui_menu_entry_t *mverify;
+	ui_menu_entry_t *mcopy;
 	ui_menu_entry_t *mfsep;
 	ui_menu_entry_t *mexit;
@@ -104,4 +105,10 @@
 	ui_menu_entry_set_cb(mverify, nav_menu_file_verify, (void *) menu);
 
+	rc = ui_menu_entry_create(mfile, "~C~opy", "Ctrl-C", &mcopy);
+	if (rc != EOK)
+		goto error;
+
+	ui_menu_entry_set_cb(mcopy, nav_menu_file_copy, (void *) menu);
+
 	rc = ui_menu_entry_sep_create(mfile, &mfsep);
 	if (rc != EOK)
@@ -214,4 +221,17 @@
 }
 
+/** File / Copy menu entry selected.
+ *
+ * @param mentry Menu entry
+ * @param arg Argument (navigator_t *)
+ */
+void nav_menu_file_copy(ui_menu_entry_t *mentry, void *arg)
+{
+	nav_menu_t *menu = (nav_menu_t *)arg;
+
+	if (menu->cb != NULL && menu->cb->file_copy != NULL)
+		menu->cb->file_copy(menu->cb_arg);
+}
+
 /** File / Exit menu entry selected.
  *
Index: uspace/app/nav/menu.h
===================================================================
--- uspace/app/nav/menu.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/menu.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -50,4 +50,5 @@
 extern void nav_menu_file_edit(ui_menu_entry_t *, void *);
 extern void nav_menu_file_verify(ui_menu_entry_t *, void *);
+extern void nav_menu_file_copy(ui_menu_entry_t *, void *);
 extern void nav_menu_file_exit(ui_menu_entry_t *, void *);
 
Index: uspace/app/nav/meson.build
===================================================================
--- uspace/app/nav/meson.build	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/meson.build	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -29,8 +29,10 @@
 deps = [ 'fmgt', 'ui' ]
 src = files(
+	'dlg/copydlg.c',
 	'dlg/ioerrdlg.c',
 	'dlg/newfiledlg.c',
 	'dlg/progress.c',
 	'dlg/verifydlg.c',
+	'copy.c',
 	'main.c',
 	'menu.c',
@@ -42,8 +44,10 @@
 
 test_src = files(
+	'dlg/copydlg.c',
 	'dlg/ioerrdlg.c',
 	'dlg/newfiledlg.c',
 	'dlg/progress.c',
 	'dlg/verifydlg.c',
+	'copy.c',
 	'menu.c',
 	'nav.c',
@@ -51,4 +55,5 @@
 	'panel.c',
 	'verify.c',
+	'test/dlg/copydlg.c',
 	'test/dlg/ioerrdlg.c',
 	'test/dlg/newfiledlg.c',
Index: uspace/app/nav/nav.c
===================================================================
--- uspace/app/nav/nav.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/nav.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -48,4 +48,5 @@
 #include <ui/ui.h>
 #include <ui/window.h>
+#include "copy.h"
 #include "dlg/ioerrdlg.h"
 #include "menu.h"
@@ -69,4 +70,5 @@
 static void navigator_file_edit(void *);
 static void navigator_file_verify(void *);
+static void navigator_file_copy(void *);
 static void navigator_file_exit(void *);
 
@@ -76,4 +78,5 @@
 	.file_edit = navigator_file_edit,
 	.file_verify = navigator_file_verify,
+	.file_copy = navigator_file_copy,
 	.file_exit = navigator_file_exit
 };
@@ -141,4 +144,7 @@
 			navigator_file_verify((void *)navigator);
 			break;
+		case KC_C:
+			navigator_file_copy((void *)navigator);
+			break;
 		case KC_Q:
 			ui_quit(navigator->ui);
@@ -325,4 +331,23 @@
 	for (i = 0; i < navigator_panels; i++) {
 		if (panel_is_active(navigator->panel[i]))
+			return navigator->panel[i];
+	}
+
+	/* This should not happen */
+	assert(false);
+	return NULL;
+}
+
+/** Get the currently inactive navigator panel.
+ *
+ * @param navigator Navigator
+ * @return Currently inactive panel
+ */
+panel_t *navigator_get_inactive_panel(navigator_t *navigator)
+{
+	int i;
+
+	for (i = 0; i < navigator_panels; i++) {
+		if (!panel_is_active(navigator->panel[i]))
 			return navigator->panel[i];
 	}
@@ -561,4 +586,33 @@
 }
 
+/** File / Copy menu entry selected */
+static void navigator_file_copy(void *arg)
+{
+	navigator_t *navigator = (navigator_t *)arg;
+
+	ui_file_list_entry_t *entry;
+	ui_file_list_entry_attr_t attr;
+	fmgt_flist_t *flist;
+	panel_t *panel;
+	errno_t rc;
+
+	panel = navigator_get_active_panel(navigator);
+	entry = ui_file_list_get_cursor(panel->flist);
+	ui_file_list_entry_get_attr(entry, &attr);
+
+	rc = fmgt_flist_create(&flist);
+	if (rc != EOK)
+		return;
+
+	rc = fmgt_flist_append(flist, attr.name);
+	if (rc != EOK) {
+		fmgt_flist_destroy(flist);
+		return;
+	}
+
+	/* flist ownership transferred */
+	navigator_copy_dlg(navigator, flist);
+}
+
 /** File / Exit menu entry selected */
 static void navigator_file_exit(void *arg)
Index: uspace/app/nav/nav.h
===================================================================
--- uspace/app/nav/nav.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/nav.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -49,4 +49,5 @@
 extern errno_t navigator_run(const char *);
 extern panel_t *navigator_get_active_panel(navigator_t *);
+extern panel_t *navigator_get_inactive_panel(navigator_t *);
 extern void navigator_switch_panel(navigator_t *);
 extern void navigator_refresh_panels(navigator_t *);
Index: uspace/app/nav/panel.c
===================================================================
--- uspace/app/nav/panel.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/panel.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -365,4 +365,14 @@
 }
 
+/** Get current directory from panel.
+ *
+ * @param panel Panel
+ * @return Path to current directory or @c NULL if out of memory
+ */
+char *panel_get_dir(panel_t *panel)
+{
+	return ui_file_list_get_dir(panel->flist);
+}
+
 /** Refresh panel contents.
  *
Index: uspace/app/nav/panel.h
===================================================================
--- uspace/app/nav/panel.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/panel.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -58,4 +58,5 @@
 extern void panel_deactivate(panel_t *);
 extern errno_t panel_read_dir(panel_t *, const char *);
+extern char *panel_get_dir(panel_t *);
 extern errno_t panel_refresh(panel_t *);
 extern void panel_activate_req(panel_t *);
Index: uspace/app/nav/test/dlg/copydlg.c
===================================================================
--- uspace/app/nav/test/dlg/copydlg.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/test/dlg/copydlg.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,91 @@
+/*
+ * 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 <errno.h>
+#include <fmgt.h>
+#include <pcut/pcut.h>
+#include <ui/ui.h>
+#include "../../dlg/copydlg.h"
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(copydlg);
+
+static copy_dlg_cb_t copy_dlg_cb;
+
+/** Create and destroy copy dialog. */
+PCUT_TEST(create_destroy)
+{
+	ui_t *ui;
+	copy_dlg_t *dlg = NULL;
+	fmgt_flist_t *flist;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_create(&flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = copy_dlg_create(ui, flist, "foo", &dlg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dlg);
+
+	copy_dlg_destroy(dlg);
+
+	fmgt_flist_destroy(flist);
+	ui_destroy(ui);
+}
+
+/** Set callbacks for copy dialog. */
+PCUT_TEST(set_cb)
+{
+	ui_t *ui;
+	copy_dlg_t *dlg = NULL;
+	fmgt_flist_t *flist;
+	errno_t rc;
+
+	rc = ui_create_disp(NULL, &ui);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_create(&flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = copy_dlg_create(ui, flist, "foo", &dlg);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+	PCUT_ASSERT_NOT_NULL(dlg);
+
+	copy_dlg_set_cb(dlg, &copy_dlg_cb, NULL);
+
+	copy_dlg_destroy(dlg);
+
+	fmgt_flist_destroy(flist);
+	ui_destroy(ui);
+}
+
+PCUT_EXPORT(copydlg);
Index: uspace/app/nav/test/main.c
===================================================================
--- uspace/app/nav/test/main.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/test/main.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -31,4 +31,5 @@
 PCUT_INIT;
 
+PCUT_IMPORT(copydlg);
 PCUT_IMPORT(ioerrdlg);
 PCUT_IMPORT(newfiledlg);
Index: uspace/app/nav/types/copy.h
===================================================================
--- uspace/app/nav/types/copy.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/types/copy.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -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 Copy types
+ */
+
+#ifndef TYPES_COPY_H
+#define TYPES_COPY_H
+
+#include <fmgt.h>
+
+/** Navigator Copy job */
+typedef struct {
+	/** Navigator */
+	struct navigator *navigator;
+	/** File list */
+	fmgt_flist_t *flist;
+	/** Destination */
+	char *dest;
+} navigator_copy_job_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/types/dlg/copydlg.h
===================================================================
--- uspace/app/nav/types/dlg/copydlg.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/app/nav/types/dlg/copydlg.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,82 @@
+/*
+ * 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 Copy dialog
+ */
+
+#ifndef TYPES_DLG_VERIFYDLG_H
+#define TYPES_DLG_VERIFYDLG_H
+
+#include <errno.h>
+#include <fmgt.h>
+#include <ui/entry.h>
+#include <ui/label.h>
+#include <ui/pbutton.h>
+#include <ui/window.h>
+#include <stdbool.h>
+
+/** Copy dialog */
+typedef struct copy_dlg {
+	/** Dialog window */
+	ui_window_t *window;
+	/** "Copy 'xxx'" */
+	ui_label_t *lcopy;
+	/** File */
+	ui_entry_t *esize;
+	/** Destination text entry */
+	ui_entry_t *edest;
+	/** OK button */
+	ui_pbutton_t *bok;
+	/** Cancel button */
+	ui_pbutton_t *bcancel;
+	/** Copy dialog callbacks */
+	struct copy_dlg_cb *cb;
+	/** Callback argument */
+	void *arg;
+	/** File list */
+	fmgt_flist_t *flist;
+} copy_dlg_t;
+
+/** Copy dialog callbacks */
+typedef struct copy_dlg_cb {
+	/** OK button was pressed */
+	void (*bok)(copy_dlg_t *, void *);
+	/** Cancel button was pressed */
+	void (*bcancel)(copy_dlg_t *, void *);
+	/** Window closure requested (e.g. via close button) */
+	void (*close)(copy_dlg_t *, void *);
+} copy_dlg_cb_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/nav/types/menu.h
===================================================================
--- uspace/app/nav/types/menu.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/app/nav/types/menu.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -51,4 +51,6 @@
 	/** File / Verify */
 	void (*file_verify)(void *);
+	/** File / Copy */
+	void (*file_copy)(void *);
 	/** File / Exit */
 	void (*file_exit)(void *);
Index: uspace/lib/fmgt/include/fmgt.h
===================================================================
--- uspace/lib/fmgt/include/fmgt.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/include/fmgt.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -42,4 +42,5 @@
 #include <stddef.h>
 #include <stdint.h>
+#include "fmgt/copy.h"
 #include "fmgt/flist.h"
 #include "fmgt/newfile.h"
@@ -52,4 +53,6 @@
 extern void fmgt_destroy(fmgt_t *);
 extern void fmgt_set_init_update(fmgt_t *, bool);
+extern const char *fmgt_basename(const char *);
+extern bool fmgt_is_dir(const char *);
 
 #endif
Index: uspace/lib/fmgt/include/fmgt/copy.h
===================================================================
--- uspace/lib/fmgt/include/fmgt/copy.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/lib/fmgt/include/fmgt/copy.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,48 @@
+/*
+ * 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 fmgt
+ * @{
+ */
+/**
+ * @file
+ * @brief Copy files and directories.
+ */
+
+#ifndef FMGT_COPY_H
+#define FMGT_COPY_H
+
+#include <errno.h>
+#include "types/fmgt.h"
+
+extern errno_t fmgt_copy(fmgt_t *, fmgt_flist_t *, const char *);
+
+#endif
+
+/** @}
+ */
Index: uspace/lib/fmgt/include/types/fmgt.h
===================================================================
--- uspace/lib/fmgt/include/types/fmgt.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/include/types/fmgt.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -140,7 +140,7 @@
 /** File system tree walk callbacks */
 typedef struct {
-	errno_t (*dir_enter)(void *, const char *);
-	errno_t (*dir_leave)(void *, const char *);
-	errno_t (*file)(void *, const char *);
+	errno_t (*dir_enter)(void *, const char *, const char *);
+	errno_t (*dir_leave)(void *, const char *, const char *);
+	errno_t (*file)(void *, const char *, const char *);
 } fmgt_walk_cb_t;
 
@@ -149,4 +149,8 @@
 	/** List of files or directories (walk roots) */
 	fmgt_flist_t *flist;
+	/** Destination path */
+	const char *dest;
+	/** Copy files into destination directory. */
+	bool into_dest;
 	/** Callbacks */
 	fmgt_walk_cb_t *cb;
Index: uspace/lib/fmgt/meson.build
===================================================================
--- uspace/lib/fmgt/meson.build	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/meson.build	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -28,4 +28,5 @@
 
 src = files(
+	'src/copy.c',
 	'src/flist.c',
 	'src/fmgt.c',
@@ -36,4 +37,5 @@
 
 test_src = files(
+	'test/copy.c',
 	'test/flist.c',
 	'test/fmgt.c',
Index: uspace/lib/fmgt/src/copy.c
===================================================================
--- uspace/lib/fmgt/src/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/lib/fmgt/src/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,227 @@
+/*
+ * 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 fmgt
+ * @{
+ */
+/** @file Copy files and directories.
+ */
+
+#include <errno.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <vfs/vfs.h>
+
+#include "fmgt.h"
+#include "fmgt/walk.h"
+#include "../private/fmgt.h"
+
+static errno_t fmgt_copy_dir_enter(void *, const char *, const char *);
+static errno_t fmgt_copy_file(void *, const char *, const char *);
+
+static fmgt_walk_cb_t fmgt_copy_cb = {
+	.dir_enter = fmgt_copy_dir_enter,
+	.file = fmgt_copy_file
+};
+
+static errno_t fmgt_write(fmgt_t *fmgt, int fd, const char *fname,
+    aoff64_t *pos, void *buffer, size_t nbytes)
+{
+	size_t total_written;
+	char *bp = (char *)buffer;
+	fmgt_io_error_t err;
+	fmgt_error_action_t action;
+	size_t nw;
+	errno_t rc;
+
+	total_written = 0;
+	while (total_written < nbytes) {
+		do {
+			rc = vfs_write(fd, pos, bp + total_written,
+			    nbytes - total_written, &nw);
+			if (rc == EOK)
+				break;
+
+			/* I/O error */
+			err.fname = fname;
+			err.optype = fmgt_io_write;
+			err.rc = rc;
+			fmgt_timer_stop(fmgt);
+			action = fmgt_io_error_query(fmgt, &err);
+			fmgt_timer_start(fmgt);
+		} while (action == fmgt_er_retry);
+
+		/* Not recovered? */
+		if (rc != EOK)
+			return rc;
+
+		total_written += nw;
+	}
+
+	return EOK;
+}
+
+/** Copy operation - enter directory.
+ *
+ * @param arg Argument (fmgt_t *)
+ * @param fname Source directory name
+ * @param dest Destination directory name
+ * @return EOK on success or an error code
+ */
+static errno_t fmgt_copy_dir_enter(void *arg, const char *src, const char *dest)
+{
+	fmgt_t *fmgt = (fmgt_t *)arg;
+	errno_t rc;
+
+	rc = vfs_link_path(dest, KIND_DIRECTORY, NULL);
+
+	/* It is okay if the directory exists. */
+	if (rc != EOK && rc != EEXIST)
+		return rc; // XXX error recovery?
+
+	(void)fmgt;
+	return EOK;
+}
+
+/** Copy single file.
+ *
+ * @param arg Argument (fmgt_t *)
+ * @param fname Source file name
+ * @param dest Destination file name
+ * @return EOK on success or an error code
+ */
+static errno_t fmgt_copy_file(void *arg, const char *src, const char *dest)
+{
+	fmgt_t *fmgt = (fmgt_t *)arg;
+	int rfd;
+	int wfd;
+	size_t nr;
+	aoff64_t rpos = 0;
+	aoff64_t wpos = 0;
+	char *buffer;
+	fmgt_io_error_t err;
+	fmgt_error_action_t action;
+	errno_t rc;
+
+	buffer = calloc(BUFFER_SIZE, 1);
+	if (buffer == NULL)
+		return ENOMEM;
+
+	rc = vfs_lookup_open(src, WALK_REGULAR, MODE_READ, &rfd);
+	if (rc != EOK) {
+		free(buffer);
+		return rc; // XXX error recovery?
+	}
+
+	rc = vfs_lookup_open(dest, WALK_REGULAR | WALK_MAY_CREATE, MODE_WRITE,
+	    &wfd);
+	if (rc != EOK) {
+		free(buffer);
+		vfs_put(rfd);
+		return rc;
+	}
+
+	fmgt_progress_init_file(fmgt, src);
+
+	do {
+		do {
+			rc = vfs_read(rfd, &rpos, buffer, BUFFER_SIZE, &nr);
+			if (rc == EOK)
+				break;
+
+			/* I/O error */
+			err.fname = src;
+			err.optype = fmgt_io_read;
+			err.rc = rc;
+			fmgt_timer_stop(fmgt);
+			action = fmgt_io_error_query(fmgt, &err);
+			fmgt_timer_start(fmgt);
+		} while (action == fmgt_er_retry);
+
+		/* Not recovered? */
+		if (rc != EOK)
+			goto error;
+
+		rc = fmgt_write(fmgt, wfd, dest, &wpos, buffer, nr);
+		if (rc != EOK)
+			goto error;
+
+		fmgt_progress_incr_bytes(fmgt, nr);
+
+		/* User requested abort? */
+		if (fmgt_abort_query(fmgt)) {
+			rc = EINTR;
+			goto error;
+		}
+	} while (nr > 0);
+
+	free(buffer);
+	vfs_put(rfd);
+	vfs_put(wfd);
+	fmgt_progress_incr_files(fmgt);
+	return EOK;
+error:
+	free(buffer);
+	vfs_put(rfd);
+	vfs_put(wfd);
+	fmgt_final_progress_update(fmgt);
+	return rc;
+}
+
+/** copy files.
+ *
+ * @param fmgt File management object
+ * @param flist File list
+ * @param dest Destination path
+ * @return EOK on success or an error code
+ */
+errno_t fmgt_copy(fmgt_t *fmgt, fmgt_flist_t *flist, const char *dest)
+{
+	fmgt_walk_params_t params;
+	errno_t rc;
+
+	fmgt_walk_params_init(&params);
+
+	params.flist = flist;
+	params.dest = dest;
+	params.cb = &fmgt_copy_cb;
+	params.arg = (void *)fmgt;
+	if (fmgt_is_dir(dest))
+		params.into_dest = true;
+
+	fmgt_progress_init(fmgt);
+
+	fmgt_timer_start(fmgt);
+	fmgt_initial_progress_update(fmgt);
+	rc = fmgt_walk(&params);
+	fmgt_final_progress_update(fmgt);
+	return rc;
+}
+
+/** @}
+ */
Index: uspace/lib/fmgt/src/fmgt.c
===================================================================
--- uspace/lib/fmgt/src/fmgt.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/src/fmgt.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -40,4 +40,5 @@
 #include <stddef.h>
 #include <str.h>
+#include <str.h>
 #include <vfs/vfs.h>
 #include <dirent.h>
@@ -282,4 +283,37 @@
 }
 
+/** Return base name (without path component).
+ *
+ * @param path Pathname
+ * @return Base name without directory components
+ */
+const char *fmgt_basename(const char *path)
+{
+	const char *p;
+
+	p = str_rchr(path, '/');
+	if (p != NULL)
+		return p + 1;
+	else
+		return path;
+}
+
+/** Determine if pathname is an existing directory.
+ *
+ * @param path Pathname
+ * @return @c true if @a path exists and is a directory
+ */
+bool fmgt_is_dir(const char *path)
+{
+	vfs_stat_t stat;
+	errno_t rc;
+
+	rc = vfs_stat_path(path, &stat);
+	if (rc != EOK)
+		return false;
+
+	return stat.is_directory;
+}
+
 /** @}
  */
Index: uspace/lib/fmgt/src/verify.c
===================================================================
--- uspace/lib/fmgt/src/verify.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/src/verify.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -33,13 +33,8 @@
  */
 
-//#include <dirent.h>
 #include <errno.h>
 #include <stdbool.h>
-//#include <stdio.h>
 #include <stdlib.h>
-//#include <stddef.h>
-//#include <str.h>
 #include <vfs/vfs.h>
-//#include <dirent.h>
 
 #include "fmgt.h"
@@ -47,5 +42,5 @@
 #include "../private/fmgt.h"
 
-static errno_t fmgt_verify_file(void *, const char *);
+static errno_t fmgt_verify_file(void *, const char *, const char *);
 
 static fmgt_walk_cb_t fmgt_verify_cb = {
@@ -53,5 +48,13 @@
 };
 
-static errno_t fmgt_verify_file(void *arg, const char *fname)
+/** Verify a single file.
+ *
+ * @param arg Argument (fmgt_t *)
+ * @param fname File name
+ * @param unused Unused
+ * @return EOK on success or an error code
+ */
+static errno_t fmgt_verify_file(void *arg, const char *fname,
+    const char *unused)
 {
 	fmgt_t *fmgt = (fmgt_t *)arg;
@@ -63,4 +66,6 @@
 	fmgt_error_action_t action;
 	errno_t rc;
+
+	(void)unused;
 
 	buffer = calloc(BUFFER_SIZE, 1);
@@ -95,4 +100,5 @@
 			free(buffer);
 			vfs_put(fd);
+			fmgt_final_progress_update(fmgt);
 			return rc;
 		}
@@ -104,4 +110,5 @@
 			free(buffer);
 			vfs_put(fd);
+			fmgt_final_progress_update(fmgt);
 			return EINTR;
 		}
Index: uspace/lib/fmgt/src/walk.c
===================================================================
--- uspace/lib/fmgt/src/walk.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/src/walk.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -46,5 +46,6 @@
 #include "fmgt/walk.h"
 
-static errno_t fmgt_walk_subtree(fmgt_walk_params_t *, const char *);
+static errno_t fmgt_walk_subtree(fmgt_walk_params_t *, const char *,
+    const char *);
 
 /** Initialize walk parameters.
@@ -63,12 +64,14 @@
  *
  * @param params Walk parameters
- * @param fname File path
- *
- * @return EOK on success or an error code
- */
-static errno_t fmgt_walk_file(fmgt_walk_params_t *params, const char *fname)
+ * @param fname Source path
+ * @param dest Destination path
+ *
+ * @return EOK on success or an error code
+ */
+static errno_t fmgt_walk_file(fmgt_walk_params_t *params, const char *fname,
+    const char *dest)
 {
 	if (params->cb->file != NULL)
-		return params->cb->file(params->arg, fname);
+		return params->cb->file(params->arg, fname, dest);
 	else
 		return EOK;
@@ -78,12 +81,13 @@
  *
  * @param params Walk parameters
- * @param dname Directory path
+ * @param dname Source directory
+ * @param dest Destination path
  * @return EOK on success or an error code
  */
 static errno_t fmgt_walk_dir_enter(fmgt_walk_params_t *params,
-    const char *dname)
+    const char *dname, const char *dest)
 {
 	if (params->cb->dir_enter != NULL)
-		return params->cb->dir_enter(params->arg, dname);
+		return params->cb->dir_enter(params->arg, dname, dest);
 	else
 		return EOK;
@@ -94,11 +98,12 @@
  * @param params Walk parameters
  * @param dname Directory path
+ * @param dest Destination path
  * @return EOK on success or an error code
  */
 static errno_t fmgt_walk_dir_leave(fmgt_walk_params_t *params,
-    const char *dname)
-{
-	if (params->cb->dir_enter != NULL)
-		return params->cb->dir_leave(params->arg, dname);
+    const char *dname, const char *dest)
+{
+	if (params->cb->dir_leave != NULL)
+		return params->cb->dir_leave(params->arg, dname, dest);
 	else
 		return EOK;
@@ -109,15 +114,18 @@
  * @param params Walk parameters
  * @param dname Directory name
- * @return EOK on success or an error code
- */
-static errno_t fmgt_walk_dir(fmgt_walk_params_t *params, const char *dname)
+ * @param dest Destination path or @c NULL
+ * @return EOK on success or an error code
+ */
+static errno_t fmgt_walk_dir(fmgt_walk_params_t *params, const char *dname,
+    const char *dest)
 {
 	DIR *dir = NULL;
 	struct dirent *de;
 	errno_t rc;
-	char *pathname = NULL;
+	char *srcpath = NULL;
+	char *destpath = NULL;
 	int rv;
 
-	rc = fmgt_walk_dir_enter(params, dname);
+	rc = fmgt_walk_dir_enter(params, dname, dest);
 	if (rc != EOK)
 		goto error;
@@ -131,5 +139,5 @@
 	de = readdir(dir);
 	while (de != NULL) {
-		rv = asprintf(&pathname, "%s/%s", dname, de->d_name);
+		rv = asprintf(&srcpath, "%s/%s", dname, de->d_name);
 		if (rv < 0) {
 			rc = ENOMEM;
@@ -137,15 +145,28 @@
 		}
 
-		rc = fmgt_walk_subtree(params, pathname);
+		if (dest != NULL) {
+			rv = asprintf(&destpath, "%s/%s", dest, de->d_name);
+			if (rv < 0) {
+				rc = ENOMEM;
+				free(srcpath);
+				goto error;
+			}
+		}
+
+		rc = fmgt_walk_subtree(params, srcpath, destpath);
 		if (rc != EOK) {
-			free(pathname);
+			free(srcpath);
+			if (destpath != NULL)
+				free(destpath);
 			goto error;
 		}
 
-		free(pathname);
+		free(srcpath);
+		if (destpath != NULL)
+			free(destpath);
 		de = readdir(dir);
 	}
 
-	rc = fmgt_walk_dir_leave(params, dname);
+	rc = fmgt_walk_dir_leave(params, dname, dest);
 	if (rc != EOK)
 		return rc;
@@ -161,10 +182,12 @@
 /** Walk subtree.
  *
- * @params params Walk parameters.
- * @params fname Subtree path.
+ * @param params Walk parameters.
+ * @param fname Subtree path.
+ * @param dest Destination path
  *
  * @return EOK on success or an error code.
  */
-static errno_t fmgt_walk_subtree(fmgt_walk_params_t *params, const char *fname)
+static errno_t fmgt_walk_subtree(fmgt_walk_params_t *params, const char *fname,
+    const char *dest)
 {
 	vfs_stat_t stat;
@@ -177,10 +200,10 @@
 	if (stat.is_directory) {
 		/* Directory */
-		rc = fmgt_walk_dir(params, fname);
+		rc = fmgt_walk_dir(params, fname, dest);
 		if (rc != EOK)
 			return rc;
 	} else {
 		/* Not a directory */
-		rc = fmgt_walk_file(params, fname);
+		rc = fmgt_walk_file(params, fname, dest);
 		if (rc != EOK)
 			return rc;
@@ -202,9 +225,21 @@
 {
 	fmgt_flist_entry_t *entry;
+	char *destname;
 	errno_t rc;
+	int rv;
 
 	entry = fmgt_flist_first(params->flist);
 	while (entry != NULL) {
-		rc = fmgt_walk_subtree(params, entry->fname);
+		if (params->into_dest) {
+			rv = asprintf(&destname, "%s/%s",
+			    params->dest, fmgt_basename(entry->fname));
+			if (rv < 0)
+				return ENOMEM;
+		} else {
+			destname = NULL;
+		}
+
+		rc = fmgt_walk_subtree(params, entry->fname,
+		    destname != NULL ? destname : params->dest);
 		if (rc != EOK)
 			return rc;
Index: uspace/lib/fmgt/test/copy.c
===================================================================
--- uspace/lib/fmgt/test/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
+++ uspace/lib/fmgt/test/copy.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -0,0 +1,232 @@
+/*
+ * 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 <errno.h>
+#include <fmgt.h>
+#include <pcut/pcut.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <str.h>
+#include <vfs/vfs.h>
+
+PCUT_INIT;
+
+PCUT_TEST_SUITE(copy);
+
+/** Copy file to file. */
+PCUT_TEST(copy_file_file)
+{
+	fmgt_t *fmgt = NULL;
+	char buf[L_tmpnam];
+	char *sname;
+	char *dname;
+	FILE *f;
+	char *p;
+	int rv;
+	fmgt_flist_t *flist;
+	errno_t rc;
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&sname, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = asprintf(&dname, "%s/%s", p, "b");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	f = fopen(sname, "wb");
+	PCUT_ASSERT_NOT_NULL(f);
+
+	rv = fprintf(f, "X");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = fclose(f);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_create(&flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_append(flist, sname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_copy(fmgt, flist, dname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	fmgt_flist_destroy(flist);
+
+	rv = remove(sname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(dname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(sname);
+	free(dname);
+	fmgt_destroy(fmgt);
+}
+
+/** Copy directory to directory. */
+PCUT_TEST(copy_dir_dir)
+{
+	fmgt_t *fmgt = NULL;
+	char buf[L_tmpnam];
+	char *sname;
+	char *dname;
+	char *p;
+	int rv;
+	fmgt_flist_t *flist;
+	errno_t rc;
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&sname, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = asprintf(&dname, "%s/%s", p, "b");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rc = vfs_link_path(sname, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_create(&flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_append(flist, sname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_copy(fmgt, flist, dname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	fmgt_flist_destroy(flist);
+
+	rv = remove(sname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(dname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(sname);
+	free(dname);
+	fmgt_destroy(fmgt);
+}
+
+/** Copy files and directories into directory. */
+PCUT_TEST(copy_into_dir)
+{
+	fmgt_t *fmgt = NULL;
+	char buf[L_tmpnam];
+	char *fname;
+	char *dname;
+	FILE *f;
+	char *p;
+	int rv;
+	fmgt_flist_t *flist;
+	errno_t rc;
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&fname, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = asprintf(&dname, "%s/%s", p, "b");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	f = fopen(fname, "wb");
+	PCUT_ASSERT_NOT_NULL(f);
+
+	rv = fprintf(f, "X");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = fclose(f);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rc = vfs_link_path(dname, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_create(&fmgt);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_create(&flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_append(flist, fname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_append(flist, dname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_copy(fmgt, flist, dname);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	fmgt_flist_destroy(flist);
+
+	rv = remove(fname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(dname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname);
+	free(dname);
+	fmgt_destroy(fmgt);
+}
+
+PCUT_EXPORT(copy);
Index: uspace/lib/fmgt/test/walk.c
===================================================================
--- uspace/lib/fmgt/test/walk.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/fmgt/test/walk.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -39,7 +39,7 @@
 PCUT_TEST_SUITE(walk);
 
-static errno_t test_walk_dir_enter(void *, const char *);
-static errno_t test_walk_dir_leave(void *, const char *);
-static errno_t test_walk_file(void *, const char *);
+static errno_t test_walk_dir_enter(void *, const char *, const char *);
+static errno_t test_walk_dir_leave(void *, const char *, const char *);
+static errno_t test_walk_file(void *, const char *, const char *);
 
 static fmgt_walk_cb_t test_walk_cb = {
@@ -55,9 +55,12 @@
 	char *dirname;
 	char *fname;
+	char *dest;
+	char *de_dest;
+	char *dl_dest;
 	errno_t rc;
 } test_resp_t;
 
-/** Walk file system tree. */
-PCUT_TEST(walk_success)
+/** Walk file system tree with no destination and successful result. */
+PCUT_TEST(walk_nodest_success)
 {
 	char buf[L_tmpnam];
@@ -129,24 +132,102 @@
 }
 
-errno_t test_walk_dir_enter(void *arg, const char *fname)
+/** Walk file system tree with destination and successful result. */
+PCUT_TEST(walk_dest_success)
+{
+	char buf[L_tmpnam];
+	char *fname;
+	FILE *f;
+	char *p;
+	int rv;
+	fmgt_flist_t *flist;
+	fmgt_walk_params_t params;
+	test_resp_t resp;
+	errno_t rc;
+
+	/* Create name for temporary directory */
+	p = tmpnam(buf);
+	PCUT_ASSERT_NOT_NULL(p);
+
+	/* Create temporary directory */
+	rc = vfs_link_path(p, KIND_DIRECTORY, NULL);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rv = asprintf(&fname, "%s/%s", p, "a");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	f = fopen(fname, "wb");
+	PCUT_ASSERT_NOT_NULL(f);
+
+	rv = fprintf(f, "X");
+	PCUT_ASSERT_TRUE(rv >= 0);
+
+	rv = fclose(f);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rc = fmgt_flist_create(&flist);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	rc = fmgt_flist_append(flist, p);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	fmgt_walk_params_init(&params);
+	params.flist = flist;
+	params.dest = "foo";
+	params.cb = &test_walk_cb;
+	params.arg = &resp;
+
+	resp.dir_enter = false;
+	resp.dir_leave = false;
+	resp.file_proc = false;
+	resp.rc = EOK;
+
+	rc = fmgt_walk(&params);
+	PCUT_ASSERT_ERRNO_VAL(EOK, rc);
+
+	PCUT_ASSERT_TRUE(resp.dir_enter);
+	PCUT_ASSERT_TRUE(resp.dir_leave);
+	PCUT_ASSERT_TRUE(resp.file_proc);
+	PCUT_ASSERT_STR_EQUALS(p, resp.dirname);
+	PCUT_ASSERT_STR_EQUALS(fname, resp.fname);
+	printf("dest='%s'\n", resp.dest);
+	printf("de_dest='%s'\n", resp.de_dest);
+	printf("dl_dest='%s'\n", resp.dl_dest);
+
+	free(resp.dirname);
+	free(resp.fname);
+	fmgt_flist_destroy(flist);
+
+	rv = remove(fname);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	rv = remove(p);
+	PCUT_ASSERT_INT_EQUALS(0, rv);
+
+	free(fname);
+}
+
+errno_t test_walk_dir_enter(void *arg, const char *fname, const char *dest)
 {
 	test_resp_t *resp = (test_resp_t *)arg;
 	resp->dir_enter = true;
 	resp->dirname = str_dup(fname);
+	resp->de_dest = str_dup(dest);
 	return resp->rc;
 }
 
-errno_t test_walk_dir_leave(void *arg, const char *fname)
+errno_t test_walk_dir_leave(void *arg, const char *fname, const char *dest)
 {
 	test_resp_t *resp = (test_resp_t *)arg;
 	resp->dir_leave = true;
+	resp->dl_dest = str_dup(dest);
 	return resp->rc;
 }
 
-errno_t test_walk_file(void *arg, const char *fname)
+errno_t test_walk_file(void *arg, const char *fname, const char *dest)
 {
 	test_resp_t *resp = (test_resp_t *)arg;
 	resp->file_proc = true;
 	resp->fname = str_dup(fname);
+	resp->dest = str_dup(dest);
 	return resp->rc;
 }
Index: uspace/lib/ui/include/ui/filelist.h
===================================================================
--- uspace/lib/ui/include/ui/filelist.h	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/ui/include/ui/filelist.h	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -50,4 +50,5 @@
 extern void ui_file_list_set_rect(ui_file_list_t *, gfx_rect_t *);
 extern errno_t ui_file_list_read_dir(ui_file_list_t *, const char *);
+extern char *ui_file_list_get_dir(ui_file_list_t *);
 extern errno_t ui_file_list_activate(ui_file_list_t *);
 extern errno_t ui_file_list_refresh(ui_file_list_t *);
Index: uspace/lib/ui/src/filelist.c
===================================================================
--- uspace/lib/ui/src/filelist.c	(revision 144fafda7cb6d43dfda1c93b8e46b8d51635e0c2)
+++ uspace/lib/ui/src/filelist.c	(revision 2309891a40e1d152d79920dd348288280124e4fb)
@@ -424,4 +424,13 @@
 }
 
+/** Return path to the current directory.
+ *
+ * @return Path to current directory or @c NULL if out of memory
+ */
+char *ui_file_list_get_dir(ui_file_list_t *flist)
+{
+	return str_dup(flist->dir);
+}
+
 /** Re-read file list from directory.
  *
