Index: uspace/srv/bd/hr/io.c
===================================================================
--- uspace/srv/bd/hr/io.c	(revision 09c195e8b9cf55320cf91b7cc57d99fd9542e356)
+++ uspace/srv/bd/hr/io.c	(revision cdfcaeaeb03f76e707b13352d32fd0ff986958e1)
@@ -315,5 +315,5 @@
 	    stripe->parity + io->strip_off);
 	if (rc != EOK)
-		io->vol->hr_ops.ext_state_cb(io->vol, stripe->p_extent, rc);
+		io->vol->hr_ops.ext_state_cb(io->vol, io->extent, rc);
 
 	return rc;
Index: uspace/srv/bd/hr/parity_stripe.c
===================================================================
--- uspace/srv/bd/hr/parity_stripe.c	(revision 09c195e8b9cf55320cf91b7cc57d99fd9542e356)
+++ uspace/srv/bd/hr/parity_stripe.c	(revision cdfcaeaeb03f76e707b13352d32fd0ff986958e1)
@@ -53,13 +53,11 @@
     range_t *);
 static size_t hr_stripe_merge_extent_spans(hr_stripe_t *, size_t, range_t [2]);
-static void hr_reset_stripe(hr_stripe_t *);
 static void hr_stripe_extend_range(range_t *, const range_t *);
 static bool hr_ranges_overlap(const range_t *, const range_t *, range_t *);
 
-hr_stripe_t *hr_create_stripes(hr_volume_t *vol, size_t cnt, bool write)
-{
-	hr_stripe_t *stripes = calloc(cnt, sizeof(*stripes));
-	if (stripes == NULL)
-		return NULL;
+hr_stripe_t *hr_create_stripes(hr_volume_t *vol, uint64_t strip_size,
+    size_t cnt, bool write)
+{
+	hr_stripe_t *stripes = hr_calloc_waitok(cnt, sizeof(*stripes));
 
 	for (size_t i = 0; i < cnt; i++) {
@@ -68,17 +66,11 @@
 		stripes[i].vol = vol;
 		stripes[i].write = write;
-		stripes[i].parity = calloc(1, vol->strip_size);
-		if (stripes[i].parity == NULL)
-			goto error;
-		stripes[i].extent_span =
-		    calloc(vol->extent_no, sizeof(*stripes[i].extent_span));
-		if (stripes[i].extent_span == NULL)
-			goto error;
+		stripes[i].parity = hr_calloc_waitok(1, strip_size);
+		stripes[i].parity_size = strip_size;
+		stripes[i].extent_span = hr_calloc_waitok(vol->extent_no,
+		    sizeof(*stripes[i].extent_span));
 	}
 
 	return stripes;
-error:
-	hr_destroy_stripes(stripes, cnt);
-	return NULL;
 }
 
@@ -96,4 +88,16 @@
 
 	free(stripes);
+}
+
+void hr_reset_stripe(hr_stripe_t *stripe)
+{
+	memset(stripe->parity, 0, stripe->parity_size);
+	stripe->ps_added = 0;
+	stripe->ps_to_be_added = 0;
+	stripe->p_count_final = false;
+
+	stripe->rc = EOK;
+	stripe->abort = false;
+	stripe->done = false;
 }
 
@@ -878,18 +882,4 @@
 }
 
-static void hr_reset_stripe(hr_stripe_t *stripe)
-{
-	printf("%s\n", __func__);
-
-	memset(stripe->parity, 0, stripe->vol->strip_size);
-	stripe->ps_added = 0;
-	stripe->ps_to_be_added = 0;
-	stripe->p_count_final = false;
-
-	stripe->rc = EOK;
-	stripe->abort = false;
-	stripe->done = false;
-}
-
 /** Extend a range.
  *
Index: uspace/srv/bd/hr/parity_stripe.h
===================================================================
--- uspace/srv/bd/hr/parity_stripe.h	(revision 09c195e8b9cf55320cf91b7cc57d99fd9542e356)
+++ uspace/srv/bd/hr/parity_stripe.h	(revision cdfcaeaeb03f76e707b13352d32fd0ff986958e1)
@@ -73,4 +73,5 @@
 	fibril_mutex_t parity_lock;
 	uint8_t *parity; /* the actual parity strip */
+	uint64_t parity_size;
 
 	/* parity writers waiting until this many parity commits */
@@ -104,6 +105,7 @@
 } hr_stripe_t;
 
-extern hr_stripe_t *hr_create_stripes(hr_volume_t *, size_t, bool);
+extern hr_stripe_t *hr_create_stripes(hr_volume_t *, uint64_t, size_t, bool);
 extern void hr_destroy_stripes(hr_stripe_t *, size_t);
