Index: .bzrignore
===================================================================
--- .bzrignore	(revision cfb1217d7123cbca17cad6d3023e7274209f2e89)
+++ .bzrignore	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -66,4 +66,5 @@
 uspace/app/stats/stats
 uspace/app/sysinfo/sysinfo
+uspace/app/sysinst/sysinst
 uspace/app/taskdump/taskdump
 uspace/app/tester/tester
@@ -124,4 +125,5 @@
 uspace/dist/app/stats
 uspace/dist/app/sysinfo
+uspace/dist/app/sysinst
 uspace/dist/app/taskdump
 uspace/dist/app/tester
Index: boot/Makefile.common
===================================================================
--- boot/Makefile.common	(revision cfb1217d7123cbca17cad6d3023e7274209f2e89)
+++ boot/Makefile.common	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -210,4 +210,5 @@
 	$(USPACE_PATH)/app/stats/stats \
 	$(USPACE_PATH)/app/sysinfo/sysinfo \
+	$(USPACE_PATH)/app/sysinst/sysinst \
 	$(USPACE_PATH)/app/top/top \
 	$(USPACE_PATH)/app/untar/untar \
Index: uspace/Makefile
===================================================================
--- uspace/Makefile	(revision cfb1217d7123cbca17cad6d3023e7274209f2e89)
+++ uspace/Makefile	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -81,4 +81,5 @@
 	app/ping \
 	app/sysinfo \
+	app/sysinst \
 	app/mkbd \
 	app/date \
