Print this page
3996 want a libzfs_core API to rollback to latest snapshot
Reviewed by: Christopher Siden <christopher.siden@delphix.com>
Reviewed by: Adam Leventhal <ahl@delphix.com>
Reviewed by: George Wilson <george.wilson@delphix.com>
Split |
Close |
Expand all |
Collapse all |
--- old/usr/src/lib/libzfs_core/common/libzfs_core.c
+++ new/usr/src/lib/libzfs_core/common/libzfs_core.c
1 1 /*
2 2 * CDDL HEADER START
3 3 *
4 4 * The contents of this file are subject to the terms of the
5 5 * Common Development and Distribution License (the "License").
6 6 * You may not use this file except in compliance with the License.
7 7 *
8 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9 9 * or http://www.opensolaris.org/os/licensing.
10 10 * See the License for the specific language governing permissions
11 11 * and limitations under the License.
12 12 *
↓ open down ↓ |
12 lines elided |
↑ open up ↑ |
13 13 * When distributing Covered Code, include this CDDL HEADER in each
14 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15 15 * If applicable, add the following below this CDDL HEADER, with the
16 16 * fields enclosed by brackets "[]" replaced with your own identifying
17 17 * information: Portions Copyright [yyyy] [name of copyright owner]
18 18 *
19 19 * CDDL HEADER END
20 20 */
21 21
22 22 /*
23 - * Copyright (c) 2012 by Delphix. All rights reserved.
23 + * Copyright (c) 2013 by Delphix. All rights reserved.
24 24 * Copyright (c) 2013 Steven Hartland. All rights reserved.
25 25 */
26 26
27 27 /*
28 28 * LibZFS_Core (lzc) is intended to replace most functionality in libzfs.
29 29 * It has the following characteristics:
30 30 *
31 31 * - Thread Safe. libzfs_core is accessible concurrently from multiple
32 32 * threads. This is accomplished primarily by avoiding global data
33 33 * (e.g. caching). Since it's thread-safe, there is no reason for a
34 34 * process to have multiple libzfs "instances". Therefore, we store
35 35 * our few pieces of data (e.g. the file descriptor) in global
36 36 * variables. The fd is reference-counted so that the libzfs_core
37 37 * library can be "initialized" multiple times (e.g. by different
38 38 * consumers within the same process).
39 39 *
40 40 * - Committed Interface. The libzfs_core interface will be committed,
41 41 * therefore consumers can compile against it and be confident that
42 42 * their code will continue to work on future releases of this code.
43 43 * Currently, the interface is Evolving (not Committed), but we intend
44 44 * to commit to it once it is more complete and we determine that it
45 45 * meets the needs of all consumers.
46 46 *
47 47 * - Programatic Error Handling. libzfs_core communicates errors with
48 48 * defined error numbers, and doesn't print anything to stdout/stderr.
49 49 *
50 50 * - Thin Layer. libzfs_core is a thin layer, marshaling arguments
51 51 * to/from the kernel ioctls. There is generally a 1:1 correspondence
52 52 * between libzfs_core functions and ioctls to /dev/zfs.
53 53 *
54 54 * - Clear Atomicity. Because libzfs_core functions are generally 1:1
55 55 * with kernel ioctls, and kernel ioctls are general atomic, each
56 56 * libzfs_core function is atomic. For example, creating multiple
57 57 * snapshots with a single call to lzc_snapshot() is atomic -- it
58 58 * can't fail with only some of the requested snapshots created, even
59 59 * in the event of power loss or system crash.
60 60 *
61 61 * - Continued libzfs Support. Some higher-level operations (e.g.
62 62 * support for "zfs send -R") are too complicated to fit the scope of
63 63 * libzfs_core. This functionality will continue to live in libzfs.
64 64 * Where appropriate, libzfs will use the underlying atomic operations
65 65 * of libzfs_core. For example, libzfs may implement "zfs send -R |
66 66 * zfs receive" by using individual "send one snapshot", rename,
67 67 * destroy, and "receive one snapshot" operations in libzfs_core.
68 68 * /sbin/zfs and /zbin/zpool will link with both libzfs and
69 69 * libzfs_core. Other consumers should aim to use only libzfs_core,
70 70 * since that will be the supported, stable interface going forwards.
71 71 */
72 72
73 73 #include <libzfs_core.h>
74 74 #include <ctype.h>
75 75 #include <unistd.h>
76 76 #include <stdlib.h>
77 77 #include <string.h>
78 78 #include <errno.h>
79 79 #include <fcntl.h>
80 80 #include <pthread.h>
81 81 #include <sys/nvpair.h>
82 82 #include <sys/param.h>
83 83 #include <sys/types.h>
84 84 #include <sys/stat.h>
85 85 #include <sys/zfs_ioctl.h>
86 86
87 87 static int g_fd;
88 88 static pthread_mutex_t g_lock = PTHREAD_MUTEX_INITIALIZER;
89 89 static int g_refcount;
90 90
91 91 int
92 92 libzfs_core_init(void)
93 93 {
94 94 (void) pthread_mutex_lock(&g_lock);
95 95 if (g_refcount == 0) {
96 96 g_fd = open("/dev/zfs", O_RDWR);
97 97 if (g_fd < 0) {
98 98 (void) pthread_mutex_unlock(&g_lock);
99 99 return (errno);
100 100 }
101 101 }
102 102 g_refcount++;
103 103 (void) pthread_mutex_unlock(&g_lock);
104 104 return (0);
105 105 }
106 106
107 107 void
108 108 libzfs_core_fini(void)
109 109 {
110 110 (void) pthread_mutex_lock(&g_lock);
111 111 ASSERT3S(g_refcount, >, 0);
112 112 g_refcount--;
113 113 if (g_refcount == 0)
114 114 (void) close(g_fd);
115 115 (void) pthread_mutex_unlock(&g_lock);
116 116 }
117 117
118 118 static int
119 119 lzc_ioctl(zfs_ioc_t ioc, const char *name,
120 120 nvlist_t *source, nvlist_t **resultp)
121 121 {
122 122 zfs_cmd_t zc = { 0 };
123 123 int error = 0;
124 124 char *packed;
125 125 size_t size;
126 126
127 127 ASSERT3S(g_refcount, >, 0);
128 128
129 129 (void) strlcpy(zc.zc_name, name, sizeof (zc.zc_name));
130 130
131 131 packed = fnvlist_pack(source, &size);
132 132 zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
133 133 zc.zc_nvlist_src_size = size;
134 134
135 135 if (resultp != NULL) {
136 136 *resultp = NULL;
137 137 zc.zc_nvlist_dst_size = MAX(size * 2, 128 * 1024);
138 138 zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
139 139 malloc(zc.zc_nvlist_dst_size);
140 140 if (zc.zc_nvlist_dst == NULL) {
141 141 error = ENOMEM;
142 142 goto out;
143 143 }
144 144 }
145 145
146 146 while (ioctl(g_fd, ioc, &zc) != 0) {
147 147 if (errno == ENOMEM && resultp != NULL) {
148 148 free((void *)(uintptr_t)zc.zc_nvlist_dst);
149 149 zc.zc_nvlist_dst_size *= 2;
150 150 zc.zc_nvlist_dst = (uint64_t)(uintptr_t)
151 151 malloc(zc.zc_nvlist_dst_size);
152 152 if (zc.zc_nvlist_dst == NULL) {
153 153 error = ENOMEM;
154 154 goto out;
155 155 }
156 156 } else {
157 157 error = errno;
158 158 break;
159 159 }
160 160 }
161 161 if (zc.zc_nvlist_dst_filled) {
162 162 *resultp = fnvlist_unpack((void *)(uintptr_t)zc.zc_nvlist_dst,
163 163 zc.zc_nvlist_dst_size);
164 164 }
165 165
166 166 out:
167 167 fnvlist_pack_free(packed, size);
168 168 free((void *)(uintptr_t)zc.zc_nvlist_dst);
169 169 return (error);
170 170 }
171 171
172 172 int
173 173 lzc_create(const char *fsname, dmu_objset_type_t type, nvlist_t *props)
174 174 {
175 175 int error;
176 176 nvlist_t *args = fnvlist_alloc();
177 177 fnvlist_add_int32(args, "type", type);
178 178 if (props != NULL)
179 179 fnvlist_add_nvlist(args, "props", props);
180 180 error = lzc_ioctl(ZFS_IOC_CREATE, fsname, args, NULL);
181 181 nvlist_free(args);
182 182 return (error);
183 183 }
184 184
185 185 int
186 186 lzc_clone(const char *fsname, const char *origin,
187 187 nvlist_t *props)
188 188 {
189 189 int error;
190 190 nvlist_t *args = fnvlist_alloc();
191 191 fnvlist_add_string(args, "origin", origin);
192 192 if (props != NULL)
193 193 fnvlist_add_nvlist(args, "props", props);
194 194 error = lzc_ioctl(ZFS_IOC_CLONE, fsname, args, NULL);
195 195 nvlist_free(args);
196 196 return (error);
197 197 }
198 198
199 199 /*
200 200 * Creates snapshots.
201 201 *
202 202 * The keys in the snaps nvlist are the snapshots to be created.
203 203 * They must all be in the same pool.
204 204 *
205 205 * The props nvlist is properties to set. Currently only user properties
206 206 * are supported. { user:prop_name -> string value }
207 207 *
208 208 * The returned results nvlist will have an entry for each snapshot that failed.
209 209 * The value will be the (int32) error code.
210 210 *
211 211 * The return value will be 0 if all snapshots were created, otherwise it will
212 212 * be the errno of a (unspecified) snapshot that failed.
213 213 */
214 214 int
215 215 lzc_snapshot(nvlist_t *snaps, nvlist_t *props, nvlist_t **errlist)
216 216 {
217 217 nvpair_t *elem;
218 218 nvlist_t *args;
219 219 int error;
220 220 char pool[MAXNAMELEN];
221 221
222 222 *errlist = NULL;
223 223
224 224 /* determine the pool name */
225 225 elem = nvlist_next_nvpair(snaps, NULL);
226 226 if (elem == NULL)
227 227 return (0);
228 228 (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
229 229 pool[strcspn(pool, "/@")] = '\0';
230 230
231 231 args = fnvlist_alloc();
232 232 fnvlist_add_nvlist(args, "snaps", snaps);
233 233 if (props != NULL)
234 234 fnvlist_add_nvlist(args, "props", props);
235 235
236 236 error = lzc_ioctl(ZFS_IOC_SNAPSHOT, pool, args, errlist);
237 237 nvlist_free(args);
238 238
239 239 return (error);
240 240 }
241 241
242 242 /*
243 243 * Destroys snapshots.
244 244 *
245 245 * The keys in the snaps nvlist are the snapshots to be destroyed.
246 246 * They must all be in the same pool.
247 247 *
248 248 * Snapshots that do not exist will be silently ignored.
249 249 *
250 250 * If 'defer' is not set, and a snapshot has user holds or clones, the
251 251 * destroy operation will fail and none of the snapshots will be
252 252 * destroyed.
253 253 *
254 254 * If 'defer' is set, and a snapshot has user holds or clones, it will be
255 255 * marked for deferred destruction, and will be destroyed when the last hold
256 256 * or clone is removed/destroyed.
257 257 *
258 258 * The return value will be 0 if all snapshots were destroyed (or marked for
259 259 * later destruction if 'defer' is set) or didn't exist to begin with.
260 260 *
261 261 * Otherwise the return value will be the errno of a (unspecified) snapshot
262 262 * that failed, no snapshots will be destroyed, and the errlist will have an
263 263 * entry for each snapshot that failed. The value in the errlist will be
264 264 * the (int32) error code.
265 265 */
266 266 int
267 267 lzc_destroy_snaps(nvlist_t *snaps, boolean_t defer, nvlist_t **errlist)
268 268 {
269 269 nvpair_t *elem;
270 270 nvlist_t *args;
271 271 int error;
272 272 char pool[MAXNAMELEN];
273 273
274 274 /* determine the pool name */
275 275 elem = nvlist_next_nvpair(snaps, NULL);
276 276 if (elem == NULL)
277 277 return (0);
278 278 (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
279 279 pool[strcspn(pool, "/@")] = '\0';
280 280
281 281 args = fnvlist_alloc();
282 282 fnvlist_add_nvlist(args, "snaps", snaps);
283 283 if (defer)
284 284 fnvlist_add_boolean(args, "defer");
285 285
286 286 error = lzc_ioctl(ZFS_IOC_DESTROY_SNAPS, pool, args, errlist);
287 287 nvlist_free(args);
288 288
289 289 return (error);
290 290 }
291 291
292 292 int
293 293 lzc_snaprange_space(const char *firstsnap, const char *lastsnap,
294 294 uint64_t *usedp)
295 295 {
296 296 nvlist_t *args;
297 297 nvlist_t *result;
298 298 int err;
299 299 char fs[MAXNAMELEN];
300 300 char *atp;
301 301
302 302 /* determine the fs name */
303 303 (void) strlcpy(fs, firstsnap, sizeof (fs));
304 304 atp = strchr(fs, '@');
305 305 if (atp == NULL)
306 306 return (EINVAL);
307 307 *atp = '\0';
308 308
309 309 args = fnvlist_alloc();
310 310 fnvlist_add_string(args, "firstsnap", firstsnap);
311 311
312 312 err = lzc_ioctl(ZFS_IOC_SPACE_SNAPS, lastsnap, args, &result);
313 313 nvlist_free(args);
314 314 if (err == 0)
315 315 *usedp = fnvlist_lookup_uint64(result, "used");
316 316 fnvlist_free(result);
317 317
318 318 return (err);
319 319 }
320 320
321 321 boolean_t
322 322 lzc_exists(const char *dataset)
323 323 {
324 324 /*
325 325 * The objset_stats ioctl is still legacy, so we need to construct our
326 326 * own zfs_cmd_t rather than using zfsc_ioctl().
327 327 */
328 328 zfs_cmd_t zc = { 0 };
329 329
330 330 (void) strlcpy(zc.zc_name, dataset, sizeof (zc.zc_name));
331 331 return (ioctl(g_fd, ZFS_IOC_OBJSET_STATS, &zc) == 0);
332 332 }
333 333
334 334 /*
335 335 * Create "user holds" on snapshots. If there is a hold on a snapshot,
336 336 * the snapshot can not be destroyed. (However, it can be marked for deletion
337 337 * by lzc_destroy_snaps(defer=B_TRUE).)
338 338 *
339 339 * The keys in the nvlist are snapshot names.
340 340 * The snapshots must all be in the same pool.
341 341 * The value is the name of the hold (string type).
342 342 *
343 343 * If cleanup_fd is not -1, it must be the result of open("/dev/zfs", O_EXCL).
344 344 * In this case, when the cleanup_fd is closed (including on process
345 345 * termination), the holds will be released. If the system is shut down
346 346 * uncleanly, the holds will be released when the pool is next opened
347 347 * or imported.
348 348 *
349 349 * Holds for snapshots which don't exist will be skipped and have an entry
350 350 * added to errlist, but will not cause an overall failure.
351 351 *
352 352 * The return value will be 0 if all holds, for snapshots that existed,
353 353 * were succesfully created.
354 354 *
355 355 * Otherwise the return value will be the errno of a (unspecified) hold that
356 356 * failed and no holds will be created.
357 357 *
358 358 * In all cases the errlist will have an entry for each hold that failed
359 359 * (name = snapshot), with its value being the error code (int32).
360 360 */
361 361 int
362 362 lzc_hold(nvlist_t *holds, int cleanup_fd, nvlist_t **errlist)
363 363 {
364 364 char pool[MAXNAMELEN];
365 365 nvlist_t *args;
366 366 nvpair_t *elem;
367 367 int error;
368 368
369 369 /* determine the pool name */
370 370 elem = nvlist_next_nvpair(holds, NULL);
371 371 if (elem == NULL)
372 372 return (0);
373 373 (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
374 374 pool[strcspn(pool, "/@")] = '\0';
375 375
376 376 args = fnvlist_alloc();
377 377 fnvlist_add_nvlist(args, "holds", holds);
378 378 if (cleanup_fd != -1)
379 379 fnvlist_add_int32(args, "cleanup_fd", cleanup_fd);
380 380
381 381 error = lzc_ioctl(ZFS_IOC_HOLD, pool, args, errlist);
382 382 nvlist_free(args);
383 383 return (error);
384 384 }
385 385
386 386 /*
387 387 * Release "user holds" on snapshots. If the snapshot has been marked for
388 388 * deferred destroy (by lzc_destroy_snaps(defer=B_TRUE)), it does not have
389 389 * any clones, and all the user holds are removed, then the snapshot will be
390 390 * destroyed.
391 391 *
392 392 * The keys in the nvlist are snapshot names.
393 393 * The snapshots must all be in the same pool.
394 394 * The value is a nvlist whose keys are the holds to remove.
395 395 *
396 396 * Holds which failed to release because they didn't exist will have an entry
397 397 * added to errlist, but will not cause an overall failure.
398 398 *
399 399 * The return value will be 0 if the nvl holds was empty or all holds that
400 400 * existed, were successfully removed.
401 401 *
402 402 * Otherwise the return value will be the errno of a (unspecified) hold that
403 403 * failed to release and no holds will be released.
404 404 *
405 405 * In all cases the errlist will have an entry for each hold that failed to
406 406 * to release.
407 407 */
408 408 int
409 409 lzc_release(nvlist_t *holds, nvlist_t **errlist)
410 410 {
411 411 char pool[MAXNAMELEN];
412 412 nvpair_t *elem;
413 413
414 414 /* determine the pool name */
415 415 elem = nvlist_next_nvpair(holds, NULL);
416 416 if (elem == NULL)
417 417 return (0);
418 418 (void) strlcpy(pool, nvpair_name(elem), sizeof (pool));
419 419 pool[strcspn(pool, "/@")] = '\0';
420 420
421 421 return (lzc_ioctl(ZFS_IOC_RELEASE, pool, holds, errlist));
422 422 }
423 423
424 424 /*
425 425 * Retrieve list of user holds on the specified snapshot.
426 426 *
427 427 * On success, *holdsp will be set to a nvlist which the caller must free.
428 428 * The keys are the names of the holds, and the value is the creation time
429 429 * of the hold (uint64) in seconds since the epoch.
430 430 */
431 431 int
432 432 lzc_get_holds(const char *snapname, nvlist_t **holdsp)
433 433 {
434 434 int error;
435 435 nvlist_t *innvl = fnvlist_alloc();
436 436 error = lzc_ioctl(ZFS_IOC_GET_HOLDS, snapname, innvl, holdsp);
437 437 fnvlist_free(innvl);
438 438 return (error);
439 439 }
440 440
441 441 /*
442 442 * If fromsnap is NULL, a full (non-incremental) stream will be sent.
443 443 */
444 444 int
445 445 lzc_send(const char *snapname, const char *fromsnap, int fd)
446 446 {
447 447 nvlist_t *args;
448 448 int err;
449 449
450 450 args = fnvlist_alloc();
451 451 fnvlist_add_int32(args, "fd", fd);
452 452 if (fromsnap != NULL)
453 453 fnvlist_add_string(args, "fromsnap", fromsnap);
454 454 err = lzc_ioctl(ZFS_IOC_SEND_NEW, snapname, args, NULL);
455 455 nvlist_free(args);
456 456 return (err);
457 457 }
458 458
459 459 /*
460 460 * If fromsnap is NULL, a full (non-incremental) stream will be estimated.
461 461 */
462 462 int
463 463 lzc_send_space(const char *snapname, const char *fromsnap, uint64_t *spacep)
464 464 {
465 465 nvlist_t *args;
466 466 nvlist_t *result;
467 467 int err;
468 468
469 469 args = fnvlist_alloc();
470 470 if (fromsnap != NULL)
471 471 fnvlist_add_string(args, "fromsnap", fromsnap);
472 472 err = lzc_ioctl(ZFS_IOC_SEND_SPACE, snapname, args, &result);
473 473 nvlist_free(args);
474 474 if (err == 0)
475 475 *spacep = fnvlist_lookup_uint64(result, "space");
476 476 nvlist_free(result);
477 477 return (err);
478 478 }
479 479
480 480 static int
481 481 recv_read(int fd, void *buf, int ilen)
482 482 {
483 483 char *cp = buf;
484 484 int rv;
485 485 int len = ilen;
486 486
487 487 do {
488 488 rv = read(fd, cp, len);
489 489 cp += rv;
490 490 len -= rv;
491 491 } while (rv > 0);
492 492
493 493 if (rv < 0 || len != 0)
494 494 return (EIO);
495 495
496 496 return (0);
497 497 }
498 498
499 499 /*
500 500 * The simplest receive case: receive from the specified fd, creating the
501 501 * specified snapshot. Apply the specified properties a "received" properties
502 502 * (which can be overridden by locally-set properties). If the stream is a
503 503 * clone, its origin snapshot must be specified by 'origin'. The 'force'
504 504 * flag will cause the target filesystem to be rolled back or destroyed if
505 505 * necessary to receive.
506 506 *
507 507 * Return 0 on success or an errno on failure.
508 508 *
509 509 * Note: this interface does not work on dedup'd streams
510 510 * (those with DMU_BACKUP_FEATURE_DEDUP).
511 511 */
512 512 int
513 513 lzc_receive(const char *snapname, nvlist_t *props, const char *origin,
514 514 boolean_t force, int fd)
515 515 {
516 516 /*
517 517 * The receive ioctl is still legacy, so we need to construct our own
518 518 * zfs_cmd_t rather than using zfsc_ioctl().
519 519 */
520 520 zfs_cmd_t zc = { 0 };
521 521 char *atp;
522 522 char *packed = NULL;
523 523 size_t size;
524 524 dmu_replay_record_t drr;
525 525 int error;
526 526
527 527 ASSERT3S(g_refcount, >, 0);
528 528
529 529 /* zc_name is name of containing filesystem */
530 530 (void) strlcpy(zc.zc_name, snapname, sizeof (zc.zc_name));
531 531 atp = strchr(zc.zc_name, '@');
532 532 if (atp == NULL)
533 533 return (EINVAL);
534 534 *atp = '\0';
535 535
536 536 /* if the fs does not exist, try its parent. */
537 537 if (!lzc_exists(zc.zc_name)) {
538 538 char *slashp = strrchr(zc.zc_name, '/');
539 539 if (slashp == NULL)
540 540 return (ENOENT);
541 541 *slashp = '\0';
542 542
543 543 }
544 544
545 545 /* zc_value is full name of the snapshot to create */
546 546 (void) strlcpy(zc.zc_value, snapname, sizeof (zc.zc_value));
547 547
548 548 if (props != NULL) {
549 549 /* zc_nvlist_src is props to set */
550 550 packed = fnvlist_pack(props, &size);
551 551 zc.zc_nvlist_src = (uint64_t)(uintptr_t)packed;
552 552 zc.zc_nvlist_src_size = size;
553 553 }
554 554
555 555 /* zc_string is name of clone origin (if DRR_FLAG_CLONE) */
556 556 if (origin != NULL)
557 557 (void) strlcpy(zc.zc_string, origin, sizeof (zc.zc_string));
558 558
559 559 /* zc_begin_record is non-byteswapped BEGIN record */
560 560 error = recv_read(fd, &drr, sizeof (drr));
561 561 if (error != 0)
562 562 goto out;
563 563 zc.zc_begin_record = drr.drr_u.drr_begin;
564 564
565 565 /* zc_cookie is fd to read from */
566 566 zc.zc_cookie = fd;
567 567
568 568 /* zc guid is force flag */
569 569 zc.zc_guid = force;
570 570
571 571 /* zc_cleanup_fd is unused */
572 572 zc.zc_cleanup_fd = -1;
↓ open down ↓ |
539 lines elided |
↑ open up ↑ |
573 573
574 574 error = ioctl(g_fd, ZFS_IOC_RECV, &zc);
575 575 if (error != 0)
576 576 error = errno;
577 577
578 578 out:
579 579 if (packed != NULL)
580 580 fnvlist_pack_free(packed, size);
581 581 free((void*)(uintptr_t)zc.zc_nvlist_dst);
582 582 return (error);
583 +}
584 +
585 +/*
586 + * Roll back this filesystem or volume to its most recent snapshot.
587 + * If snapnamebuf is not NULL, it will be filled in with the name
588 + * of the most recent snapshot.
589 + *
590 + * Return 0 on success or an errno on failure.
591 + */
592 +int
593 +lzc_rollback(const char *fsname, char *snapnamebuf, int snapnamelen)
594 +{
595 + nvlist_t *args;
596 + nvlist_t *result;
597 + int err;
598 +
599 + args = fnvlist_alloc();
600 + err = lzc_ioctl(ZFS_IOC_ROLLBACK, fsname, args, &result);
601 + nvlist_free(args);
602 + if (err == 0 && snapnamebuf != NULL) {
603 + const char *snapname = fnvlist_lookup_string(result, "target");
604 + (void) strlcpy(snapnamebuf, snapname, snapnamelen);
605 + }
606 + return (err);
583 607 }
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX