Skip to content

Commit 2e60238

Browse files
committed
dm snapshot: add optional discard support features
discard_zeroes_cow - a discard issued to the snapshot device that maps to entire chunks to will zero the corresponding exception(s) in the snapshot's exception store. discard_passdown_origin - a discard to the snapshot device is passed down to the snapshot-origin's underlying device. This doesn't cause copy-out to the snapshot exception store because the snapshot-origin target is bypassed. The discard_passdown_origin feature depends on the discard_zeroes_cow feature being enabled. When these 2 features are enabled they allow a temporarily read-only device that has completely exhausted its free space to recover space. To do so dm-snapshot provides temporary buffer to accommodate writes that the temporarily read-only device cannot handle yet. Once the upper layer frees space (e.g. fstrim to XFS) the discards issued to the dm-snapshot target will be issued to underlying read-only device whose free space was exhausted. In addition those discards will also cause zeroes to be written to the snapshot exception store if corresponding exceptions exist. If the underlying origin device provides deduplication for zero blocks then if/when the snapshot is merged backed to the origin those blocks will become unused. Once the origin has gained adequate space, merging the snapshot back to the thinly provisioned device will permit continued use of that device without the temporary space provided by the snapshot. Requested-by: John Dorminy <[email protected]> Signed-off-by: Mike Snitzer <[email protected]>
1 parent b9411d7 commit 2e60238

File tree

2 files changed

+181
-21
lines changed

2 files changed

+181
-21
lines changed

Documentation/device-mapper/snapshot.txt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ its visible content unchanged, at least until the <COW device> fills up.
3131

3232