Index: uspace/app/sysinst/Makefile
===================================================================
--- uspace/app/sysinst/Makefile	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
+++ uspace/app/sysinst/Makefile	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -0,0 +1,45 @@
+#
+# Copyright (c) 2014 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.
+#
+
+USPACE_PREFIX = ../..
+LIBS = \
+    $(LIBBLOCK_PREFIX)/libblock.a \
+    $(LIBFDISK_PREFIX)/libfdisk.a \
+
+EXTRA_CFLAGS = \
+    -I$(LIBBLOCK_PREFIX) \
+    -I$(LIBFDISK_PREFIX)/include \
+    -Iinclude
+
+BINARY = sysinst
+
+SOURCES = \
+	futil.c \
+	sysinst.c
+
+include $(USPACE_PREFIX)/Makefile.common
Index: uspace/app/sysinst/futil.c
===================================================================
--- uspace/app/sysinst/futil.c	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
+++ uspace/app/sysinst/futil.c	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -0,0 +1,193 @@
+/*
+ * Copyright (c) 2014 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 sysinst
+ * @{
+ */
+/** @file File manipulation utility functions for installer
+ */
+
+#include <dirent.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <dirent.h>
+
+#include "futil.h"
+
+#define BUF_SIZE 16384
+static char buf[BUF_SIZE];
+
+/** Copy file.
+ *
+ * @param srcp Source path
+ * @param dstp Destination path
+ *
+ * @return EOK on success, EIO on I/O error
+ */
+int futil_copy_file(const char *srcp, const char *destp)
+{
+	int sf, df;
+	ssize_t nr, nw;
+	int rc;
+
+	printf("Copy '%s' to '%s'.\n", srcp, destp);
+
+	sf = open(srcp, O_RDONLY);
+	if (sf < 0)
+		return EIO;
+
+	df = open(destp, O_CREAT | O_WRONLY, 0);
+	if (df < 0)
+		return EIO;
+
+	do {
+		nr = read(sf, buf, BUF_SIZE);
+		if (nr == 0)
+			break;
+		if (nr < 0)
+			return EIO;
+
+		nw = write(df, buf, nr);
+		if (nw <= 0)
+			return EIO;
+	} while (true);
+
+	(void) close(sf);
+
+	rc = close(df);
+	if (rc < 0)
+		return EIO;
+
+	return EOK;
+}
+
+/** Copy contents of srcdir (recursively) into destdir.
+ *
+ * @param srcdir Source directory
+ * @param destdir Destination directory
+ *
+ * @return EOK on success, ENOMEM if out of memory, EIO on I/O error
+ */
+int futil_rcopy_contents(const char *srcdir, const char *destdir)
+{
+	DIR *dir;
+	struct dirent *de;
+	struct stat s;
+	char *srcp, *destp;
+	int rc;
+
+	dir = opendir(srcdir);
+	if (dir == NULL)
+		return EIO;
+
+	de = readdir(dir);
+	while (de != NULL) {
+		if (asprintf(&srcp, "%s/%s", srcdir, de->d_name) < 0)
+			return ENOMEM;
+		if (asprintf(&destp, "%s/%s", destdir, de->d_name) < 0)
+			return ENOMEM;
+
+		rc = stat(srcp, &s);
+		if (rc != EOK)
+			return EIO;
+
+		if (s.is_file) {
+			rc = futil_copy_file(srcp, destp);
+			if (rc != EOK)
+				return EIO;
+		} else if (s.is_directory) {
+			printf("Create directory '%s'\n", destp);
+			rc = mkdir(destp, 0);
+			if (rc != EOK)
+				return EIO;
+			rc = futil_rcopy_contents(srcp, destp);
+			if (rc != EOK)
+				return EIO;
+		} else {
+			return EIO;
+		}
+
+		de = readdir(dir);
+	}
+
+	return EOK;
+}
+
+/** Return file contents as a heap-allocated block of bytes.
+ *
+ * @param srcp File path
+ * @param rdata Place to store pointer to data
+ * @param rsize Place to store size of data
+ *
+ * @return EOK on success, ENOENT if failed to open file, EIO on other
+ *         I/O error, ENOMEM if out of memory
+ */
+int futil_get_file(const char *srcp, void **rdata, size_t *rsize)
+{
+	int sf;
+	ssize_t nr;
+	off64_t off;
+	size_t fsize;
+	char *data;
+
+	sf = open(srcp, O_RDONLY);
+	if (sf < 0)
+		return ENOENT;
+
+	off = lseek(sf, 0, SEEK_END);
+	if (off == (off64_t)-1)
+		return EIO;
+
+	fsize = (size_t)off;
+
+	off = lseek(sf, 0, SEEK_SET);
+	if (off == (off64_t)-1)
+		return EIO;
+
+	data = calloc(fsize, 1);
+	if (data == NULL)
+		return ENOMEM;
+
+	nr = read(sf, data, fsize);
+	if (nr != (ssize_t)fsize)
+		return EIO;
+
+	(void) close(sf);
+	*rdata = data;
+	*rsize = fsize;
+
+	return EOK;
+}
+
+/** @}
+ */
Index: uspace/app/sysinst/futil.h
===================================================================
--- uspace/app/sysinst/futil.h	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
+++ uspace/app/sysinst/futil.h	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2014 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 sysinst
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#ifndef FUTIL_H
+#define FUTIL_H
+
+#include <ipc/loc.h>
+#include <sys/types.h>
+
+extern int futil_copy_file(const char *, const char *);
+extern int futil_rcopy_contents(const char *, const char *);
+extern int futil_get_file(const char *, void **, size_t *);
+
+#endif
+
+/** @}
+ */
Index: uspace/app/sysinst/grub.h
===================================================================
--- uspace/app/sysinst/grub.h	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
+++ uspace/app/sysinst/grub.h	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2014 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 sysinst
+ * @{
+ */
+/**
+ * @file
+ * @brief
+ */
+
+#ifndef GRUB_H
+#define GRUB_H
+
+enum {
+	grub_boot_machine_kernel_sector = 0x5c,
+	grub_boot_machine_boot_drive = 0x64
+};
+
+enum {
+	/* 8086 segment (16*pa) where to load GRUB core image */
+	grub_boot_i386_pc_kernel_seg = 0x800
+};
+
+typedef struct {
+	uint64_t start;
+	uint16_t len;
+	uint16_t segment;
+} __attribute__ ((packed)) grub_boot_blocklist_t;
+
+#endif
+
+/** @}
+ */
Index: uspace/app/sysinst/sysinst.c
===================================================================
--- uspace/app/sysinst/sysinst.c	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
+++ uspace/app/sysinst/sysinst.c	(revision 3a34852c5ad9335d47aeef1f7d6c94e3b985fb12)
@@ -0,0 +1,391 @@
+/*
+ * Copyright (c) 2014 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 sysinst
+ * @{
+ */
+/** @file System installer.
+ *
+ * Install the operating system onto a disk device. Note that this only works
+ * on ia32/amd64 with Grub platform 'pc'.
+ */
+
+#include <block.h>
+#include <byteorder.h>
+#include <errno.h>
+#include <fdisk.h>
+#include <loc.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <task.h>
+#include <vfs/vfs.h>
+
+#include "futil.h"
+#include "grub.h"
+
+/** Device to install to
+ *
+ * Note that you cannot simply change this, because the installation
+ * device is hardcoded in core.img. If you wanted to install to another
+ * device, you must build your own core.img (e.g. using tools/grub/mkimage.sh
+ * and modifying tools/grub/load.cfg, supplying the device to boot from
+ * in Grub notation).
+ */
+#define DEFAULT_DEV "devices/\\hw\\pci0\\00:01.0\\ata-c1\\d0"
+//#define DEFAULT_DEV "devices/\\hw\\pci0\\00:01.2\\uhci_rh\\usb01_a1\\mass-storage0\\l0"
+
+/** Filysystem type. Cannot be changed without building a custom core.img */
+#define FS_TYPE "mfs"
+
+#define FS_SRV "/srv/mfs"
+#define MOUNT_POINT "/inst"
+
+/** Device containing HelenOS live CD */
+#define CD_DEV "devices/\\hw\\pci0\\00:01.0\\ata-c2\\d0"
+
+#define CD_FS_TYPE "cdfs"
+#define CD_FS_SRV "/srv/cdfs"
+#define CD_MOUNT_POINT "/cdrom"
+
+#define BOOT_FILES_SRC "/cdrom"
+#define BOOT_BLOCK_IDX 0 /* MBR */
+
+/** Label the destination device.
+ *
+ * @param dev Disk device to label
+ * @param pdev Place to store partition device name
+ *
+ * @return EOK on success or error code
+ */
+static int sysinst_label_dev(const char *dev, char **pdev)
+{
+	fdisk_t *fdisk;
+	fdisk_dev_t *fdev;
+	fdisk_part_t *part;
+	fdisk_part_spec_t pspec;
+	fdisk_cap_t cap;
+	service_id_t sid;
+	int rc;
+
+	printf("sysinst_label_dev(): get service ID '%s'\n", dev);
+	rc = loc_service_get_id(dev, &sid, 0);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_label_dev(): open device\n");
+
+	rc = fdisk_create(&fdisk);
+	if (rc != EOK) {
+		printf("Error initializing fdisk.\n");
+		return rc;
+	}
+
+	rc = fdisk_dev_open(fdisk, sid, &fdev);
+	if (rc != EOK) {
+		printf("Error opening device.\n");
+		return rc;
+	}
+
+	printf("sysinst_label_dev(): create label\n");
+
+	rc = fdisk_label_create(fdev, lt_mbr);
+	if (rc != EOK) {
+		printf("Error creating label (%d).\n", rc);
+		return rc;
+	}
+
+	printf("sysinst_label_dev(): create partition\n");
+
+	rc = fdisk_part_get_max_avail(fdev, spc_pri, &cap);
+	if (rc != EOK) {
+		printf("Error getting available capacity (%d).\n", rc);
+		return rc;
+	}
+
+	fdisk_pspec_init(&pspec);
+	pspec.capacity = cap;
+	pspec.pkind = lpk_primary;
+	pspec.fstype = fs_minix;
+
+	rc = fdisk_part_create(fdev, &pspec, &part);
+	if (rc != EOK) {
+		printf("Error creating partition.\n");
+		return rc;
+	}
+
+	/* XXX libfdisk should give us the service name */
+	rc = asprintf(pdev, "%sp1", dev);
+	if (rc < 0)
+		return ENOMEM;
+
+	printf("sysinst_label_dev(): OK\n");
+	return EOK;
+}
+
+/** Mount target file system.
+ *
+ * @param dev Partition device
+ * @return EOK on success or error code
+ */
+static int sysinst_fs_mount(const char *dev)
+{
+	task_wait_t twait;
+	task_exit_t texit;
+	int rc;
+	int trc;
+
+	printf("sysinst_fs_mount(): start filesystem server\n");
+	rc = task_spawnl(NULL, &twait, FS_SRV, FS_SRV, NULL);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_fs_mount(): wait for filesystem server\n");
+	rc = task_wait(&twait, &texit, &trc);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_fs_mount(): verify filesystem server result\n");
+	if (texit != TASK_EXIT_NORMAL || trc != 0)
+		return EIO;
+
+	rc = mkdir(MOUNT_POINT, 0);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_fs_mount(): mount filesystem\n");
+	rc = vfs_mount(FS_TYPE, MOUNT_POINT, dev, "", 0, 0);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_fs_mount(): OK\n");
+	return EOK;
+}
+
+/** Copy boot files.
+ *
+ * @return EOK on success or error code
+ */
+static int sysinst_copy_boot_files(void)
+{
+	task_wait_t twait;
+	task_exit_t texit;
+	int rc;
+	int trc;
+
+	printf("sysinst_copy_boot_files(): start filesystem server\n");
+	rc = task_spawnl(NULL, &twait, CD_FS_SRV, CD_FS_SRV, NULL);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_files(): wait for filesystem server\n");
+	rc = task_wait(&twait, &texit, &trc);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_files(): verify filesystem server result\n");
+	if (texit != TASK_EXIT_NORMAL || trc != 0)
+		return EIO;
+
+	printf("sysinst_copy_boot_files(): create CD mount point\n");
+	rc = mkdir(CD_MOUNT_POINT, 0);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_files(): mount CD filesystem\n");
+	rc = vfs_mount(CD_FS_TYPE, CD_MOUNT_POINT, CD_DEV, "", 0, 0);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_files(): copy bootloader files\n");
+	rc = futil_rcopy_contents(BOOT_FILES_SRC, MOUNT_POINT);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_files(): unmount %s\n", MOUNT_POINT);
+	rc = vfs_unmount(MOUNT_POINT);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_files(): OK\n");
+	return EOK;
+}
+
+/** Write unaligned 64-bit little-endian number.
+ *
+ * @param a Destination buffer
+ * @param data Number
+ */
+static void set_unaligned_u64le(uint8_t *a, uint64_t data)
+{
+	int i;
+
+	for (i = 0; i < 8; i++) {
+		a[i] = (data >> (i * 8)) & 0xff;
+	}
+}
+
+/** Copy boot blocks.
+ *
+ * Install Grub's boot blocks.
+ *
+ * @param devp Disk device
+ * @return EOK on success or error code
+ */
+static int sysinst_copy_boot_blocks(const char *devp)
+{
+	void *boot_img;
+	size_t boot_img_size;
+	void *core_img;
+	size_t core_img_size;
+	service_id_t sid;
+	size_t bsize;
+	uint8_t bbuf[512];
+	aoff64_t core_start;
+	aoff64_t core_blocks;
+	grub_boot_blocklist_t *first_bl, *bl;
+	int rc;
+
+	printf("sysinst_copy_boot_blocks: Read boot block image.\n");
+	rc = futil_get_file(BOOT_FILES_SRC "/boot/grub/i386-pc/boot.img",
+	    &boot_img, &boot_img_size);
+	if (rc != EOK || boot_img_size != 512)
+		return EIO;
+
+	printf("sysinst_copy_boot_blocks: Read GRUB core image.\n");
+	rc = futil_get_file(BOOT_FILES_SRC "/boot/grub/i386-pc/core.img",
+	    &core_img, &core_img_size);
+	if (rc != EOK)
+		return EIO;
+
+	printf("sysinst_copy_boot_blocks: get service ID.\n");
+	rc = loc_service_get_id(devp, &sid, 0);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_blocks: block_init.\n");
+	rc = block_init(sid, 512);
+	if (rc != EOK)
+		return rc;
+
+	printf("sysinst_copy_boot_blocks: get block size\n");
+	rc = block_get_bsize(sid, &bsize);
+	if (rc != EOK)
+		return rc;
+
+	if (bsize != 512) {
+		printf("Device block size != 512.\n");
+		return EIO;
+	}
+
+	printf("sysinst_copy_boot_blocks: read boot block\n");
+	rc = block_read_direct(sid, BOOT_BLOCK_IDX, 1, bbuf);
+	if (rc != EOK)
+		return EIO;
+
+	core_start = 16;
+	core_blocks = (core_img_size + 511) / 512;
+
+	/* Clean blocklists */
+	first_bl = core_img + 512 - sizeof(*first_bl);
+	bl = first_bl;
+	while (bl->len != 0) {
+		memset(bl, 0, sizeof(*bl));
+		--bl;
+		if ((void *)bl < core_img) {
+			printf("No block terminator in core image.\n");
+			return EIO;
+		}
+	}
+
+	first_bl->start = host2uint64_t_le(core_start + 1);
+	first_bl->len = host2uint16_t_le(core_blocks - 1);
+	first_bl->segment = grub_boot_i386_pc_kernel_seg + (512 >> 4);
+
+	/* Write boot code into boot block */
+	memcpy(bbuf, boot_img, 440); /* XXX 440 = sizeof(br_block_t.code_area) */
+	bbuf[grub_boot_machine_boot_drive] = 0xff;
+	set_unaligned_u64le(bbuf + grub_boot_machine_kernel_sector, core_start);
+
+	printf("sysinst_copy_boot_blocks: write boot block\n");
+	rc = block_write_direct(sid, BOOT_BLOCK_IDX, 1, bbuf);
+	if (rc != EOK)
+		return EIO;
+
+	printf("sysinst_copy_boot_blocks: write boot block\n");
+	/* XXX Must pad last block with zeros */
+	rc = block_write_direct(sid, core_start, core_blocks, core_img);
+	if (rc != EOK)
+		return EIO;
+
+	printf("sysinst_copy_boot_blocks: OK.\n");
+	return EOK;
+}
+
+/** Install system to a device.
+ *
+ * @param dev Device to install to.
+ * @return EOK on success or error code
+ */
+static int sysinst_install(const char *dev)
+{
+	int rc;
+	char *pdev;
+
+	rc = sysinst_label_dev(dev, &pdev);
+	if (rc != EOK)
+		return rc;
+
+	printf("Partition '%s'. Mount it.\n", pdev);
+	rc = sysinst_fs_mount(pdev);
+	if (rc != EOK)
+		return rc;
+
+	free(pdev);
+
+	printf("FS created and mounted. Copying boot files.\n");
+	rc = sysinst_copy_boot_files();
+	if (rc != EOK)
+		return rc;
+
+	printf("Boot files done. Installing boot blocks.\n");
+	rc = sysinst_copy_boot_blocks(dev);
+	if (rc != EOK)
+		return rc;
+
+	return EOK;
+}
+
+int main(int argc, char *argv[])
+{
+	const char *dev = DEFAULT_DEV;
+	return sysinst_install(dev);
+}
+
+/** @}
+ */