+extern void hr_reset_stripe(hr_stripe_t *);
 extern void hr_stripe_commit_parity(hr_stripe_t *, uint64_t, const void *,
     uint64_t);
Index: uspace/srv/bd/hr/raid5.c
===================================================================
--- uspace/srv/bd/hr/raid5.c	(revision 09c195e8b9cf55320cf91b7cc57d99fd9542e356)
+++ uspace/srv/bd/hr/raid5.c	(revision cdfcaeaeb03f76e707b13352d32fd0ff986958e1)
@@ -57,10 +57,8 @@
 
 static void hr_raid5_vol_state_eval_forced(hr_volume_t *);
-
 static size_t hr_raid5_parity_extent(hr_level_t, hr_layout_t, size_t,
     uint64_t);
 static size_t hr_raid5_data_extent(hr_level_t, hr_layout_t, size_t, uint64_t,
     uint64_t);
-
 static errno_t hr_raid5_rebuild(void *);
 
@@ -251,16 +249,8 @@
 	size_t stripes_cnt = end_stripe - start_stripe + 1;
 
-	hr_stripe_t *stripes = hr_create_stripes(vol, stripes_cnt, false);
-	if (stripes == NULL)
-		return ENOMEM;
-
-	/*
-	 * Pre-allocate range locks, because after group creation and
-	 * firing off IO requests there is no easy consistent ENOMEM error
-	 * path.
-	 */
+	hr_stripe_t *stripes = hr_create_stripes(vol, vol->strip_size,
+	    stripes_cnt, false);
+
 	hr_range_lock_t **rlps = hr_malloc_waitok(stripes_cnt * sizeof(*rlps));
-	for (size_t i = 0; i < stripes_cnt; i++)
-		rlps[i] = hr_malloc_waitok(sizeof(**rlps));
 
 	/*
@@ -272,5 +262,5 @@
 	for (uint64_t s = start_stripe; s <= end_stripe; s++) {
 		uint64_t relative = s - start_stripe;
-		hr_range_lock_acquire_noalloc(rlps[relative], vol, s, 1);
+		rlps[relative] = hr_range_lock_acquire(vol, s, 1);
 	}
 
@@ -378,4 +368,6 @@
 		hr_range_lock_release(rlps[i]);
 
+	free(rlps);
+
 	hr_destroy_stripes(stripes, stripes_cnt);
 
@@ -420,7 +412,6 @@
 	size_t stripes_cnt = end_stripe - start_stripe + 1;
 
-	hr_stripe_t *stripes = hr_create_stripes(vol, stripes_cnt, true);
-	if (stripes == NULL)
-		return ENOMEM;
+	hr_stripe_t *stripes = hr_create_stripes(vol, vol->strip_size,
+	    stripes_cnt, true);
 
 	uint64_t stripe_size = strip_size * (vol->extent_no - 1);
@@ -469,12 +460,5 @@
 	}
 
-	/*
-	 * Pre-allocate range locks, because after group creation and
-	 * firing off IO requests there is no easy consistent ENOMEM error
-	 * path.
-	 */
 	hr_range_lock_t **rlps = hr_malloc_waitok(stripes_cnt * sizeof(*rlps));
