1 /* $NetBSD: dm_target_snapshot.c,v 1.8 2009/02/19 23:07:33 haad Exp $ */ 2 3 /* 4 * Copyright (c) 2008 The NetBSD Foundation, Inc. 5 * All rights reserved. 6 * 7 * This code is derived from software contributed to The NetBSD Foundation 8 * by Adam Hamsik. 9 * 10 * Redistribution and use in source and binary forms, with or without 11 * modification, are permitted provided that the following conditions 12 * are met: 13 * 1. Redistributions of source code must retain the above copyright 14 * notice, this list of conditions and the following disclaimer. 15 * 2. Redistributions in binary form must reproduce the above copyright 16 * notice, this list of conditions and the following disclaimer in the 17 * documentation and/or other materials provided with the distribution. 18 * 19 * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS 20 * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 21 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 22 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS 23 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 24 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 25 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS 26 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN 27 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 28 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 29 * POSSIBILITY OF SUCH DAMAGE. 30 */ 31 32 /* 33 * 1. Suspend my_data to temporarily stop any I/O while the snapshot is being 34 * activated. 35 * dmsetup suspend my_data 36 * 37 * 2. Create the snapshot-origin device with no table. 38 * dmsetup create my_data_org 39 * 40 * 3. Read the table from my_data and load it into my_data_org. 41 * dmsetup table my_data | dmsetup load my_data_org 42 * 43 * 4. Resume this new table. 44 * dmsetup resume my_data_org 45 * 46 * 5. Create the snapshot device with no table. 47 * dmsetup create my_data_snap 48 * 49 * 6. Load the table into my_data_snap. This uses /dev/hdd1 as the COW device and 50 * uses a 32kB chunk-size. 51 * echo "0 `blockdev --getsize /dev/mapper/my_data` snapshot \ 52 * /dev/mapper/my_data_org /dev/hdd1 p 64" | dmsetup load my_data_snap 53 * 54 * 7. Reload my_data as a snapshot-origin device that points to my_data_org. 55 * echo "0 `blockdev --getsize /dev/mapper/my_data` snapshot-origin \ 56 * /dev/mapper/my_data_org" | dmsetup load my_data 57 * 58 * 8. Resume the snapshot and origin devices. 59 * dmsetup resume my_data_snap 60 * dmsetup resume my_data 61 * 62 * Before snapshot creation 63 * dev_name; dev table 64 * | my_data; 0 1024 linear /dev/sd1a 384| 65 * 66 * After snapshot creation 67 * |my_data_org;0 1024 linear /dev/sd1a 384| 68 * / 69 * |my_data; 0 1024 snapshot-origin /dev/vg00/my_data_org| 70 * / 71 * |my_data_snap; 0 1024 snapshot /dev/vg00/my_data /dev/mapper/my_data_cow P 8 72 * \ 73 * |my_data_cow; 0 256 linear /dev/sd1a 1408| 74 */ 75 76 /* 77 * This file implements initial version of device-mapper snapshot target. 78 */ 79 #include <sys/types.h> 80 #include <sys/param.h> 81 82 #include <sys/buf.h> 83 #include <sys/kmem.h> 84 #include <sys/vnode.h> 85 86 #include "dm.h" 87 88 #ifdef DM_TARGET_MODULE 89 /* 90 * Every target can be compiled directly to dm driver or as a 91 * separate module this part of target is used for loading targets 92 * to dm driver. 93 * Target can be unloaded from kernel only if there are no users of 94 * it e.g. there are no devices which uses that target. 95 */ 96 #include <sys/kernel.h> 97 #include <sys/module.h> 98 99 MODULE(MODULE_CLASS_MISC, dm_target_snapshot, "dm"); 100 101 static int 102 dm_target_snapshot_modcmd(modcmd_t cmd, void *arg) 103 { 104 dm_target_t *dmt, *dmt1; 105 int r; 106 107 dmt = NULL; 108 dmt1 = NULL; 109 110 switch (cmd) { 111 case MODULE_CMD_INIT: 112 if (((dmt = dm_target_lookup("snapshot")) != NULL)) { 113 dm_target_unbusy(dmt); 114 return EEXIST; 115 } 116 117 if (((dmt = dm_target_lookup("snapshot-origin")) != NULL)){ 118 dm_target_unbusy(dmt); 119 return EEXIST; 120 } 121 122 dmt = dm_target_alloc("snapshot"); 123 dmt1 = dm_target_alloc("snapshot-origin"); 124 125 dmt->version[0] = 1; 126 dmt->version[1] = 0; 127 dmt->version[2] = 5; 128 strlcpy(dmt->name, "snapshot", DM_MAX_TYPE_NAME); 129 dmt->init = &dm_target_snapshot_init; 130 dmt->status = &dm_target_snapshot_status; 131 dmt->strategy = &dm_target_snapshot_strategy; 132 dmt->deps = &dm_target_snapshot_deps; 133 dmt->destroy = &dm_target_snapshot_destroy; 134 dmt->upcall = &dm_target_snapshot_upcall; 135 136 r = dm_target_insert(dmt); 137 138 dmt1->version[0] = 1; 139 dmt1->version[1] = 0; 140 dmt1->version[2] = 5; 141 strlcpy(dmt1->name, "snapshot-origin", DM_MAX_TYPE_NAME); 142 dmt1->init = &dm_target_snapshot_orig_init; 143 dmt1->status = &dm_target_snapshot_orig_status; 144 dmt1->strategy = &dm_target_snapshot_orig_strategy; 145 dmt1->deps = &dm_target_snapshot_orig_deps; 146 dmt1->destroy = &dm_target_snapshot_orig_destroy; 147 dmt1->upcall = &dm_target_snapshot_orig_upcall; 148 149 r = dm_target_insert(dmt1); 150 break; 151 152 case MODULE_CMD_FINI: 153 /* 154 * Try to remove snapshot target if it works remove snap-origin 155 * it is not possible to remove snapshot and do not remove 156 * snap-origin because they are used together. 157 */ 158 if ((r = dm_target_rem("snapshot")) == 0) 159 r = dm_target_rem("snapshot-origin"); 160 161 break; 162 163 case MODULE_CMD_STAT: 164 return ENOTTY; 165 166 default: 167 return ENOTTY; 168 } 169 170 return r; 171 } 172 173 #endif 174 175 /* 176 * Init function called from dm_table_load_ioctl. 177 * argv: /dev/mapper/my_data_org /dev/mapper/tsc_cow_dev p 64 178 * snapshot_origin device, cow device, persistent flag, chunk size 179 */ 180 int 181 dm_target_snapshot_init(dm_dev_t *dmv, void **target_config, char *params) 182 { 183 dm_target_snapshot_config_t *tsc; 184 dm_pdev_t *dmp_snap, *dmp_cow; 185 char **ap, *argv[5]; 186 187 dmp_cow = NULL; 188 189 if (params == NULL) 190 return EINVAL; 191 /* 192 * Parse a string, containing tokens delimited by white space, 193 * into an argument vector 194 */ 195 for (ap = argv; ap < &argv[4] && 196 (*ap = strsep(¶ms, " \t")) != NULL;) { 197 if (**ap != '\0') 198 ap++; 199 } 200 201 printf("Snapshot target init function called!!\n"); 202 printf("Snapshotted device: %s, cow device %s,\n\t persistent flag: %s, " 203 "chunk size: %s\n", argv[0], argv[1], argv[2], argv[3]); 204 205 /* Insert snap device to global pdev list */ 206 if ((dmp_snap = dm_pdev_insert(argv[0])) == NULL) 207 return ENOENT; 208 209 if ((tsc = kmem_alloc(sizeof(dm_target_snapshot_config_t), KM_NOSLEEP)) 210 == NULL) 211 return 1; 212 213 tsc->tsc_persistent_dev = 0; 214 215 /* There is now cow device for nonpersistent snapshot devices */ 216 if (strcmp(argv[2], "p") == 0) { 217 tsc->tsc_persistent_dev = 1; 218 219 /* Insert cow device to global pdev list */ 220 if ((dmp_cow = dm_pdev_insert(argv[1])) == NULL) 221 return ENOENT; 222 } 223 224 tsc->tsc_chunk_size = atoi(argv[3]); 225 226 tsc->tsc_snap_dev = dmp_snap; 227 tsc->tsc_cow_dev = dmp_cow; 228 229 *target_config = tsc; 230 231 dmv->dev_type = DM_SNAPSHOT_DEV; 232 233 return 0; 234 } 235 236 /* 237 * Status routine is called to get params string, which is target 238 * specific. When dm_table_status_ioctl is called with flag 239 * DM_STATUS_TABLE_FLAG I have to sent params string back. 240 */ 241 char * 242 dm_target_snapshot_status(void *target_config) 243 { 244 dm_target_snapshot_config_t *tsc; 245 246 uint32_t i; 247 uint32_t count; 248 size_t prm_len, cow_len; 249 char *params, *cow_name; 250 251 tsc = target_config; 252 253 prm_len = 0; 254 cow_len = 0; 255 count = 0; 256 cow_name = NULL; 257 258 printf("Snapshot target status function called\n"); 259 260 /* count number of chars in offset */ 261 for(i = tsc->tsc_chunk_size; i != 0; i /= 10) 262 count++; 263 264 if(tsc->tsc_persistent_dev) 265 cow_len = strlen(tsc->tsc_cow_dev->name); 266 267 /* length of names + count of chars + spaces and null char */ 268 prm_len = strlen(tsc->tsc_snap_dev->name) + cow_len + count + 5; 269 270 if ((params = kmem_alloc(prm_len, KM_NOSLEEP)) == NULL) 271 return NULL; 272 273 printf("%s %s %s %"PRIu64"\n", tsc->tsc_snap_dev->name, 274 tsc->tsc_cow_dev->name, tsc->tsc_persistent_dev ? "p":"n", 275 tsc->tsc_chunk_size); 276 277 snprintf(params, prm_len, "%s %s %s %"PRIu64, tsc->tsc_snap_dev->name, 278 tsc->tsc_persistent_dev ? tsc->tsc_cow_dev->name : "", 279 tsc->tsc_persistent_dev ? "p":"n", 280 tsc->tsc_chunk_size); 281 282 return params; 283 } 284 285 /* Strategy routine called from dm_strategy. */ 286 int 287 dm_target_snapshot_strategy(dm_table_entry_t *table_en, struct buf *bp) 288 { 289 290 printf("Snapshot target read function called!!\n"); 291 292 bp->b_error = EIO; 293 bp->b_resid = 0; 294 295 biodone(bp); 296 297 return 0; 298 } 299 300 /* Doesn't do anything here. */ 301 int 302 dm_target_snapshot_destroy(dm_table_entry_t *table_en) 303 { 304 dm_target_snapshot_config_t *tsc; 305 306 /* 307 * Destroy function is called for every target even if it 308 * doesn't have target_config. 309 */ 310 311 if (table_en->target_config == NULL) 312 return 0; 313 314 printf("Snapshot target destroy function called\n"); 315 316 tsc = table_en->target_config; 317 318 /* Decrement pdev ref counter if 0 remove it */ 319 dm_pdev_decr(tsc->tsc_snap_dev); 320 321 if (tsc->tsc_persistent_dev) 322 dm_pdev_decr(tsc->tsc_cow_dev); 323 324 /* Unbusy target so we can unload it */ 325 dm_target_unbusy(table_en->target); 326 327 kmem_free(table_en->target_config, sizeof(dm_target_snapshot_config_t)); 328 329 table_en->target_config = NULL; 330 331 return 0; 332 } 333 334 /* Add this target dependiences to prop_array_t */ 335 int 336 dm_target_snapshot_deps(dm_table_entry_t *table_en, 337 prop_array_t prop_array) 338 { 339 dm_target_snapshot_config_t *tsc; 340 struct vattr va; 341 342 int error; 343 344 if (table_en->target_config == NULL) 345 return 0; 346 347 tsc = table_en->target_config; 348 349 if ((error = VOP_GETATTR(tsc->tsc_snap_dev->pdev_vnode, &va, curlwp->l_cred)) != 0) 350 return error; 351 352 prop_array_add_uint64(prop_array, (uint64_t)va.va_rdev); 353 354 if (tsc->tsc_persistent_dev) { 355 356 if ((error = VOP_GETATTR(tsc->tsc_cow_dev->pdev_vnode, &va, 357 curlwp->l_cred)) != 0) 358 return error; 359 360 prop_array_add_uint64(prop_array, (uint64_t)va.va_rdev); 361 362 } 363 364 return 0; 365 } 366 367 /* Upcall is used to inform other depended devices about IO. */ 368 int 369 dm_target_snapshot_upcall(dm_table_entry_t *table_en, struct buf *bp) 370 { 371 printf("dm_target_snapshot_upcall called\n"); 372 373 printf("upcall buf flags %s %s\n", 374 (bp->b_flags & B_WRITE) ? "B_WRITE":"", 375 (bp->b_flags & B_READ) ? "B_READ":""); 376 377 return 0; 378 } 379 380 /* 381 * dm target snapshot origin routines. 382 * 383 * Keep for compatibility with linux lvm2tools. They use two targets 384 * to implement snapshots. Snapshot target will implement exception 385 * store and snapshot origin will implement device which calls every 386 * snapshot device when write is done on master device. 387 */ 388 389 /* 390 * Init function called from dm_table_load_ioctl. 391 * 392 * argv: /dev/mapper/my_data_real 393 */ 394 int 395 dm_target_snapshot_orig_init(dm_dev_t *dmv, void **target_config, 396 char *params) 397 { 398 dm_target_snapshot_origin_config_t *tsoc; 399 dm_pdev_t *dmp_real; 400 401 if (params == NULL) 402 return EINVAL; 403 404 printf("Snapshot origin target init function called!!\n"); 405 printf("Parent device: %s\n", params); 406 407 /* Insert snap device to global pdev list */ 408 if ((dmp_real = dm_pdev_insert(params)) == NULL) 409 return ENOENT; 410 411 if ((tsoc = kmem_alloc(sizeof(dm_target_snapshot_origin_config_t), KM_NOSLEEP)) 412 == NULL) 413 return 1; 414 415 tsoc->tsoc_real_dev = dmp_real; 416 417 dmv->dev_type = DM_SNAPSHOT_ORIG_DEV; 418 419 *target_config = tsoc; 420 421 return 0; 422 } 423 424 /* 425 * Status routine is called to get params string, which is target 426 * specific. When dm_table_status_ioctl is called with flag 427 * DM_STATUS_TABLE_FLAG I have to sent params string back. 428 */ 429 char * 430 dm_target_snapshot_orig_status(void *target_config) 431 { 432 dm_target_snapshot_origin_config_t *tsoc; 433 434 size_t prm_len; 435 char *params; 436 437 tsoc = target_config; 438 439 prm_len = 0; 440 441 printf("Snapshot origin target status function called\n"); 442 443 /* length of names + count of chars + spaces and null char */ 444 prm_len = strlen(tsoc->tsoc_real_dev->name) + 1; 445 446 printf("real_dev name %s\n",tsoc->tsoc_real_dev->name); 447 448 if ((params = kmem_alloc(prm_len, KM_NOSLEEP)) == NULL) 449 return NULL; 450 451 printf("%s\n", tsoc->tsoc_real_dev->name); 452 453 snprintf(params, prm_len, "%s", tsoc->tsoc_real_dev->name); 454 455 return params; 456 } 457 458 /* Strategy routine called from dm_strategy. */ 459 int 460 dm_target_snapshot_orig_strategy(dm_table_entry_t *table_en, struct buf *bp) 461 { 462 463 printf("Snapshot_Orig target read function called!!\n"); 464 465 bp->b_error = EIO; 466 bp->b_resid = 0; 467 468 biodone(bp); 469 470 return 0; 471 } 472 473 /* Decrement pdev and free allocated space. */ 474 int 475 dm_target_snapshot_orig_destroy(dm_table_entry_t *table_en) 476 { 477 dm_target_snapshot_origin_config_t *tsoc; 478 479 /* 480 * Destroy function is called for every target even if it 481 * doesn't have target_config. 482 */ 483 484 if (table_en->target_config == NULL) 485 return 0; 486 487 tsoc = table_en->target_config; 488 489 /* Decrement pdev ref counter if 0 remove it */ 490 dm_pdev_decr(tsoc->tsoc_real_dev); 491 492 /* Unbusy target so we can unload it */ 493 dm_target_unbusy(table_en->target); 494 495 kmem_free(table_en->target_config, sizeof(dm_target_snapshot_origin_config_t)); 496 497 table_en->target_config = NULL; 498 499 return 0; 500 } 501 502 /* 503 * Get target deps and add them to prop_array_t. 504 */ 505 int 506 dm_target_snapshot_orig_deps(dm_table_entry_t *table_en, 507 prop_array_t prop_array) 508 { 509 dm_target_snapshot_origin_config_t *tsoc; 510 struct vattr va; 511 512 int error; 513 514 if (table_en->target_config == NULL) 515 return 0; 516 517 tsoc = table_en->target_config; 518 519 if ((error = VOP_GETATTR(tsoc->tsoc_real_dev->pdev_vnode, &va, 520 curlwp->l_cred)) != 0) 521 return error; 522 523 prop_array_add_uint64(prop_array, (uint64_t)va.va_rdev); 524 525 return 0; 526 } 527 528 /* Unsupported for this target. */ 529 int 530 dm_target_snapshot_orig_upcall(dm_table_entry_t *table_en, struct buf *bp) 531 { 532 return 0; 533 } 534