3333
*) snapshot <origin> <COW device> <persistent?> <chunksize>
34+
[<# feature args> [<arg>]*]
3435

3536
A snapshot of the <origin> block device is created. Changed chunks of
3637
<chunksize> sectors will be stored on the <COW device>. Writes will
@@ -53,8 +54,23 @@ When loading or unloading the snapshot target, the corresponding
5354
snapshot-origin or snapshot-merge target must be suspended. A failure to
5455
suspend the origin target could result in data corruption.
5556

57+
Optional features:
58+
59+
discard_zeroes_cow - a discard issued to the snapshot device that
60+
maps to entire chunks to will zero the corresponding exception(s) in
61+
the snapshot's exception store.
62+
63+
discard_passdown_origin - a discard to the snapshot device is passed
64+
down to the snapshot-origin's underlying device. This doesn't cause
65+
copy-out to the snapshot exception store because the snapshot-origin
66+
target is bypassed.
67+
68+
The discard_passdown_origin feature depends on the discard_zeroes_cow
69+
feature being enabled.
70+
5671

5772
* snapshot-merge <origin> <COW device> <persistent> <chunksize>
73+
[<# feature args> [<arg>]*]
5874

5975
takes the same table arguments as the snapshot target except it only
6076
works with persistent snapshots. This target assumes the role of the

drivers/md/dm-snap.c

Lines changed: 165 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,4 @@
11
/*
2-
* dm-snapshot.c
3-
*
42
* Copyright (C) 2001-2002 Sistina Software (UK) Limited.
53
*
64
* This file is released under the GPL.
@@ -134,7 +132,10 @@ struct dm_snapshot {
134132
* - I/O error while merging
135133
* => stop merging; set merge_failed; process I/O normally.
136134
*/
137-
int merge_failed;
135+
bool merge_failed:1;
136+
137+
bool discard_zeroes_cow:1;
138+
bool discard_passdown_origin:1;
138139

139140
/*
140141
* Incoming bios that overlap with chunks being merged must wait
@@ -1173,21 +1174,73 @@ static void stop_merge(struct dm_snapshot *s)
11731174
clear_bit(SHUTDOWN_MERGE, &s->state_bits);
11741175
}
11751176

1177+
static int parse_snapshot_features(struct dm_arg_set *as, struct dm_snapshot *s,
1178+
struct dm_target *ti)
1179+
{
1180+
int r;
1181+
unsigned argc;
1182+
const char *arg_name;
1183+
1184+
static const struct dm_arg _args[] = {
1185+
{0, 2, "Invalid number of feature arguments"},
1186+
};
1187+
1188+
/*
1189+
* No feature arguments supplied.
1190+
*/
1191+
if (!as->argc)
1192+
return 0;
1193+
1194+
r = dm_read_arg_group(_args, as, &argc, &ti->error);
1195+
if (r)
1196+
return -EINVAL;
1197+
1198+
while (argc && !r) {
1199+
arg_name = dm_shift_arg(as);
1200+
argc--;
1201+
1202+
if (!strcasecmp(arg_name, "discard_zeroes_cow"))
1203+
s->discard_zeroes_cow = true;
1204+
1205+
else if (!strcasecmp(arg_name, "discard_passdown_origin"))
1206+
s->discard_passdown_origin = true;
1207+
1208+
else {
1209+
ti->error = "Unrecognised feature requested";
1210+
r = -EINVAL;
1211+
break;
1212+
}
1213+
}
1214+
1215+
if (!s->discard_zeroes_cow && s->discard_passdown_origin) {
1216+
/*
1217+
* TODO: really these are disjoint.. but ti->num_discard_bios
1218+
* and dm_bio_get_target_bio_nr() require rigid constraints.
1219+
*/
1220+
ti->error = "discard_passdown_origin feature depends on discard_zeroes_cow";
1221+
r = -EINVAL;
1222+
}
1223+
1224+
return r;
1225+
}
1226+
11761227
/*
1177-
* Construct a snapshot mapping: <origin_dev> <COW-dev> <p|po|n> <chunk-size>
1228+
* Construct a snapshot mapping:
1229+
* <origin_dev> <COW-dev> <p|po|n> <chunk-size> [<# feature args> [<arg>]*]
11781230
*/
11791231
static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
11801232
{
11811233
struct dm_snapshot *s;
1234+
struct dm_arg_set as;
11821235
int i;
11831236
int r = -EINVAL;
11841237
char *origin_path, *cow_path;
11851238
dev_t origin_dev, cow_dev;
11861239
unsigned args_used, num_flush_bios = 1;
11871240
fmode_t origin_mode = FMODE_READ;
11881241

1189-
if (argc != 4) {
1190-
ti->error = "requires exactly 4 arguments";
1242+
if (argc < 4) {
1243+
ti->error = "requires 4 or more arguments";
11911244
r = -EINVAL;
11921245
goto bad;
11931246
}
@@ -1204,6 +1257,13 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
12041257
goto bad;
12051258
}
12061259

1260+
as.argc = argc;
1261+
as.argv = argv;
1262+
dm_consume_args(&as, 4);
1263+
r = parse_snapshot_features(&as, s, ti);
1264+
if (r)
1265+
goto bad_features;
1266+
12071267
origin_path = argv[0];
12081268
argv++;
12091269
argc--;
@@ -1289,6 +1349,8 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
12891349

12901350
ti->private = s;
12911351
ti->num_flush_bios = num_flush_bios;
1352+
if (s->discard_zeroes_cow)
1353+
ti->num_discard_bios = (s->discard_passdown_origin ? 2 : 1);
12921354
ti->per_io_data_size = sizeof(struct dm_snap_tracked_chunk);
12931355

12941356
/* Add snapshot to the list of snapshots for this origin */
@@ -1336,29 +1398,22 @@ static int snapshot_ctr(struct dm_target *ti, unsigned int argc, char **argv)
13361398

13371399
bad_read_metadata:
13381400
unregister_snapshot(s);
1339-
13401401
bad_load_and_register:
13411402
mempool_exit(&s->pending_pool);
1342-
13431403
bad_pending_pool:
13441404
dm_kcopyd_client_destroy(s->kcopyd_client);
1345-
13461405
bad_kcopyd:
13471406
dm_exception_table_exit(&s->pending, pending_cache);
13481407
dm_exception_table_exit(&s->complete, exception_cache);
1349-
13501408
bad_hash_tables:
13511409
dm_exception_store_destroy(s->store);
1352-
13531410
bad_store:
13541411
dm_put_device(ti, s->cow);
1355-
13561412
bad_cow:
13571413
dm_put_device(ti, s->origin);
1358-
13591414
bad_origin:
1415+
bad_features:
13601416
kfree(s);
1361-
13621417
bad:
13631418
return r;
13641419
}
@@ -1806,6 +1861,37 @@ static void remap_exception(struct dm_snapshot *s, struct dm_exception *e,
18061861
(bio->bi_iter.bi_sector & s->store->chunk_mask);
18071862
}
18081863

1864+
static void zero_callback(int read_err, unsigned long write_err, void *context)
1865+
{
1866+
struct bio *bio = context;
1867+
struct dm_snapshot *s = bio->bi_private;
1868+
1869+
up(&s->cow_count);
1870+
bio->bi_status = write_err ? BLK_STS_IOERR : 0;
1871+
bio_endio(bio);
1872+
}
1873+
1874+
static void zero_exception(struct dm_snapshot *s, struct dm_exception *e,
1875+
struct bio *bio, chunk_t chunk)
1876+
{
1877+
struct dm_io_region dest;
1878+
1879+
dest.bdev = s->cow->bdev;
1880+
dest.sector = bio->bi_iter.bi_sector;
1881+
dest.count = s->store->chunk_size;
1882+
1883+
down(&s->cow_count);
1884+
WARN_ON_ONCE(bio->bi_private);
1885+
bio->bi_private = s;
1886+
dm_kcopyd_zero(s->kcopyd_client, 1, &dest, 0, zero_callback, bio);
1887+
}
1888+
1889+
static bool io_overlaps_chunk(struct dm_snapshot *s, struct bio *bio)
1890+
{
1891+
return bio->bi_iter.bi_size ==
1892+
(s->store->chunk_size << SECTOR_SHIFT);
1893+
}
1894+
18091895
static int snapshot_map(struct dm_target *ti, struct bio *bio)
18101896
{
18111897
struct dm_exception *e;
@@ -1839,10 +1925,43 @@ static int snapshot_map(struct dm_target *ti, struct bio *bio)
18391925
goto out_unlock;
18401926
}
18411927

1928+
if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) {
1929+
if (s->discard_passdown_origin && dm_bio_get_target_bio_nr(bio)) {
1930+
/*
1931+
* passdown discard to origin (without triggering
1932+
* snapshot exceptions via do_origin; doing so would
1933+
* defeat the goal of freeing space in origin that is
1934+
* implied by the "discard_passdown_origin" feature)
1935+
*/
1936+
bio_set_dev(bio, s->origin->bdev);
1937+
track_chunk(s, bio, chunk);
1938+
goto out_unlock;
1939+
}
1940+
/* discard to snapshot (target_bio_nr == 0) zeroes exceptions */
1941+
}
1942+
18421943
/* If the block is already remapped - use that, else remap it */
18431944
e = dm_lookup_exception(&s->complete, chunk);
18441945
if (e) {
18451946
remap_exception(s, e, bio, chunk);
1947+
if (unlikely(bio_op(bio) == REQ_OP_DISCARD) &&
1948+
io_overlaps_chunk(s, bio)) {
1949+
dm_exception_table_unlock(&lock);
1950+
up_read(&s->lock);
1951+
zero_exception(s, e, bio, chunk);
1952+
r = DM_MAPIO_SUBMITTED; /* discard is not issued */
1953+
goto out;
1954+
}
1955+
goto out_unlock;
1956+
}
1957+
1958+
if (unlikely(bio_op(bio) == REQ_OP_DISCARD)) {
1959+
/*
1960+
* If no exception exists, complete discard immediately
1961+
* otherwise it'll trigger copy-out.
1962+
*/
1963+
bio_endio(bio);
1964+
r = DM_MAPIO_SUBMITTED;
18461965
goto out_unlock;
18471966
}
18481967