-	for (size_t i = 0; i < stripes_cnt; i++)
-		rlps[i] = hr_malloc_waitok(sizeof(**rlps));
 
 	/*
@@ -486,5 +470,5 @@
 	for (uint64_t s = start_stripe; s <= end_stripe; s++) {
 		uint64_t relative = s - start_stripe;
-		hr_range_lock_acquire_noalloc(rlps[relative], vol, s, 1);
+		rlps[relative] = hr_range_lock_acquire(vol, s, 1);
 	}
 
@@ -590,4 +574,6 @@
 		hr_range_lock_release(rlps[i]);
 
+	free(rlps);
+
 	hr_destroy_stripes(stripes, stripes_cnt);
 
@@ -622,4 +608,10 @@
 		if (vol->extents[i].state != HR_EXT_ONLINE)
 			bad++;
+
+	size_t invalid_no = hr_count_extents(vol, HR_EXT_INVALID);
+
+	fibril_mutex_lock(&vol->hotspare_lock);
+	size_t hs_no = vol->hotspare_no;
+	fibril_mutex_unlock(&vol->hotspare_lock);
 
 	switch (bad) {
@@ -633,9 +625,5 @@
 
 		if (state != HR_VOL_REBUILD) {
-			/* XXX: allow REBUILD on INVALID extents */
-			fibril_mutex_lock(&vol->hotspare_lock);
-			size_t hs_no = vol->hotspare_no;
-			fibril_mutex_unlock(&vol->hotspare_lock);
-			if (hs_no > 0) {
+			if (hs_no > 0 || invalid_no > 0) {
 				fid_t fib = fibril_create(hr_raid5_rebuild,
 				    vol);
@@ -655,14 +643,4 @@
 	fibril_rwlock_write_unlock(&vol->states_lock);
 	fibril_rwlock_read_unlock(&vol->extents_lock);
-}
-
-static void xor(void *dst, const void *src, size_t size)
-{
-	size_t i;
-	uint64_t *d = dst;
-	const uint64_t *s = src;
-
-	for (i = 0; i < size / sizeof(uint64_t); ++i)
-		*d++ ^= *s++;
 }
 
@@ -730,116 +708,108 @@
 static errno_t hr_raid5_rebuild(void *arg)
 {
-	HR_DEBUG("hr_raid5_rebuild()\n");
+	HR_DEBUG("%s()", __func__);
 
 	hr_volume_t *vol = arg;
 	errno_t rc = EOK;
+	size_t rebuild_idx;
 	void *buf = NULL, *xorbuf = NULL;
 
-	fibril_rwlock_read_lock(&vol->extents_lock);
-	fibril_rwlock_write_lock(&vol->states_lock);
-
-	if (vol->hotspare_no == 0) {
-		HR_WARN("hr_raid5_rebuild(): no free hotspares on \"%s\", "
-		    "aborting rebuild\n", vol->devname);
-		/* retval isn't checked for now */
-		goto end;
-	}
-
-	size_t bad = vol->extent_no;
-	for (size_t i = 0; i < vol->extent_no; i++) {
-		if (vol->extents[i].state == HR_EXT_FAILED) {
-			bad = i;
-			break;
-		}
-	}
-
-	if (bad == vol->extent_no) {
-		HR_WARN("hr_raid5_rebuild(): no bad extent on \"%s\", "
-		    "aborting rebuild\n", vol->devname);
-		/* retval isn't checked for now */
-		goto end;
-	}
-
-	size_t hotspare_idx = vol->hotspare_no - 1;
-
-	hr_ext_state_t hs_state = vol->hotspares[hotspare_idx].state;
-	if (hs_state != HR_EXT_HOTSPARE) {
-		HR_ERROR("hr_raid5_rebuild(): invalid hotspare state \"%s\", "
-		    "aborting rebuild\n", hr_get_ext_state_str(hs_state));
-		rc = EINVAL;
-		goto end;
-	}
-
-	HR_DEBUG("hr_raid5_rebuild(): swapping in hotspare\n");
-
-	block_fini(vol->extents[bad].svc_id);
-
-	vol->extents[bad].svc_id = vol->hotspares[hotspare_idx].svc_id;
-	hr_update_ext_state(vol, bad, HR_EXT_HOTSPARE);
-
-	vol->hotspares[hotspare_idx].svc_id = 0;
-	fibril_mutex_lock(&vol->hotspare_lock);
-	hr_update_hotspare_state(vol, hotspare_idx, HR_EXT_MISSING);
-	fibril_mutex_unlock(&vol->hotspare_lock);
-
-	vol->hotspare_no--;
-
-	hr_extent_t *rebuild_ext = &vol->extents[bad];
-
-	HR_DEBUG("hr_raid5_rebuild(): starting rebuild on (%" PRIun ")\n",
-	    rebuild_ext->svc_id);
-
-	hr_update_ext_state(vol, bad, HR_EXT_REBUILD);
-	hr_update_vol_state(vol, HR_VOL_REBUILD);
+	rc = hr_init_rebuild(vol, &rebuild_idx);
+	if (rc != EOK)
+		return rc;
 
 	uint64_t max_blks = DATA_XFER_LIMIT / vol->bsize;
 	uint64_t left = vol->data_blkno / (vol->extent_no - 1);
-	buf = malloc(max_blks * vol->bsize);
-	xorbuf = malloc(max_blks * vol->bsize);
+	buf = hr_malloc_waitok(max_blks * vol->bsize);
+	xorbuf = hr_malloc_waitok(max_blks * vol->bsize);
+
+	uint64_t strip_size = vol->strip_size / vol->bsize; /* in blocks */
 
 	uint64_t ba = 0, cnt;
 	hr_add_data_offset(vol, &ba);
 
+	/*
+	 * this is not necessary because a rebuild is
+	 * protected by itself, i.e. there can be only
+	 * one REBUILD at a time
+	 */
+	fibril_rwlock_read_lock(&vol->extents_lock);
+
+	/* increment metadata counter only on first write */
+	bool exp = false;
+	if (atomic_compare_exchange_strong(&vol->first_write, &exp, true)) {
+		vol->meta_ops->inc_counter(vol);
+		vol->meta_ops->save(vol, WITH_STATE_CALLBACK);
+	}
+
+	hr_range_lock_t *rl = NULL;
+	hr_stripe_t *stripe = hr_create_stripes(vol, max_blks * vol->bsize, 1,
+	    false);
+
+	unsigned int percent, old_percent = 100;
 	while (left != 0) {
 		cnt = min(left, max_blks);
 
-		/*
-		 * Almost the same as read_degraded,
-		 * but we don't want to allocate new
-		 * xorbuf each blk rebuild batch.
-		 */
-		bool first = true;
-		for (size_t i = 0; i < vol->extent_no; i++) {
-			if (i == bad)
+		uint64_t strip_no = ba / strip_size;
+		uint64_t last_ba = ba + cnt - 1;
+		uint64_t end_strip_no = last_ba / strip_size;
+		uint64_t start_stripe = strip_no / (vol->extent_no - 1);
+		uint64_t end_stripe = end_strip_no / (vol->extent_no - 1);
+		size_t stripes_cnt = end_stripe - start_stripe + 1;
+
+		stripe->ps_to_be_added = vol->extent_no - 1;
+		stripe->p_count_final = true;
+
+		hr_fgroup_t *worker_group =
+		    hr_fgroup_create(vol->fge, vol->extent_no);
+
+		rl = hr_range_lock_acquire(vol, start_stripe, stripes_cnt);
+
+		atomic_store_explicit(&vol->rebuild_blk, ba,
+		    memory_order_relaxed);
+
+		for (size_t e = 0; e < vol->extent_no; e++) {
+			if (e == rebuild_idx)
 				continue;
-			if (first)
-				rc = block_read_direct(vol->extents[i].svc_id,
-				    ba, cnt, xorbuf);
-			else
-				rc = block_read_direct(vol->extents[i].svc_id,
-				    ba, cnt, buf);
-			if (rc != EOK) {
-				hr_raid5_ext_state_cb(vol, i, rc);
-				HR_ERROR("rebuild on \"%s\" (%" PRIun "), "
-				    "failed due to a failed ONLINE extent, "
-				    "number %zu\n",
-				    vol->devname, vol->svc_id, i);
-				goto end;
-			}
-
-			if (!first)
-				xor(xorbuf, buf, cnt * vol->bsize);
-			else
-				first = false;
-		}
-
-		rc = block_write_direct(rebuild_ext->svc_id, ba, cnt, xorbuf);
-		if (rc != EOK) {
-			hr_raid5_ext_state_cb(vol, bad, rc);
-			HR_ERROR("rebuild on \"%s\" (%" PRIun "), failed due to "
-			    "the rebuilt extent number %zu failing\n",
-			    vol->devname, vol->svc_id, bad);
+
+			hr_io_raid5_t *io = hr_fgroup_alloc(worker_group);
+			io->extent = e;
+			io->ba = ba;
+			io->cnt = cnt;
+			io->strip_off = 0;
+			io->vol = vol;
+			io->stripe = stripe;
+
+			hr_fgroup_submit(worker_group,
+			    hr_io_raid5_reconstruct_reader, io);
+		}
+
+		hr_io_raid5_t *io = hr_fgroup_alloc(worker_group);
+		io->extent = rebuild_idx;
+		io->ba = ba;
+		io->cnt = cnt;
+		io->strip_off = 0;
+		io->vol = vol;
+		io->stripe = stripe;
+
+		hr_fgroup_submit(worker_group, hr_io_raid5_parity_writer, io);
+
+		size_t failed;
+		(void)hr_fgroup_wait(worker_group, NULL, &failed);
+		if (failed > 0) {
+			hr_range_lock_release(rl);
+			HR_NOTE("\"%s\": REBUILD aborted.\n", vol->devname);
 			goto end;
 		}
+
+		percent = ((ba + cnt) * 100) / vol->data_blkno;
+		if (percent != old_percent) {
+			if (percent % 5 == 0)
+				HR_DEBUG("\"%s\" REBUILD progress: %u%%\n",
+				    vol->devname, percent);
+		}
+
+		hr_range_lock_release(rl);
+		hr_reset_stripe(stripe);
 
 		ba += cnt;
@@ -850,37 +820,27 @@
 		 * during rebuild.
 		 */
-
-		/*
-		 * fibril_rwlock_write_unlock(&vol->states_lock);
-		 * fibril_mutex_unlock(&vol->lock);
-		 * fibril_mutex_lock(&vol->lock);
-		 * fibril_rwlock_write_lock(&vol->states_lock);
-		 */
 	}
 
 	HR_DEBUG("hr_raid5_rebuild(): rebuild finished on \"%s\" (%" PRIun "), "
-	    "extent number %zu\n", vol->devname, vol->svc_id, hotspare_idx);
-
-	hr_update_ext_state(vol, bad, HR_EXT_ONLINE);
+	    "extent number %zu\n", vol->devname, vol->svc_id, rebuild_idx);
+
+	fibril_rwlock_write_lock(&vol->states_lock);
+
+	hr_update_ext_state(vol, rebuild_idx, HR_EXT_ONLINE);
+
+	hr_mark_vol_state_dirty(vol);
 
 	fibril_rwlock_write_unlock(&vol->states_lock);
+
+	/* (void)vol->meta_ops->save(vol, WITH_STATE_CALLBACK); */
+
+end:
 	fibril_rwlock_read_unlock(&vol->extents_lock);
 
-	rc = vol->meta_ops->save(vol, WITH_STATE_CALLBACK);
-
-	fibril_rwlock_read_lock(&vol->extents_lock);
-	fibril_rwlock_write_lock(&vol->states_lock);
-
-end:
-	hr_raid5_vol_state_eval_forced(vol);
-
-	fibril_rwlock_write_unlock(&vol->states_lock);
-	fibril_rwlock_read_unlock(&vol->extents_lock);
-
-	if (buf != NULL)
-		free(buf);
-
-	if (xorbuf != NULL)
-		free(xorbuf);
+	hr_raid1_vol_state_eval(vol);
+
+	hr_destroy_stripes(stripe, 1);
+	free(buf);
+	free(xorbuf);
 
 	return rc;
Index: uspace/srv/bd/hr/util.c
===================================================================
--- uspace/srv/bd/hr/util.c	(revision 09c195e8b9cf55320cf91b7cc57d99fd9542e356)
+++ uspace/srv/bd/hr/util.c	(revision cdfcaeaeb03f76e707b13352d32fd0ff986958e1)
@@ -55,6 +55,4 @@
 #include "var.h"
 
-static hr_range_lock_t *hr_range_lock_acquire_internal(hr_range_lock_t *,
-    hr_volume_t *, uint64_t, uint64_t);
 static bool hr_range_lock_overlap(hr_range_lock_t *, hr_range_lock_t *);
 static errno_t hr_add_svc_linked_to_list(list_t *, service_id_t, bool, void *);
@@ -515,11 +513,4 @@
 }
 
-void hr_range_lock_acquire_noalloc(hr_range_lock_t *rl, hr_volume_t *vol,
-    uint64_t ba, uint64_t cnt)
-{
-	assert(rl != NULL);
-	(void)hr_range_lock_acquire_internal(rl, vol, ba, cnt);
-}
-
 hr_range_lock_t *hr_range_lock_acquire(hr_volume_t *vol, uint64_t ba,
     uint64_t cnt)
@@ -527,10 +518,4 @@
 	hr_range_lock_t *rl = hr_malloc_waitok(sizeof(hr_range_lock_t));
 
-	return hr_range_lock_acquire_internal(rl, vol, ba, cnt);
-}
-
-static hr_range_lock_t *hr_range_lock_acquire_internal(hr_range_lock_t *rl,
-    hr_volume_t *vol, uint64_t ba, uint64_t cnt)
-{
 	rl->vol = vol;
 	rl->off = ba;
Index: uspace/srv/bd/hr/util.h
===================================================================
--- uspace/srv/bd/hr/util.h	(revision 09c195e8b9cf55320cf91b7cc57d99fd9542e356)
+++ uspace/srv/bd/hr/util.h	(revision cdfcaeaeb03f76e707b13352d32fd0ff986958e1)
@@ -103,6 +103,4 @@
 extern size_t hr_count_extents(hr_volume_t *, hr_ext_state_t);
 extern void hr_mark_vol_state_dirty(hr_volume_t *);
-extern void hr_range_lock_acquire_noalloc(hr_range_lock_t *, hr_volume_t *,
-    uint64_t, uint64_t);
 extern hr_range_lock_t *hr_range_lock_acquire(hr_volume_t *, uint64_t,
     uint64_t);
