Index: uspace/app/hrctl/hrctl.c
===================================================================
--- uspace/app/hrctl/hrctl.c	(revision 431b513a6e48dd61f02244447a7173fd175fa1f7)
+++ uspace/app/hrctl/hrctl.c	(revision e0bbecb7cb52d3499d94395fa598579d09a6de5d)
@@ -34,4 +34,5 @@
  */
 
+#include <capa.h>
 #include <ctype.h>
 #include <errno.h>
@@ -51,4 +52,14 @@
 static errno_t fill_config_devs(int, char **, hr_config_t *);
 static errno_t get_vol_configs_from_sif(const char *, hr_config_t **, size_t *);
+static int create_from_config(hr_t *, const char *);
+static int create_from_argv(hr_t *, int, char **);
+static int handle_create(hr_t *, int, char **);
+static int assemble_from_config(hr_t *, const char *);
+static int assemble_from_argv(hr_t *, int, char **);
+static int handle_assemble(hr_t *, int, char **);
+static int handle_disassemble(hr_t *, int, char **);
+static int handle_modify(hr_t *, int, char **);
+static errno_t print_vol_info(hr_vol_info_t *);
+static int handle_state(hr_t *, int, char **);
 
 static const char usage_str[] =
@@ -76,5 +87,5 @@
     "          -h, --hotspare device             add hotspare.\n"
     "\n"
-    "  -s, --state                               Display state of active volumes.\n"
+    "  -s, --state [volume]                      Display state of active volume(s).\n"
     "\n"
     "level can be one of:\n"
@@ -107,463 +118,4 @@
     "\t- volume name must be shorter than 32 characters\n"
     "\t- automatic assembly and disassembly on nested volumes is UNDEFINED!\n";