@@ -1890,9 +2009,7 @@ static int snapshot_map(struct dm_target *ti, struct bio *bio)
18902009

18912010
r = DM_MAPIO_SUBMITTED;
18922011

1893-
if (!pe->started &&
1894-
bio->bi_iter.bi_size ==
1895-
(s->store->chunk_size << SECTOR_SHIFT)) {
2012+
if (!pe->started && io_overlaps_chunk(s, bio)) {
18962013
pe->started = 1;
18972014

18982015
dm_exception_table_unlock(&lock);
@@ -2138,6 +2255,7 @@ static void snapshot_status(struct dm_target *ti, status_type_t type,
21382255
{
21392256
unsigned sz = 0;
21402257
struct dm_snapshot *snap = ti->private;
2258+
unsigned num_features;
21412259

21422260
switch (type) {
21432261
case STATUSTYPE_INFO:
@@ -2178,8 +2296,16 @@ static void snapshot_status(struct dm_target *ti, status_type_t type,
21782296
* make sense.
21792297
*/
21802298
DMEMIT("%s %s", snap->origin->name, snap->cow->name);
2181-
snap->store->type->status(snap->store, type, result + sz,
2182-
maxlen - sz);
2299+
sz += snap->store->type->status(snap->store, type, result + sz,
2300+
maxlen - sz);
2301+
num_features = snap->discard_zeroes_cow + snap->discard_passdown_origin;
2302+
if (num_features) {
2303+
DMEMIT(" %u", num_features);
2304+
if (snap->discard_zeroes_cow)
2305+
DMEMIT(" discard_zeroes_cow");
2306+
if (snap->discard_passdown_origin)
2307+
DMEMIT(" discard_passdown_origin");
2308+
}
21832309
break;
21842310
}
21852311
}
@@ -2198,6 +2324,22 @@ static int snapshot_iterate_devices(struct dm_target *ti,
21982324
return r;
21992325
}
22002326

2327+
static void snapshot_io_hints(struct dm_target *ti, struct queue_limits *limits)
2328+
{
2329+
struct dm_snapshot *snap = ti->private;
2330+
2331+
if (snap->discard_zeroes_cow) {
2332+
struct dm_snapshot *snap_src = NULL, *snap_dest = NULL;
2333+
2334+
(void) __find_snapshots_sharing_cow(snap, &snap_src, &snap_dest, NULL);
2335+
if (snap_src && snap_dest)
2336+
snap = snap_src;
2337+
2338+
/* All discards are split on chunk_size boundary */
2339+
limits->discard_granularity = snap->store->chunk_size;
2340+
limits->max_discard_sectors = snap->store->chunk_size;
2341+
}
2342+
}
22012343

22022344
/*-----------------------------------------------------------------
22032345
* Origin methods
@@ -2522,7 +2664,7 @@ static struct target_type origin_target = {
25222664

25232665
static struct target_type snapshot_target = {
25242666
.name = "snapshot",
2525-
.version = {1, 15, 0},
2667+
.version = {1, 16, 0},
25262668
.module = THIS_MODULE,
25272669
.ctr = snapshot_ctr,
25282670
.dtr = snapshot_dtr,
@@ -2532,11 +2674,12 @@ static struct target_type snapshot_target = {
25322674
.resume = snapshot_resume,
25332675
.status = snapshot_status,
25342676
.iterate_devices = snapshot_iterate_devices,
2677+
.io_hints = snapshot_io_hints,
25352678
};
25362679

25372680
static struct target_type merge_target = {
25382681
.name = dm_snapshot_merge_target_name,
2539-
.version = {1, 4, 0},
2682+
.version = {1, 5, 0},
25402683
.module = THIS_MODULE,
25412684
.ctr = snapshot_ctr,
25422685
.dtr = snapshot_dtr,
@@ -2547,6 +2690,7 @@ static struct target_type merge_target = {
25472690
.resume = snapshot_merge_resume,
25482691
.status = snapshot_status,
25492692
.iterate_devices = snapshot_iterate_devices,
2693+
.io_hints = snapshot_io_hints,
25502694
};
25512695

25522696
static int __init dm_snapshot_init(void)

0 commit comments

Comments
 (0)