aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--module/zfs/dmu.c77
1 files changed, 77 insertions, 0 deletions
diff --git a/module/zfs/dmu.c b/module/zfs/dmu.c
index e2abf8cf2..00a7a07f4 100644
--- a/module/zfs/dmu.c
+++ b/module/zfs/dmu.c
@@ -1022,11 +1022,57 @@ dmu_req_copy(void *arg_buf, int size, int *offset, struct request *req)
return 0;
}
+static void
+dmu_bio_put(struct bio *bio)
+{
+ struct bio *bio_next;
+
+ while (bio) {
+ bio_next = bio->bi_next;
+ bio_put(bio);
+ bio = bio_next;
+ }
+}
+
+static int
+dmu_bio_clone(struct bio *bio, struct bio **bio_copy)
+{
+ struct bio *bio_root = NULL;
+ struct bio *bio_last = NULL;
+ struct bio *bio_new;
+
+ if (bio == NULL)
+ return EINVAL;
+
+ while (bio) {
+ bio_new = bio_clone(bio, GFP_NOIO);
+ if (bio_new == NULL) {
+ dmu_bio_put(bio_root);
+ return ENOMEM;
+ }
+
+ if (bio_last) {
+ bio_last->bi_next = bio_new;
+ bio_last = bio_new;
+ } else {
+ bio_root = bio_new;
+ bio_last = bio_new;
+ }
+
+ bio = bio->bi_next;
+ }
+
+ *bio_copy = bio_root;
+
+ return 0;
+}
+
int
dmu_read_req(objset_t *os, uint64_t object, struct request *req)
{
uint64_t size = blk_rq_bytes(req);
uint64_t offset = blk_rq_pos(req) << 9;
+ struct bio *bio_saved = req->bio;
dmu_buf_t **dbp;
int numbufs, i, err;
@@ -1039,6 +1085,17 @@ dmu_read_req(objset_t *os, uint64_t object, struct request *req)
if (err)
return (err);
+ /*
+ * Clone the bio list so the bv->bv_offset and bv->bv_len members
+ * can be safely modified. The original bio list is relinked in to
+ * the request when the function exits. This is required because
+ * some file systems blindly assume that these values will remain
+ * constant between bio_submit() and the IO completion callback.
+ */
+ err = dmu_bio_clone(bio_saved, &req->bio);
+ if (err)
+ goto error;
+
for (i = 0; i < numbufs; i++) {
int tocpy, didcpy, bufoff;
dmu_buf_t *db = dbp[i];
@@ -1062,6 +1119,10 @@ dmu_read_req(objset_t *os, uint64_t object, struct request *req)
offset += didcpy;
err = 0;
}
+
+ dmu_bio_put(req->bio);
+ req->bio = bio_saved;
+error:
dmu_buf_rele_array(dbp, numbufs, FTAG);
return (err);
@@ -1072,6 +1133,7 @@ dmu_write_req(objset_t *os, uint64_t object, struct request *req, dmu_tx_t *tx)
{
uint64_t size = blk_rq_bytes(req);
uint64_t offset = blk_rq_pos(req) << 9;
+ struct bio *bio_saved = req->bio;
dmu_buf_t **dbp;
int numbufs;
int err = 0;
@@ -1085,6 +1147,17 @@ dmu_write_req(objset_t *os, uint64_t object, struct request *req, dmu_tx_t *tx)
if (err)
return (err);
+ /*
+ * Clone the bio list so the bv->bv_offset and bv->bv_len members
+ * can be safely modified. The original bio list is relinked in to
+ * the request when the function exits. This is required because
+ * some file systems blindly assume that these values will remain
+ * constant between bio_submit() and the IO completion callback.
+ */
+ err = dmu_bio_clone(bio_saved, &req->bio);
+ if (err)
+ goto error;
+
for (i = 0; i < numbufs; i++) {
int tocpy, didcpy, bufoff;
dmu_buf_t *db = dbp[i];
@@ -1119,7 +1192,11 @@ dmu_write_req(objset_t *os, uint64_t object, struct request *req, dmu_tx_t *tx)
err = 0;
}
+ dmu_bio_put(req->bio);
+ req->bio = bio_saved;
+error:
dmu_buf_rele_array(dbp, numbufs, FTAG);
+
return (err);
}