-
-static void usage(void)
-{
-	printf("%s", usage_str);
-}
-
-static errno_t fill_config_devs(int argc, char **argv, hr_config_t *cfg)
-{
-	errno_t rc;
-	size_t i;
-
-	for (i = 0; i < HR_MAX_EXTENTS && optind < argc; i++) {
-		rc = loc_service_get_id(argv[optind], &cfg->devs[i], 0);
-		if (rc == ENOENT) {
-			printf(NAME ": device \"%s\" not found, aborting\n",
-			    argv[optind]);
-			return ENOENT;
-		} else if (rc != EOK) {
-			printf(NAME ": error resolving device \"%s\", aborting\n",
-			    argv[optind]);
-			return EINVAL;
-		}
-		optind++;
-	}
-
-	if (optind < argc) {
-		printf(NAME ": too many devices specified, max = %u\n",
-		    HR_MAX_EXTENTS);
-		return ELIMIT;
-	}
-
-	cfg->dev_no = i;
-
-	return EOK;
-}
-
-static errno_t get_vol_configs_from_sif(const char *path, hr_config_t **rcfgs,
-    size_t *rcount)
-{
-	errno_t rc;
-	sif_doc_t *doc = NULL;
-	sif_node_t *hrconfig_node;
-	sif_node_t *root_node;
-	sif_node_t *volume_node;
-	sif_node_t *nextent;
-	const char *ntype;
-	const char *devname;
-	const char *level_str;
-	const char *extent_devname;
-	hr_config_t *vol_configs = NULL;
-
-	rc = sif_load(path, &doc);
-	if (rc != EOK)
-		goto error;
-
-	root_node = sif_get_root(doc);
-
-	hrconfig_node = sif_node_first_child(root_node);
-	ntype = sif_node_get_type(hrconfig_node);
-	if (str_cmp(ntype, "hrconfig") != 0) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	size_t vol_count = 0;
-	volume_node = sif_node_first_child(hrconfig_node);
-	while (volume_node) {
-		ntype = sif_node_get_type(volume_node);
-		if (str_cmp(ntype, "volume") != 0) {
-			rc = EINVAL;
-			goto error;
-		}
-		vol_configs = realloc(vol_configs,
-		    (vol_count + 1) * sizeof(hr_config_t));
-		if (vol_configs == NULL) {
-			rc = ENOMEM;
-			goto error;
-		}
-
-		hr_config_t *cfg = vol_configs + vol_count;
-
-		devname = sif_node_get_attr(volume_node, "devname");
-		if (devname == NULL) {
-			rc = EINVAL;
-			goto error;
-		}
-		str_cpy(cfg->devname, sizeof(cfg->devname), devname);
-
-		level_str = sif_node_get_attr(volume_node, "level");
-		if (level_str == NULL)
-			cfg->level = HR_LVL_UNKNOWN;
-		else
-			cfg->level = strtol(level_str, NULL, 10);
-
-		nextent = sif_node_first_child(volume_node);
-		size_t i = 0;
-		while (nextent && i < HR_MAX_EXTENTS) {
-			ntype = sif_node_get_type(nextent);
-			if (str_cmp(ntype, "extent") != 0) {
-				rc = EINVAL;
-				goto error;
-			}
-
-			extent_devname = sif_node_get_attr(nextent, "devname");
-			if (extent_devname == NULL) {
-				rc = EINVAL;
-				goto error;
-			}
-
-			rc = loc_service_get_id(extent_devname, &cfg->devs[i], 0);
-			if (rc == ENOENT) {
-				printf(NAME ": no device \"%s\", marking as missing\n",
-				    extent_devname);
-				cfg->devs[i] = 0;
-				rc = EOK;
-			} else if (rc != EOK) {
-				printf(NAME ": error resolving device \"%s\", aborting\n",
-				    extent_devname);
-				goto error;
-			}
-
-			nextent = sif_node_next_child(nextent);
-			i++;
-		}
-
-		if (i > HR_MAX_EXTENTS) {
-			printf(NAME ": too many devices specified in volume \"%s\", "
-			    "skipping\n", devname);
-			memset(&vol_configs[vol_count], 0, sizeof(hr_config_t));
-		} else {
-			cfg->dev_no = i;
-			vol_count++;
-		}
-
-		volume_node = sif_node_next_child(volume_node);
-	}
-
-	if (rc == EOK) {
-		if (rcount)
-			*rcount = vol_count;
-		if (rcfgs)
-			*rcfgs = vol_configs;
-	}
-error:
-	if (doc != NULL)
-		sif_delete(doc);
-	if (rc != EOK) {
-		if (vol_configs)
-			free(vol_configs);
-	}
-	return rc;
-}
-
-static int create_from_config(hr_t *hr, const char *config_path)
-{
-	hr_config_t *vol_configs = NULL;
-	size_t vol_count = 0;
-	errno_t rc = get_vol_configs_from_sif(config_path, &vol_configs,
-	    &vol_count);
-	if (rc != EOK) {
-		printf(NAME ": config parsing failed\n");
-		return EXIT_FAILURE;
-	}
-
-	for (size_t i = 0; i < vol_count; i++) {
-		rc = hr_create(hr, &vol_configs[i]);
-		if (rc != EOK) {
-			printf(NAME ": creation of volume \"%s\" failed: %s, "
-			    "but continuing\n",
-			    vol_configs[i].devname, str_error(rc));
-		} else {
-			printf(NAME ": volume \"%s\" successfully created\n",
-			    vol_configs[i].devname);
-		}
-	}
-
-	free(vol_configs);
-	return EXIT_SUCCESS;
-}
-
-static int create_from_argv(hr_t *hr, int argc, char **argv)
-{
-	/* we need name + --level + arg + at least one extent */
-	if (optind + 3 >= argc) {
-		printf(NAME ": not enough arguments\n");
-		return EXIT_FAILURE;
-	}
-
-	hr_config_t *vol_config = calloc(1, sizeof(hr_config_t));
-	if (vol_config == NULL) {
-		printf(NAME ": not enough memory\n");
-		return EXIT_FAILURE;
-	}
-
-	const char *name = argv[optind++];
-	if (str_size(name) >= HR_DEVNAME_LEN) {
-		printf(NAME ": devname must be less then 32 bytes.\n");
-		goto error;
-	}
-
-	str_cpy(vol_config->devname, HR_DEVNAME_LEN, name);
-
-	const char *level_opt = argv[optind++];
-	if (str_cmp(level_opt, "--level") != 0 &&
-	    str_cmp(level_opt, "-l") != 0) {
-		printf(NAME ": unknown option \"%s\"\n", level_opt);
-		goto error;
-	}
-
-	const char *level_str = argv[optind++];
-	if (str_size(level_str) == 1 && isdigit(level_str[0])) {
-		vol_config->level = strtol(level_str, NULL, 10);
-	} else {
-		if (str_cmp(level_str, "mirror") == 0 ||
-		    str_cmp(level_str, "mirroring") == 0) {
-			vol_config->level = HR_LVL_1;
-		} else if (str_cmp(level_str, "stripe") == 0 ||
-		    str_cmp(level_str, "striping") == 0) {
-			vol_config->level = HR_LVL_0;
-		} else if (str_cmp(level_str, "parity") == 0 ||
-		    str_cmp(level_str, "parity_distributed") == 0) {
-			vol_config->level = HR_LVL_5;
-		} else if (str_cmp(level_str, "parity_dedicated") == 0) {
-			vol_config->level = HR_LVL_4;
-		} else {
-			printf(NAME ": unknown level \"%s\"\n", level_str);
-			goto error;
-		}
-	}
-
-	errno_t rc = fill_config_devs(argc, argv, vol_config);
-	if (rc != EOK)
-		goto error;
-
-	rc = hr_create(hr, vol_config);
-	if (rc != EOK) {
-		printf(NAME ": creation failed: %s\n", str_error(rc));
-		goto error;
-	} else {
-		printf(NAME ": volume \"%s\" successfully created\n",
-		    vol_config->devname);
-	}
-
-	free(vol_config);
-	return EXIT_SUCCESS;
-error:
-	free(vol_config);
-	return EXIT_FAILURE;
-}
-
-static int handle_create(hr_t *hr, int argc, char **argv)
-{
-	int rc;
-
-	if (optind >= argc) {
-		printf(NAME ": no arguments to --create\n");
-		return EXIT_FAILURE;
-	}
-
-	if (str_cmp(argv[optind], "-f") == 0) {
-		optind++;
-		if (optind >= argc) {
-			printf(NAME ": not enough arguments\n");
-			return EXIT_FAILURE;
-		}
-
-		const char *config_path = argv[optind++];
-
-		if (optind < argc) {
-			printf(NAME ": unexpected arguments\n");
-			return EXIT_FAILURE;
-		}
-
-		rc = create_from_config(hr, config_path);
-	} else {
-		rc = create_from_argv(hr, argc, argv);
-	}
-
-	return rc;
-}
-
-static int assemble_from_config(hr_t *hr, const char *config_path)
-{
-	hr_config_t *vol_configs = NULL;
-	size_t vol_count = 0;
-	errno_t rc = get_vol_configs_from_sif(config_path, &vol_configs,
-	    &vol_count);
-	if (rc != EOK) {
-		printf(NAME ": config parsing failed\n");
-		return EXIT_FAILURE;
-	}
-
-	size_t cnt = 0;
-	for (size_t i = 0; i < vol_count; i++) {
-		size_t tmpcnt = 0;
-		(void)hr_assemble(hr, &vol_configs[i], &tmpcnt);
-		cnt += tmpcnt;
-	}
-
-	printf(NAME ": assembled %zu volumes\n", cnt);
-
-	free(vol_configs);
-	return EXIT_SUCCESS;
-}
-
-static int assemble_from_argv(hr_t *hr, int argc, char **argv)
-{
-	hr_config_t *vol_config = calloc(1, sizeof(hr_config_t));
-	if (vol_config == NULL) {
-		printf(NAME ": not enough memory\n");
-		return ENOMEM;
-	}
-
-	errno_t rc = fill_config_devs(argc, argv, vol_config);
-	if (rc != EOK)
-		goto error;
-
-	size_t cnt;
-	rc = hr_assemble(hr, vol_config, &cnt);
-	if (rc != EOK) {
-		printf(NAME ": assmeble failed: %s\n", str_error(rc));
-		goto error;
-	}
-
-	printf("hrctl: assembled %zu volumes\n", cnt);
-
-	free(vol_config);
-	return EXIT_SUCCESS;
-error:
-	free(vol_config);
-	return EXIT_FAILURE;
-}
-
-static int handle_assemble(hr_t *hr, int argc, char **argv)
-{
-	int rc;
-
-	if (optind >= argc) {
-		size_t cnt;
-		errno_t rc = hr_auto_assemble(hr, &cnt);
-		if (rc != EOK) {
-			/* XXX: here have own error codes */
-			printf("hrctl: auto assemble rc: %s\n", str_error(rc));
-			return EXIT_FAILURE;
-		}
-
-		printf(NAME ": auto assembled %zu volumes\n", cnt);
-		return EXIT_SUCCESS;
-	}
-
-	if (str_cmp(argv[optind], "-f") == 0) {
-		if (++optind >= argc) {
-			printf(NAME ": not enough arguments\n");
-			return EXIT_FAILURE;
-		}
-		const char *config_path = argv[optind++];
-
-		if (optind < argc) {
-			printf(NAME ": unexpected arguments\n");
-			return EXIT_FAILURE;
-		}
-
-		rc = assemble_from_config(hr, config_path);
-	} else {
-		rc = assemble_from_argv(hr, argc, argv);
-	}
-
-	return rc;
-}
-
-static int handle_disassemble(hr_t *hr, int argc, char **argv)
-{
-	if (optind >= argc) {
-		errno_t rc = hr_stop_all(hr);
-		if (rc != EOK) {
-			printf(NAME ": stopping some volumes failed: %s\n",
-			    str_error(rc));
-			return EXIT_FAILURE;
-		}
-		return EXIT_SUCCESS;
-	}
-
-	if (optind + 1 < argc) {
-		printf(NAME ": only 1 device can be manually specified\n");
-		return EXIT_FAILURE;
-	}
-
-	const char *devname = argv[optind++];
-
-	errno_t rc = hr_stop(hr, devname);
-	if (rc != EOK) {
-		printf(NAME ": disassembly of device \"%s\" failed: %s\n",
-		    devname, str_error(rc));
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int handle_modify(hr_t *hr, int argc, char **argv)
-{
-	if (optind >= argc) {
-		printf(NAME ": no arguments to --modify\n");
-		return EXIT_FAILURE;
-	}
-
-	const char *volname = argv[optind++];
-
-	/* at least 1 option and its agument */
-	if (optind + 1 >= argc) {
-		printf(NAME ": not enough arguments\n");
-		return EXIT_FAILURE;
-	}
-
-	if (optind + 2 < argc) {
-		printf(NAME ": unexpected arguments\n");
-		return EXIT_FAILURE;
-	}
-
-	if (str_cmp(argv[optind], "--fail") == 0 ||
-	    str_cmp(argv[optind], "-f") == 0) {
-		optind++;
-		unsigned long extent = strtol(argv[optind++], NULL, 10);
-		errno_t rc = hr_fail_extent(hr, volname, extent);
-		if (rc != EOK) {
-			printf(NAME ": failing extent failed: %s\n",
-			    str_error(rc));
-			return EXIT_FAILURE;
-		}
-	} else if (str_cmp(argv[optind], "--hotspare") == 0 ||
-	    str_cmp(argv[optind], "-h") == 0) {
-		optind++;
-		errno_t rc = hr_add_hotspare(hr, volname, argv[optind++]);
-		if (rc != EOK) {
-			printf(NAME ": adding hotspare failed: %s\n",
-			    str_error(rc));
-			return EXIT_FAILURE;
-		}
-	} else {
-		printf(NAME ": unknown argument\n");
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
-
-static int handle_state(hr_t *hr, int argc, char **argv)
-{
-	(void)argc;
-	(void)argv;
-
-	errno_t rc = hr_print_state(hr);
-	if (rc != EOK) {
-		printf(NAME ": state printing failed: %s\n", str_error(rc));
-		return EXIT_FAILURE;
-	}
-
-	return EXIT_SUCCESS;
-}
 
 int main(int argc, char **argv)
@@ -629,4 +181,610 @@
 }
 
+static void usage(void)
+{
+	printf("%s", usage_str);
+}
+
+static errno_t fill_config_devs(int argc, char **argv, hr_config_t *cfg)
+{
+	errno_t rc;
+	size_t i;
+
+	for (i = 0; i < HR_MAX_EXTENTS && optind < argc; i++) {
+		rc = loc_service_get_id(argv[optind], &cfg->devs[i], 0);
+		if (rc == ENOENT) {
+			printf(NAME ": device \"%s\" not found, aborting\n",
+			    argv[optind]);
+			return ENOENT;
+		} else if (rc != EOK) {
+			printf(NAME ": error resolving device \"%s\", aborting\n",
+			    argv[optind]);
+			return EINVAL;
+		}
+		optind++;
+	}
+
+	if (optind < argc) {
+		printf(NAME ": too many devices specified, max = %u\n",
+		    HR_MAX_EXTENTS);
+		return ELIMIT;
+	}
+
+	cfg->dev_no = i;
+
+	return EOK;
+}
+
+static errno_t get_vol_configs_from_sif(const char *path, hr_config_t **rcfgs,
+    size_t *rcount)
+{
+	errno_t rc;
+	sif_doc_t *doc = NULL;
+	sif_node_t *hrconfig_node;
+	sif_node_t *root_node;
+	sif_node_t *volume_node;
+	sif_node_t *nextent;
+	const char *ntype;
+	const char *devname;
+	const char *level_str;
+	const char *extent_devname;
+	hr_config_t *vol_configs = NULL;
+
+	rc = sif_load(path, &doc);
+	if (rc != EOK)
+		goto error;
+
+	root_node = sif_get_root(doc);
+
+	hrconfig_node = sif_node_first_child(root_node);
+	ntype = sif_node_get_type(hrconfig_node);
+	if (str_cmp(ntype, "hrconfig") != 0) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	size_t vol_count = 0;
+	volume_node = sif_node_first_child(hrconfig_node);
+	while (volume_node) {
+		ntype = sif_node_get_type(volume_node);
+		if (str_cmp(ntype, "volume") != 0) {
+			rc = EINVAL;
+			goto error;
+		}
+		vol_configs = realloc(vol_configs,
+		    (vol_count + 1) * sizeof(hr_config_t));
+		if (vol_configs == NULL) {
+			rc = ENOMEM;
+			goto error;
+		}
+
+		hr_config_t *cfg = vol_configs + vol_count;
+
+		devname = sif_node_get_attr(volume_node, "devname");
+		if (devname == NULL) {
+			rc = EINVAL;
+			goto error;
+		}
+		str_cpy(cfg->devname, sizeof(cfg->devname), devname);
+
+		level_str = sif_node_get_attr(volume_node, "level");
+		if (level_str == NULL)
+			cfg->level = HR_LVL_UNKNOWN;
+		else
+			cfg->level = strtol(level_str, NULL, 10);
+
+		nextent = sif_node_first_child(volume_node);
+		size_t i = 0;
+		while (nextent && i < HR_MAX_EXTENTS) {
+			ntype = sif_node_get_type(nextent);
+			if (str_cmp(ntype, "extent") != 0) {
+				rc = EINVAL;
+				goto error;
+			}
+
+			extent_devname = sif_node_get_attr(nextent, "devname");
+			if (extent_devname == NULL) {
+				rc = EINVAL;
+				goto error;
+			}
+
+			rc = loc_service_get_id(extent_devname, &cfg->devs[i], 0);
+			if (rc == ENOENT) {
+				printf(NAME ": no device \"%s\", marking as missing\n",
+				    extent_devname);
+				cfg->devs[i] = 0;
+				rc = EOK;
+			} else if (rc != EOK) {
+				printf(NAME ": error resolving device \"%s\", aborting\n",
+				    extent_devname);
+				goto error;
+			}
+
+			nextent = sif_node_next_child(nextent);
+			i++;
+		}
+
+		if (i > HR_MAX_EXTENTS) {
+			printf(NAME ": too many devices specified in volume \"%s\", "
+			    "skipping\n", devname);
+			memset(&vol_configs[vol_count], 0, sizeof(hr_config_t));
+		} else {
+			cfg->dev_no = i;
+			vol_count++;
+		}
+
+		volume_node = sif_node_next_child(volume_node);
+	}
+
+	if (rc == EOK) {
+		if (rcount)
+			*rcount = vol_count;
+		if (rcfgs)
+			*rcfgs = vol_configs;
+	}
+error:
+	if (doc != NULL)
+		sif_delete(doc);
+	if (rc != EOK) {
+		if (vol_configs)
+			free(vol_configs);
+	}
+	return rc;
+}
+
+static int create_from_config(hr_t *hr, const char *config_path)
+{
+	hr_config_t *vol_configs = NULL;
+	size_t vol_count = 0;
+	errno_t rc = get_vol_configs_from_sif(config_path, &vol_configs,
+	    &vol_count);
+	if (rc != EOK) {
+		printf(NAME ": config parsing failed\n");
+		return EXIT_FAILURE;
+	}
+
+	for (size_t i = 0; i < vol_count; i++) {
+		rc = hr_create(hr, &vol_configs[i]);
+		if (rc != EOK) {
+			printf(NAME ": creation of volume \"%s\" failed: %s, "
+			    "but continuing\n",
+			    vol_configs[i].devname, str_error(rc));
+		} else {
+			printf(NAME ": volume \"%s\" successfully created\n",
+			    vol_configs[i].devname);
+		}
+	}
+
+	free(vol_configs);
+	return EXIT_SUCCESS;
+}
+
+static int create_from_argv(hr_t *hr, int argc, char **argv)
+{
+	/* we need name + --level + arg + at least one extent */
+	if (optind + 3 >= argc) {
+		printf(NAME ": not enough arguments\n");
+		return EXIT_FAILURE;
+	}
+
+	hr_config_t *vol_config = calloc(1, sizeof(hr_config_t));
+	if (vol_config == NULL) {
+		printf(NAME ": not enough memory\n");
+		return EXIT_FAILURE;
+	}
+
+	const char *name = argv[optind++];
+	if (str_size(name) >= HR_DEVNAME_LEN) {
+		printf(NAME ": devname must be less then 32 bytes.\n");
+		goto error;
+	}
+
+	str_cpy(vol_config->devname, HR_DEVNAME_LEN, name);
+
+	const char *level_opt = argv[optind++];
+	if (str_cmp(level_opt, "--level") != 0 &&
+	    str_cmp(level_opt, "-l") != 0) {
+		printf(NAME ": unknown option \"%s\"\n", level_opt);
+		goto error;
+	}
+
+	const char *level_str = argv[optind++];
+	if (str_size(level_str) == 1 && isdigit(level_str[0])) {
+		vol_config->level = strtol(level_str, NULL, 10);
+	} else {
+		if (str_cmp(level_str, "mirror") == 0 ||
+		    str_cmp(level_str, "mirroring") == 0) {
+			vol_config->level = HR_LVL_1;
+		} else if (str_cmp(level_str, "stripe") == 0 ||
+		    str_cmp(level_str, "striping") == 0) {
+			vol_config->level = HR_LVL_0;
+		} else if (str_cmp(level_str, "parity") == 0 ||
+		    str_cmp(level_str, "parity_distributed") == 0) {
+			vol_config->level = HR_LVL_5;
+		} else if (str_cmp(level_str, "parity_dedicated") == 0) {
+			vol_config->level = HR_LVL_4;
+		} else {
+			printf(NAME ": unknown level \"%s\"\n", level_str);
+			goto error;
+		}
+	}
+
+	errno_t rc = fill_config_devs(argc, argv, vol_config);
+	if (rc != EOK)
+		goto error;
+
+	rc = hr_create(hr, vol_config);
+	if (rc != EOK) {
+		printf(NAME ": creation failed: %s\n", str_error(rc));
+		goto error;
+	} else {
+		printf(NAME ": volume \"%s\" successfully created\n",
+		    vol_config->devname);
+	}
+
+	free(vol_config);
+	return EXIT_SUCCESS;
+error:
+	free(vol_config);
+	return EXIT_FAILURE;
+}
+
+static int handle_create(hr_t *hr, int argc, char **argv)
+{
+	int rc;
+
+	if (optind >= argc) {
+		printf(NAME ": no arguments to --create\n");
+		return EXIT_FAILURE;
+	}
+
+	if (str_cmp(argv[optind], "-f") == 0) {
+		optind++;
+		if (optind >= argc) {
+			printf(NAME ": not enough arguments\n");
+			return EXIT_FAILURE;
+		}
+
+		const char *config_path = argv[optind++];
+
+		if (optind < argc) {
+			printf(NAME ": unexpected arguments\n");
+			return EXIT_FAILURE;
+		}
+
+		rc = create_from_config(hr, config_path);
+	} else {
+		rc = create_from_argv(hr, argc, argv);
+	}
+
+	return rc;
+}
+
+static int assemble_from_config(hr_t *hr, const char *config_path)
+{
+	hr_config_t *vol_configs = NULL;
+	size_t vol_count = 0;
+	errno_t rc = get_vol_configs_from_sif(config_path, &vol_configs,
+	    &vol_count);
+	if (rc != EOK) {
+		printf(NAME ": config parsing failed\n");
+		return EXIT_FAILURE;
+	}
+
+	size_t cnt = 0;
+	for (size_t i = 0; i < vol_count; i++) {
+		size_t tmpcnt = 0;
+		(void)hr_assemble(hr, &vol_configs[i], &tmpcnt);
+		cnt += tmpcnt;
+	}
+
+	printf(NAME ": assembled %zu volumes\n", cnt);
+
+	free(vol_configs);
+	return EXIT_SUCCESS;
+}
+
+static int assemble_from_argv(hr_t *hr, int argc, char **argv)
+{
+	hr_config_t *vol_config = calloc(1, sizeof(hr_config_t));
+	if (vol_config == NULL) {
+		printf(NAME ": not enough memory\n");
+		return ENOMEM;
+	}
+
+	errno_t rc = fill_config_devs(argc, argv, vol_config);
+	if (rc != EOK)
+		goto error;
+
+	size_t cnt;
+	rc = hr_assemble(hr, vol_config, &cnt);
+	if (rc != EOK) {
+		printf(NAME ": assmeble failed: %s\n", str_error(rc));
+		goto error;
+	}
+
+	printf("hrctl: assembled %zu volumes\n", cnt);
+
+	free(vol_config);
+	return EXIT_SUCCESS;
+error:
+	free(vol_config);
+	return EXIT_FAILURE;
+}
+
+static int handle_assemble(hr_t *hr, int argc, char **argv)
+{
+	int rc;
+
+	if (optind >= argc) {
+		size_t cnt;
+		errno_t rc = hr_auto_assemble(hr, &cnt);
+		if (rc != EOK) {
+			/* XXX: here have own error codes */
+			printf("hrctl: auto assemble rc: %s\n", str_error(rc));
+			return EXIT_FAILURE;
+		}
+
+		printf(NAME ": auto assembled %zu volumes\n", cnt);
+		return EXIT_SUCCESS;
+	}
+
+	if (str_cmp(argv[optind], "-f") == 0) {
+		if (++optind >= argc) {
+			printf(NAME ": not enough arguments\n");
+			return EXIT_FAILURE;
+		}
+		const char *config_path = argv[optind++];
+
+		if (optind < argc) {
+			printf(NAME ": unexpected arguments\n");
+			return EXIT_FAILURE;
+		}
+
+		rc = assemble_from_config(hr, config_path);
+	} else {
+		rc = assemble_from_argv(hr, argc, argv);
+	}
+
+	return rc;
+}
+
+static int handle_disassemble(hr_t *hr, int argc, char **argv)
+{
+	if (optind >= argc) {
+		errno_t rc = hr_stop_all(hr);
+		if (rc != EOK) {
+			printf(NAME ": stopping some volumes failed: %s\n",
+			    str_error(rc));
+			return EXIT_FAILURE;
+		}
+		return EXIT_SUCCESS;
+	}
+
+	if (optind + 1 < argc) {
+		printf(NAME ": only 1 device can be manually specified\n");
+		return EXIT_FAILURE;
+	}
+
+	const char *devname = argv[optind++];
+
+	errno_t rc = hr_stop(hr, devname);
+	if (rc != EOK) {
+		printf(NAME ": disassembly of device \"%s\" failed: %s\n",
+		    devname, str_error(rc));
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static int handle_modify(hr_t *hr, int argc, char **argv)
+{
+	if (optind >= argc) {
+		printf(NAME ": no arguments to --modify\n");
+		return EXIT_FAILURE;
+	}
+
+	const char *volname = argv[optind++];
+
+	/* at least 1 option and its agument */
+	if (optind + 1 >= argc) {
+		printf(NAME ": not enough arguments\n");
+		return EXIT_FAILURE;
+	}
+
+	if (optind + 2 < argc) {
+		printf(NAME ": unexpected arguments\n");
+		return EXIT_FAILURE;
+	}
+
+	if (str_cmp(argv[optind], "--fail") == 0 ||
+	    str_cmp(argv[optind], "-f") == 0) {
+		optind++;
+		unsigned long extent = strtol(argv[optind++], NULL, 10);
+		errno_t rc = hr_fail_extent(hr, volname, extent);
+		if (rc != EOK) {
+			printf(NAME ": failing extent failed: %s\n",
+			    str_error(rc));
+			return EXIT_FAILURE;
+		}
+	} else if (str_cmp(argv[optind], "--hotspare") == 0 ||
+	    str_cmp(argv[optind], "-h") == 0) {
+		optind++;
+		errno_t rc = hr_add_hotspare(hr, volname, argv[optind++]);
+		if (rc != EOK) {
+			printf(NAME ": adding hotspare failed: %s\n",
+			    str_error(rc));
+			return EXIT_FAILURE;
+		}
+	} else {
+		printf(NAME ": unknown argument\n");
+		return EXIT_FAILURE;
+	}
+
+	return EXIT_SUCCESS;
+}
+
+static errno_t print_vol_info(hr_vol_info_t *info)
+{
+	errno_t rc;
+	size_t i;
+	hr_extent_t *ext;
+	const char *devname;
+
+	printf("volume: \"%s\" (%" PRIun ")\n", info->devname, info->svc_id);
+
+	printf("|   metadata type: %s\n",
+	    hr_get_metadata_type_str(info->meta_type));
+	printf("|           level: %s\n", hr_get_level_str(info->level));
+	if (info->layout != HR_RLQ_NONE)
+		printf("|          layout: %s\n",
+		    hr_get_layout_str(info->layout));
+
+	if (info->strip_size > 0) {
+		if (info->strip_size < 1024) {
+			printf("|      strip size: %" PRIu32 "B\n",
+			    info->strip_size);
+		} else {
+			printf("|      strip size: %" PRIu32 "KiB\n",
+			    info->strip_size / 1024);
+		}
+	}
+
+	printf("|  no. of extents: %zu\n", info->extent_no);
+	printf("|no. of hotspares: %zu\n", info->hotspare_no);
+	printf("|number of blocks: %" PRIu64 "\n", info->data_blkno);
+	printf("|      block size: %zuB\n", info->bsize);
+
+	capa_spec_t capa;
+	char *scapa = NULL;
+	capa_from_blocks(info->data_blkno, info->bsize, &capa);
+	capa_simplify(&capa);
+	rc = capa_format(&capa, &scapa);
+	if (rc != EOK) {
+		printf(NAME ": failed to format capacity: %s\n", str_error(rc));
+		return rc;
+	}
+
+	printf("| volume capacity: %s\n", scapa);
+
+	free(scapa);
+
+	printf("|           state: %s\n", hr_get_vol_state_str(info->state));
+	printf("|         extents:\n");
+
+	for (i = 0; i < info->extent_no; i++) {
+		ext = &info->extents[i];
+		char *tmpname = NULL;
+		if (ext->state == HR_EXT_MISSING || ext->state == HR_EXT_NONE) {
+			devname = "MISSING";
+		} else {
+			rc = loc_service_get_name(ext->svc_id, &tmpname);
+			if (rc != EOK)
+				devname = "MISSING";
+			else
+				devname = tmpname;
+		}
+		printf("|                  %zu %s\n", i, hr_get_ext_state_str(ext->state));
+		printf("|                      %s\n", devname);
+		if (tmpname != NULL)
+			free(tmpname);
+	}
+
+	if (info->hotspare_no == 0)
+		return EOK;
+
+	printf("|       hotspares:\n");
+	for (i = 0; i < info->hotspare_no; i++) {
+		ext = &info->hotspares[i];
+		char *tmpname;
+		if (ext->state == HR_EXT_MISSING || ext->state == HR_EXT_NONE) {
+			devname = "MISSING";
+		} else {
+			rc = loc_service_get_name(ext->svc_id, &tmpname);
+			if (rc != EOK)
+				devname = "MISSING";
+			else
+				devname = tmpname;
+		}
+		printf("|                  %zu %s\n", i, hr_get_ext_state_str(ext->state));
+		printf("|                      %s\n", devname);
+		if (tmpname != NULL)
+			free(tmpname);
+	}
+
+	return EOK;
+}
+
+static int handle_state(hr_t *hr, int argc, char **argv)
+{
+	errno_t rc;
+	size_t cnt;
+	hr_pair_vol_state_t *pairs = NULL;
+	char *devname;
+
+	/* print state of all volumes */
+	if (optind >= argc) {
+		rc = hr_get_vol_states(hr, &pairs, &cnt);
+		if (rc != EOK) {
+			printf(NAME ": failed getting state of volumes: %s\n",
+			    str_error(rc));
+			return EXIT_FAILURE;
+		}
+
+		if (cnt == 0) {
+			printf(NAME ": no active volumes\n");
+			return EXIT_SUCCESS;
+		}
+
+		for (size_t i = 0; i < cnt; i++) {
+			service_id_t svc_id = pairs[i].svc_id;
+			hr_vol_state_t state = pairs[i].state;
+			rc = loc_service_get_name(svc_id, &devname);
+			if (rc != EOK) {
+				printf(NAME ": getting service name failed: "
+				    "%s\n", str_error(rc));
+				return EXIT_FAILURE;
+			}
+			printf("volume \"%s\" (%" PRIun ") %s\n", devname,
+			    svc_id, hr_get_vol_state_str(state));
+
+			free(devname);
+		}
+		free(pairs);
+
+		return EXIT_SUCCESS;
+	}
+
+	/* print volume info of requested volumes */
+	while (optind < argc) {
+		service_id_t svc_id;
+		devname = argv[optind++];
+		rc = loc_service_get_id(devname, &svc_id, 0);
+		if (rc != EOK) {
+			printf(NAME ": getting service id of \"%s\" failed: "
+			    "%s\n", devname, str_error(rc));
+			return EXIT_FAILURE;
+		}
+
+		hr_vol_info_t info;
+		rc = hr_get_vol_info(hr, svc_id, &info);
+		if (rc != EOK) {
+			printf(NAME ": getting volume info failed: %s\n",
+			    str_error(rc));
+			return EXIT_FAILURE;
+		}
+
+		rc = print_vol_info(&info);
+		if (rc != EOK) {
+			printf(NAME ": volume info printing failed: %s\n",
+			    str_error(rc));
+			return EXIT_FAILURE;
+		}
+	}
+
+	return EXIT_SUCCESS;
+}
+
 /** @}
  */
Index: uspace/app/hrctl/meson.build
===================================================================
--- uspace/app/hrctl/meson.build	(revision 431b513a6e48dd61f02244447a7173fd175fa1f7)
+++ uspace/app/hrctl/meson.build	(revision e0bbecb7cb52d3499d94395fa598579d09a6de5d)
@@ -1,4 +1,4 @@
 #
-# Copyright (c) 2024 Miroslav Cimerman
+# Copyright (c) 2025 Miroslav Cimerman
 # All rights reserved.
 #
Index: uspace/lib/device/include/hr.h
===================================================================
--- uspace/lib/device/include/hr.h	(revision 431b513a6e48dd61f02244447a7173fd175fa1f7)
+++ uspace/lib/device/include/hr.h	(revision e0bbecb7cb52d3499d94395fa598579d09a6de5d)
@@ -82,4 +82,12 @@
 } hr_ext_state_t;
 
+typedef enum {
+	HR_METADATA_NATIVE = 0,
+	HR_METADATA_GEOM_MIRROR,
+	HR_METADATA_GEOM_STRIPE,
+	HR_METADATA_SOFTRAID,
+	HR_METADATA_LAST_DUMMY
+} hr_metadata_type_t;
+
 typedef struct hr {
 	async_sess_t *sess;
@@ -98,25 +106,25 @@
 } hr_extent_t;
 
+typedef struct hr_pair_vol_state {
+	service_id_t svc_id;
+	hr_vol_state_t state;
+} hr_pair_vol_state_t;
+
 typedef struct hr_vol_info {
+	char devname[HR_DEVNAME_LEN];
+	service_id_t svc_id;
+	hr_level_t level;
 	hr_extent_t extents[HR_MAX_EXTENTS];
 	hr_extent_t hotspares[HR_MAX_HOTSPARES];
 	size_t extent_no;
 	size_t hotspare_no;
-	service_id_t svc_id;
-	hr_level_t level;
-	uint64_t nblocks;
+	uint64_t data_blkno;
 	uint32_t strip_size;
 	size_t bsize;
 	hr_vol_state_t state;
-	uint8_t layout;
+	hr_layout_t layout;
+	hr_metadata_type_t meta_type;
+	/* TODO: add rebuild pos */
 } hr_vol_info_t;
-
-typedef enum {
-	HR_METADATA_NATIVE = 0,
-	HR_METADATA_GEOM_MIRROR,
-	HR_METADATA_GEOM_STRIPE,
-	HR_METADATA_SOFTRAID,
-	HR_METADATA_LAST_DUMMY
-} hr_metadata_type_t;
 
 extern errno_t hr_sess_init(hr_t **);
@@ -129,8 +137,10 @@
 extern errno_t hr_fail_extent(hr_t *, const char *, unsigned long);
 extern errno_t hr_add_hotspare(hr_t *, const char *, const char *);
-extern errno_t hr_print_state(hr_t *);
+extern errno_t hr_get_vol_states(hr_t *, hr_pair_vol_state_t **, size_t *);
+extern errno_t hr_get_vol_info(hr_t *, service_id_t, hr_vol_info_t *);
 extern const char *hr_get_vol_state_str(hr_vol_state_t);
 extern const char *hr_get_ext_state_str(hr_ext_state_t);
 extern const char *hr_get_layout_str(hr_layout_t);
+extern const char *hr_get_level_str(hr_level_t);
 extern const char *hr_get_metadata_type_str(hr_metadata_type_t);
 
Index: uspace/lib/device/include/ipc/hr.h
===================================================================
--- uspace/lib/device/include/ipc/hr.h	(revision 431b513a6e48dd61f02244447a7173fd175fa1f7)
+++ uspace/lib/device/include/ipc/hr.h	(revision e0bbecb7cb52d3499d94395fa598579d09a6de5d)
@@ -46,5 +46,6 @@
 	HR_FAIL_EXTENT,
 	HR_ADD_HOTSPARE,
-	HR_STATUS
+	HR_GET_VOL_STATES,
+	HR_GET_VOL_INFO
 } hr_request_t;
 
Index: uspace/lib/device/src/hr.c
===================================================================
--- uspace/lib/device/src/hr.c	(revision 431b513a6e48dd61f02244447a7173fd175fa1f7)
+++ uspace/lib/device/src/hr.c	(revision e0bbecb7cb52d3499d94395fa598579d09a6de5d)
@@ -209,87 +209,4 @@
 }
 
-static errno_t print_vol_info(size_t index, hr_vol_info_t *vol_info)
-{
-	errno_t rc;
-	size_t i;
-	char *devname;
-	hr_extent_t *ext;
-
-	printf("--- vol %zu ---\n", index);
-
-	printf("svc_id: %" PRIun "\n", vol_info->svc_id);
-
-	rc = loc_service_get_name(vol_info->svc_id, &devname);
-	if (rc != EOK)
-		return rc;
-	printf("devname: %s\n", devname);
-
-	printf("state: %s\n", hr_get_vol_state_str(vol_info->state));
-
-	printf("level: %d\n", vol_info->level);
-	if (vol_info->level == HR_LVL_4 || vol_info->level == HR_LVL_5) {
-		printf("layout: %s\n",
-		    hr_get_layout_str(vol_info->layout));
-	}
-	if (vol_info->level == HR_LVL_0 || vol_info->level == HR_LVL_4) {
-		if (vol_info->strip_size / 1024 < 1)
-			printf("strip size in bytes: %" PRIu32 "\n",
-			    vol_info->strip_size);
-		else
-			printf("strip size: %" PRIu32 "K\n",
-			    vol_info->strip_size / 1024);
-	}
-	printf("size in bytes: %" PRIu64 "MiB\n",
-	    vol_info->nblocks * vol_info->bsize / 1024 / 1024);
-	printf("size in blocks: %" PRIu64 "\n", vol_info->nblocks);
-	printf("block size: %zu\n", vol_info->bsize);
-
-	if (vol_info->level == HR_LVL_4)
-		printf("extents: [P] [state] [index] [devname]\n");
-	else
-		printf("extents: [state] [index] [devname]\n");
-	for (i = 0; i < vol_info->extent_no; i++) {
-		ext = &vol_info->extents[i];
-		if (ext->state == HR_EXT_MISSING || ext->state == HR_EXT_NONE) {
-			devname = (char *) "MISSING-devname";
-		} else {
-			rc = loc_service_get_name(ext->svc_id, &devname);
-			if (rc != EOK) {
-				printf("loc_service_get_name() failed, skipping...\n");
-				continue;
-			}
-		}
-		if (vol_info->level == HR_LVL_4) {
-			if ((i == 0 && vol_info->layout == HR_RLQ_RAID4_0) ||
-			    (i == vol_info->extent_no - 1 &&
-			    vol_info->layout == HR_RLQ_RAID4_N))
-				printf("          P   %s    %zu       %s\n", hr_get_ext_state_str(ext->state), i, devname);
-			else
-				printf("              %s    %zu       %s\n", hr_get_ext_state_str(ext->state), i, devname);
-		} else {
-			printf("          %s    %zu       %s\n", hr_get_ext_state_str(ext->state), i, devname);
-		}
-	}
-
-	if (vol_info->hotspare_no == 0)
-		return EOK;
-
-	printf("hotspares: [state] [index] [devname]\n");
-	for (i = 0; i < vol_info->hotspare_no; i++) {
-		ext = &vol_info->hotspares[i];
-		if (ext->state == HR_EXT_MISSING) {
-			devname = (char *) "MISSING-devname";
-		} else {
-			rc = loc_service_get_name(ext->svc_id, &devname);
-			if (rc != EOK)
-				return rc;
-		}
-		printf("            %s   %zu     %s\n",
-		    hr_get_ext_state_str(ext->state), i, devname);
-	}
-
-	return EOK;
-}
-
 /** Stop/deactivate volume.
  *
@@ -408,34 +325,36 @@
 }
 
-/** Print state of volumes.
- *
- * @param hr	Server session
- *
- * @return EOK on success or an error code
- */
-errno_t hr_print_state(hr_t *hr)
+/** Get state of volumes.
+ *
+ * @param hr		Server session
+ * @param rpairs	Place to store pointer to (service id, vol state) pairs
+ * @param rcnt		Place to store pair count
+ *
+ * @return EOK on success or an error code
+ */
+errno_t hr_get_vol_states(hr_t *hr, hr_pair_vol_state_t **rpairs, size_t *rcnt)
 {
 	errno_t rc, retval;
 	async_exch_t *exch;
 	aid_t req;
-	size_t size, i;
-	hr_vol_info_t *vols = NULL;
-
-	exch = async_exchange_begin(hr->sess);
-	if (exch == NULL) {
-		rc = EINVAL;
-		goto error;
-	}
-
-	req = async_send_0(exch, HR_STATUS, NULL);
-	rc = async_data_read_start(exch, &size, sizeof(size_t));
-	if (rc != EOK) {
-		async_exchange_end(exch);
-		async_forget(req);
-		return rc;
-	}
-
-	vols = calloc(size, sizeof(hr_vol_info_t));
-	if (vols == NULL) {
+	size_t cnt, i;
+	hr_pair_vol_state_t *pairs = NULL;
+
+	exch = async_exchange_begin(hr->sess);
+	if (exch == NULL) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	req = async_send_0(exch, HR_GET_VOL_STATES, NULL);
+	rc = async_data_read_start(exch, &cnt, sizeof(size_t));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	pairs = calloc(cnt, sizeof(*pairs));
+	if (pairs == NULL) {
 		async_exchange_end(exch);
 		async_forget(req);
@@ -443,7 +362,6 @@
 	}
 
-	for (i = 0; i < size; i++) {
-		rc = async_data_read_start(exch, &vols[i],
-		    sizeof(hr_vol_info_t));
+	for (i = 0; i < cnt; i++) {
+		rc = async_data_read_start(exch, &pairs[i], sizeof(*pairs));
 		if (rc != EOK) {
 			async_exchange_end(exch);
@@ -460,18 +378,58 @@
 	}
 
-	if (size == 0) {
-		printf("no active volumes\n");
-		goto error;
-	}
-
-	for (i = 0; i < size; i++) {
-		rc = print_vol_info(i, &vols[i]);
-		if (rc != EOK)
-			goto error;
-	}
-
-error:
-	if (vols != NULL)
-		free(vols);
+	if (rpairs != NULL)
+		*rpairs = pairs;
+	if (rcnt != NULL)
+		*rcnt = cnt;
+	return EOK;
+
+error:
+	if (pairs != NULL)
+		free(pairs);
+	return rc;
+}
+
+/** Get volume info.
+ *
+ * @param hr		Server session
+ * @param svc_id	Service id of volume
+ * @param rinfo		Place to store volume info
+ *
+ * @return EOK on success or an error code
+ */
+errno_t hr_get_vol_info(hr_t *hr, service_id_t svc_id, hr_vol_info_t *rinfo)
+{
+	errno_t rc, retval;
+	async_exch_t *exch;
+	aid_t req;
+
+	exch = async_exchange_begin(hr->sess);
+	if (exch == NULL) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	req = async_send_0(exch, HR_GET_VOL_INFO, NULL);
+	rc = async_data_write_start(exch, &svc_id, sizeof(svc_id));
+	if (rc != EOK) {
+		async_exchange_end(exch);
+		async_forget(req);
+		return rc;
+	}
+
+	rc = async_data_read_start(exch, rinfo, sizeof(*rinfo));
+	async_exchange_end(exch);
+	if (rc != EOK) {
+		async_forget(req);
+		goto error;
+	}
+
+	async_wait_for(req, &retval);
+	if (retval != EOK) {
+		rc = retval;
+		goto error;
+	}
+
+error:
 	return rc;
 }
@@ -555,4 +513,26 @@
 }
 
+/** Get volume level string.
+ *
+ * @param level Levelvalue
+ *
+ * @return Level string
+ */
+const char *hr_get_level_str(hr_level_t level)
+{
+	switch (level) {
+	case HR_LVL_0:
+		return "stripe (RAID 0)";
+	case HR_LVL_1:
+		return "mirror (RAID 1)";
+	case HR_LVL_4:
+		return "dedicated parity (RAID 4)";
+	case HR_LVL_5:
+		return "distributed parity (RAID 5)";
+	default:
+		return "Invalid RAID level";
+	}
+}
+
 /** Get volume metadata type string.
  *
Index: uspace/srv/bd/hr/hr.c
===================================================================
--- uspace/srv/bd/hr/hr.c	(revision 431b513a6e48dd61f02244447a7173fd175fa1f7)
+++ uspace/srv/bd/hr/hr.c	(revision e0bbecb7cb52d3499d94395fa598579d09a6de5d)
@@ -59,5 +59,5 @@
 static void hr_stop_all_srv(ipc_call_t *);
 static void hr_add_hotspare_srv(ipc_call_t *);
-static void hr_print_state_srv(ipc_call_t *);
+static void hr_get_vol_states_srv(ipc_call_t *);
 static void hr_ctl_conn(ipc_call_t *);
 static void hr_client_conn(ipc_call_t *, void *);
@@ -416,9 +416,9 @@
 }
 
-/** Volume state printing (server).
- *
- * Prints info about all active volumes.
- */
-static void hr_print_state_srv(ipc_call_t *icall)
+/** Send volume states.
+ *
+ * Sends the client pairs of (volume service_id, state).
+ */
+static void hr_get_vol_states_srv(ipc_call_t *icall)
 {
 	HR_DEBUG("%s()", __func__);
@@ -426,5 +426,5 @@
 	errno_t rc;
 	size_t vol_cnt = 0;
-	hr_vol_info_t info;
+	hr_pair_vol_state_t pair;
 	ipc_call_t call;
 	size_t size;
@@ -439,5 +439,5 @@
 	}
 
-	if (size != sizeof(size_t)) {
+	if (size != sizeof(vol_cnt)) {
 		rc = EINVAL;
 		goto error;
@@ -449,19 +449,6 @@
 
 	list_foreach(hr_volumes, lvolumes, hr_volume_t, vol) {
-		memcpy(info.extents, vol->extents,
-		    sizeof(hr_extent_t) * HR_MAX_EXTENTS);
-		memcpy(info.hotspares, vol->hotspares,
-		    sizeof(hr_extent_t) * HR_MAX_HOTSPARES);
-		info.svc_id = vol->svc_id;
-		info.extent_no = vol->extent_no;
-		info.hotspare_no = vol->hotspare_no;
-		info.level = vol->level;
-		/* print usable number of blocks */
-		/* TODO: change to data_blkno */
-		info.nblocks = vol->data_blkno;
-		info.strip_size = vol->strip_size;
-		info.bsize = vol->bsize;
-		info.state = vol->state;
-		info.layout = vol->layout;
+		pair.svc_id = vol->svc_id;
+		pair.state = vol->state;
 
 		if (!async_data_read_receive(&call, &size)) {
@@ -470,10 +457,10 @@
 		}
 
-		if (size != sizeof(hr_vol_info_t)) {
+		if (size != sizeof(pair)) {
 			rc = EINVAL;
 			goto error;
 		}
 
-		rc = async_data_read_finalize(&call, &info, size);
+		rc = async_data_read_finalize(&call, &pair, size);
 		if (rc != EOK)
 			goto error;
@@ -489,4 +476,76 @@
 }
 
+/** Send volume info.
+ *
+ * Sends the client volume info.
+ */
+static void hr_get_vol_info_srv(ipc_call_t *icall)
+{
+	HR_DEBUG("%s()", __func__);
+
+	errno_t rc;
+	size_t size;
+	ipc_call_t call;
+	service_id_t svc_id;
+	hr_vol_info_t info;
+	hr_volume_t *vol;
+
+	if (!async_data_write_receive(&call, &size)) {
+		rc = EREFUSED;
+		goto error;
+	}
+
+	if (size != sizeof(service_id_t)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	rc = async_data_write_finalize(&call, &svc_id, size);
+	if (rc != EOK)
+		goto error;
+
+	vol = hr_get_volume(svc_id);
+	if (vol == NULL) {
+		rc = ENOENT;
+		goto error;
+	}
+
+	memcpy(info.extents, vol->extents,
+	    sizeof(hr_extent_t) * HR_MAX_EXTENTS);
+	memcpy(info.hotspares, vol->hotspares,
+	    sizeof(hr_extent_t) * HR_MAX_HOTSPARES);
+	info.svc_id = vol->svc_id;
+	info.extent_no = vol->extent_no;
+	info.hotspare_no = vol->hotspare_no;
+	info.level = vol->level;
+	info.data_blkno = vol->data_blkno;
+	info.strip_size = vol->strip_size;
+	info.bsize = vol->bsize;
+	info.state = vol->state;
+	info.layout = vol->layout;
+	info.meta_type = vol->meta_ops->get_type();
+	memcpy(info.devname, vol->devname, HR_DEVNAME_LEN);
+
+	if (!async_data_read_receive(&call, &size)) {
+		rc = EREFUSED;
+		goto error;
+	}
+
+	if (size != sizeof(info)) {
+		rc = EINVAL;
+		goto error;
+	}
+
+	rc = async_data_read_finalize(&call, &info, size);
+	if (rc != EOK)
+		goto error;
+
+	async_answer_0(icall, EOK);
+	return;
+error:
+	async_answer_0(&call, rc);
+	async_answer_0(icall, rc);
+}
+
 /**  HelenRAID server control IPC methods crossroad.
  */
@@ -529,6 +588,9 @@
 			hr_add_hotspare_srv(&call);
 			break;
-		case HR_STATUS:
-			hr_print_state_srv(&call);
+		case HR_GET_VOL_STATES:
+			hr_get_vol_states_srv(&call);
+			break;
+		case HR_GET_VOL_INFO:
+			hr_get_vol_info_srv(&call);
 			break;
 		default:
