xref: /dragonfly/sys/vfs/hammer/hammer_ioctl.c (revision 6693db17)
1 /*
2  * Copyright (c) 2008 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Matthew Dillon <dillon@backplane.com>
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
11  * 1. Redistributions of source code must retain the above copyright
12  *    notice, this list of conditions and the following disclaimer.
13  * 2. Redistributions in binary form must reproduce the above copyright
14  *    notice, this list of conditions and the following disclaimer in
15  *    the documentation and/or other materials provided with the
16  *    distribution.
17  * 3. Neither the name of The DragonFly Project nor the names of its
18  *    contributors may be used to endorse or promote products derived
19  *    from this software without specific, prior written permission.
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24  * FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE
25  * COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26  * INCIDENTAL, SPECIAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES (INCLUDING,
27  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
29  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
31  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
32  * SUCH DAMAGE.
33  *
34  * $DragonFly: src/sys/vfs/hammer/hammer_ioctl.c,v 1.32 2008/11/13 02:23:29 dillon Exp $
35  */
36 
37 #include "hammer.h"
38 
39 static int hammer_ioc_gethistory(hammer_transaction_t trans, hammer_inode_t ip,
40 				struct hammer_ioc_history *hist);
41 static int hammer_ioc_synctid(hammer_transaction_t trans, hammer_inode_t ip,
42 				struct hammer_ioc_synctid *std);
43 static int hammer_ioc_get_version(hammer_transaction_t trans,
44 				hammer_inode_t ip,
45 				struct hammer_ioc_version *ver);
46 static int hammer_ioc_set_version(hammer_transaction_t trans,
47 				hammer_inode_t ip,
48 				struct hammer_ioc_version *ver);
49 static int hammer_ioc_get_info(hammer_transaction_t trans,
50 				struct hammer_ioc_info *info);
51 static int hammer_ioc_add_snapshot(hammer_transaction_t trans, hammer_inode_t ip,
52 				struct hammer_ioc_snapshot *snap);
53 static int hammer_ioc_del_snapshot(hammer_transaction_t trans, hammer_inode_t ip,
54 				struct hammer_ioc_snapshot *snap);
55 static int hammer_ioc_get_snapshot(hammer_transaction_t trans, hammer_inode_t ip,
56 				struct hammer_ioc_snapshot *snap);
57 static int hammer_ioc_get_config(hammer_transaction_t trans, hammer_inode_t ip,
58 				struct hammer_ioc_config *snap);
59 static int hammer_ioc_set_config(hammer_transaction_t trans, hammer_inode_t ip,
60 				struct hammer_ioc_config *snap);
61 
62 int
63 hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
64 	     struct ucred *cred)
65 {
66 	struct hammer_transaction trans;
67 	int error;
68 
69 	error = priv_check_cred(cred, PRIV_HAMMER_IOCTL, 0);
70 
71 	hammer_start_transaction(&trans, ip->hmp);
72 
73 	switch(com) {
74 	case HAMMERIOC_PRUNE:
75 		if (error == 0) {
76 			error = hammer_ioc_prune(&trans, ip,
77 					(struct hammer_ioc_prune *)data);
78 		}
79 		break;
80 	case HAMMERIOC_GETHISTORY:
81 		error = hammer_ioc_gethistory(&trans, ip,
82 					(struct hammer_ioc_history *)data);
83 		break;
84 	case HAMMERIOC_REBLOCK:
85 		if (error == 0) {
86 			error = hammer_ioc_reblock(&trans, ip,
87 					(struct hammer_ioc_reblock *)data);
88 		}
89 		break;
90 	case HAMMERIOC_REBALANCE:
91 		if (error == 0) {
92 			error = hammer_ioc_rebalance(&trans, ip,
93 					(struct hammer_ioc_rebalance *)data);
94 		}
95 		break;
96 	case HAMMERIOC_SYNCTID:
97 		error = hammer_ioc_synctid(&trans, ip,
98 					(struct hammer_ioc_synctid *)data);
99 		break;
100 	case HAMMERIOC_GET_PSEUDOFS:
101 		error = hammer_ioc_get_pseudofs(&trans, ip,
102 				    (struct hammer_ioc_pseudofs_rw *)data);
103 		break;
104 	case HAMMERIOC_SET_PSEUDOFS:
105 		if (error == 0) {
106 			error = hammer_ioc_set_pseudofs(&trans, ip, cred,
107 				    (struct hammer_ioc_pseudofs_rw *)data);
108 		}
109 		break;
110 	case HAMMERIOC_UPG_PSEUDOFS:
111 		if (error == 0) {
112 			error = hammer_ioc_upgrade_pseudofs(&trans, ip,
113 				    (struct hammer_ioc_pseudofs_rw *)data);
114 		}
115 		break;
116 	case HAMMERIOC_DGD_PSEUDOFS:
117 		if (error == 0) {
118 			error = hammer_ioc_downgrade_pseudofs(&trans, ip,
119 				    (struct hammer_ioc_pseudofs_rw *)data);
120 		}
121 		break;
122 	case HAMMERIOC_RMR_PSEUDOFS:
123 		if (error == 0) {
124 			error = hammer_ioc_destroy_pseudofs(&trans, ip,
125 				    (struct hammer_ioc_pseudofs_rw *)data);
126 		}
127 		break;
128 	case HAMMERIOC_WAI_PSEUDOFS:
129 		if (error == 0) {
130 			error = hammer_ioc_wait_pseudofs(&trans, ip,
131 				    (struct hammer_ioc_pseudofs_rw *)data);
132 		}
133 		break;
134 	case HAMMERIOC_MIRROR_READ:
135 		if (error == 0) {
136 			error = hammer_ioc_mirror_read(&trans, ip,
137 				    (struct hammer_ioc_mirror_rw *)data);
138 		}
139 		break;
140 	case HAMMERIOC_MIRROR_WRITE:
141 		if (error == 0) {
142 			error = hammer_ioc_mirror_write(&trans, ip,
143 				    (struct hammer_ioc_mirror_rw *)data);
144 		}
145 		break;
146 	case HAMMERIOC_GET_VERSION:
147 		error = hammer_ioc_get_version(&trans, ip,
148 				    (struct hammer_ioc_version *)data);
149 		break;
150 	case HAMMERIOC_GET_INFO:
151 		error = hammer_ioc_get_info(&trans,
152 				    (struct hammer_ioc_info *)data);
153 		break;
154 	case HAMMERIOC_SET_VERSION:
155 		if (error == 0) {
156 			error = hammer_ioc_set_version(&trans, ip,
157 					    (struct hammer_ioc_version *)data);
158 		}
159 		break;
160 	case HAMMERIOC_ADD_VOLUME:
161 		if (error == 0) {
162 			error = priv_check_cred(cred, PRIV_HAMMER_VOLUME, 0);
163 			if (error == 0)
164 				error = hammer_ioc_volume_add(&trans, ip,
165 					    (struct hammer_ioc_volume *)data);
166 		}
167 		break;
168 	case HAMMERIOC_DEL_VOLUME:
169 		if (error == 0) {
170 			error = priv_check_cred(cred, PRIV_HAMMER_VOLUME, 0);
171 			if (error == 0)
172 				error = hammer_ioc_volume_del(&trans, ip,
173 					    (struct hammer_ioc_volume *)data);
174 		}
175 		break;
176 	case HAMMERIOC_ADD_SNAPSHOT:
177 		if (error == 0) {
178 			error = hammer_ioc_add_snapshot(
179 					&trans, ip, (struct hammer_ioc_snapshot *)data);
180 		}
181 		break;
182 	case HAMMERIOC_DEL_SNAPSHOT:
183 		if (error == 0) {
184 			error = hammer_ioc_del_snapshot(
185 					&trans, ip, (struct hammer_ioc_snapshot *)data);
186 		}
187 		break;
188 	case HAMMERIOC_GET_SNAPSHOT:
189 		error = hammer_ioc_get_snapshot(
190 					&trans, ip, (struct hammer_ioc_snapshot *)data);
191 		break;
192 	case HAMMERIOC_GET_CONFIG:
193 		error = hammer_ioc_get_config(
194 					&trans, ip, (struct hammer_ioc_config *)data);
195 		break;
196 	case HAMMERIOC_SET_CONFIG:
197 		if (error == 0) {
198 			error = hammer_ioc_set_config(
199 					&trans, ip, (struct hammer_ioc_config *)data);
200 		}
201 		break;
202 	default:
203 		error = EOPNOTSUPP;
204 		break;
205 	}
206 	hammer_done_transaction(&trans);
207 	return (error);
208 }
209 
210 /*
211  * Iterate through an object's inode or an object's records and record
212  * modification TIDs.
213  */
214 static void add_history(hammer_inode_t ip, struct hammer_ioc_history *hist,
215 			hammer_btree_elm_t elm);
216 
217 static
218 int
219 hammer_ioc_gethistory(hammer_transaction_t trans, hammer_inode_t ip,
220 		      struct hammer_ioc_history *hist)
221 {
222 	struct hammer_cursor cursor;
223 	hammer_btree_elm_t elm;
224 	int error;
225 
226 	/*
227 	 * Validate the structure and initialize for return.
228 	 */
229 	if (hist->beg_tid > hist->end_tid)
230 		return(EINVAL);
231 	if (hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) {
232 		if (hist->key > hist->nxt_key)
233 			return(EINVAL);
234 	}
235 
236 	hist->obj_id = ip->obj_id;
237 	hist->count = 0;
238 	hist->nxt_tid = hist->end_tid;
239 	hist->head.flags &= ~HAMMER_IOC_HISTORY_NEXT_TID;
240 	hist->head.flags &= ~HAMMER_IOC_HISTORY_NEXT_KEY;
241 	hist->head.flags &= ~HAMMER_IOC_HISTORY_EOF;
242 	hist->head.flags &= ~HAMMER_IOC_HISTORY_UNSYNCED;
243 	if ((ip->flags & HAMMER_INODE_MODMASK) &
244 	    ~(HAMMER_INODE_ATIME | HAMMER_INODE_MTIME)) {
245 		hist->head.flags |= HAMMER_IOC_HISTORY_UNSYNCED;
246 	}
247 
248 	/*
249 	 * Setup the cursor.  We can't handle undeletable records
250 	 * (create_tid of 0) at the moment.  A create_tid of 0 has
251 	 * a special meaning and cannot be specified in the cursor.
252 	 */
253 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
254 	if (error) {
255 		hammer_done_cursor(&cursor);
256 		return(error);
257 	}
258 
259 	cursor.key_beg.obj_id = hist->obj_id;
260 	cursor.key_beg.create_tid = hist->beg_tid;
261 	cursor.key_beg.delete_tid = 0;
262 	cursor.key_beg.obj_type = 0;
263 	if (cursor.key_beg.create_tid == HAMMER_MIN_TID)
264 		cursor.key_beg.create_tid = 1;
265 
266 	cursor.key_end.obj_id = hist->obj_id;
267 	cursor.key_end.create_tid = hist->end_tid;
268 	cursor.key_end.delete_tid = 0;
269 	cursor.key_end.obj_type = 0;
270 
271 	cursor.flags |= HAMMER_CURSOR_END_EXCLUSIVE;
272 
273 	if (hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) {
274 		/*
275 		 * key-range within the file.  For a regular file the
276 		 * on-disk key represents BASE+LEN, not BASE, so the
277 		 * first possible record containing the offset 'key'
278 		 * has an on-disk key of (key + 1).
279 		 */
280 		cursor.key_beg.key = hist->key;
281 		cursor.key_end.key = HAMMER_MAX_KEY;
282 		cursor.key_beg.localization = ip->obj_localization +
283 					      HAMMER_LOCALIZE_MISC;
284 		cursor.key_end.localization = ip->obj_localization +
285 					      HAMMER_LOCALIZE_MISC;
286 
287 		switch(ip->ino_data.obj_type) {
288 		case HAMMER_OBJTYPE_REGFILE:
289 			++cursor.key_beg.key;
290 			cursor.key_beg.rec_type = HAMMER_RECTYPE_DATA;
291 			break;
292 		case HAMMER_OBJTYPE_DIRECTORY:
293 			cursor.key_beg.rec_type = HAMMER_RECTYPE_DIRENTRY;
294 			cursor.key_beg.localization = ip->obj_localization +
295 						hammer_dir_localization(ip);
296 			cursor.key_end.localization = ip->obj_localization +
297 						hammer_dir_localization(ip);
298 			break;
299 		case HAMMER_OBJTYPE_DBFILE:
300 			cursor.key_beg.rec_type = HAMMER_RECTYPE_DB;
301 			break;
302 		default:
303 			error = EINVAL;
304 			break;
305 		}
306 		cursor.key_end.rec_type = cursor.key_beg.rec_type;
307 	} else {
308 		/*
309 		 * The inode itself.
310 		 */
311 		cursor.key_beg.key = 0;
312 		cursor.key_end.key = 0;
313 		cursor.key_beg.rec_type = HAMMER_RECTYPE_INODE;
314 		cursor.key_end.rec_type = HAMMER_RECTYPE_INODE;
315 		cursor.key_beg.localization = ip->obj_localization +
316 					      HAMMER_LOCALIZE_INODE;
317 		cursor.key_end.localization = ip->obj_localization +
318 					      HAMMER_LOCALIZE_INODE;
319 	}
320 
321 	error = hammer_btree_first(&cursor);
322 	while (error == 0) {
323 		elm = &cursor.node->ondisk->elms[cursor.index];
324 
325 		add_history(ip, hist, elm);
326 		if (hist->head.flags & (HAMMER_IOC_HISTORY_NEXT_TID |
327 				        HAMMER_IOC_HISTORY_NEXT_KEY |
328 				        HAMMER_IOC_HISTORY_EOF)) {
329 			break;
330 		}
331 		error = hammer_btree_iterate(&cursor);
332 	}
333 	if (error == ENOENT) {
334 		hist->head.flags |= HAMMER_IOC_HISTORY_EOF;
335 		error = 0;
336 	}
337 	hammer_done_cursor(&cursor);
338 	return(error);
339 }
340 
341 /*
342  * Add the scanned element to the ioctl return structure.  Some special
343  * casing is required for regular files to accomodate how data ranges are
344  * stored on-disk.
345  */
346 static void
347 add_history(hammer_inode_t ip, struct hammer_ioc_history *hist,
348 	    hammer_btree_elm_t elm)
349 {
350 	int i;
351 
352 	if (elm->base.btype != HAMMER_BTREE_TYPE_RECORD)
353 		return;
354 	if ((hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) &&
355 	    ip->ino_data.obj_type == HAMMER_OBJTYPE_REGFILE) {
356 		/*
357 		 * Adjust nxt_key
358 		 */
359 		if (hist->nxt_key > elm->leaf.base.key - elm->leaf.data_len &&
360 		    hist->key < elm->leaf.base.key - elm->leaf.data_len) {
361 			hist->nxt_key = elm->leaf.base.key - elm->leaf.data_len;
362 		}
363 		if (hist->nxt_key > elm->leaf.base.key)
364 			hist->nxt_key = elm->leaf.base.key;
365 
366 		/*
367 		 * Record is beyond MAXPHYS, there won't be any more records
368 		 * in the iteration covering the requested offset (key).
369 		 */
370 		if (elm->leaf.base.key >= MAXPHYS &&
371 		    elm->leaf.base.key - MAXPHYS > hist->key) {
372 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_KEY;
373 		}
374 
375 		/*
376 		 * Data-range of record does not cover the key.
377 		 */
378 		if (elm->leaf.base.key - elm->leaf.data_len > hist->key)
379 			return;
380 
381 	} else if (hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) {
382 		/*
383 		 * Adjust nxt_key
384 		 */
385 		if (hist->nxt_key > elm->leaf.base.key &&
386 		    hist->key < elm->leaf.base.key) {
387 			hist->nxt_key = elm->leaf.base.key;
388 		}
389 
390 		/*
391 		 * Record is beyond the requested key.
392 		 */
393 		if (elm->leaf.base.key > hist->key)
394 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_KEY;
395 	}
396 
397 	/*
398 	 * Add create_tid if it is in-bounds.
399 	 */
400 	i = hist->count;
401 	if ((i == 0 ||
402 	     elm->leaf.base.create_tid != hist->hist_ary[i - 1].tid) &&
403 	    elm->leaf.base.create_tid >= hist->beg_tid &&
404 	    elm->leaf.base.create_tid < hist->end_tid) {
405 		if (hist->count == HAMMER_MAX_HISTORY_ELMS) {
406 			hist->nxt_tid = elm->leaf.base.create_tid;
407 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_TID;
408 			return;
409 		}
410 		hist->hist_ary[i].tid = elm->leaf.base.create_tid;
411 		hist->hist_ary[i].time32 = elm->leaf.create_ts;
412 		++hist->count;
413 	}
414 
415 	/*
416 	 * Add delete_tid if it is in-bounds.  Note that different portions
417 	 * of the history may have overlapping data ranges with different
418 	 * delete_tid's.  If this case occurs the delete_tid may match the
419 	 * create_tid of a following record.  XXX
420 	 *
421 	 *	[        ]
422 	 *            [     ]
423 	 */
424 	i = hist->count;
425 	if (elm->leaf.base.delete_tid &&
426 	    elm->leaf.base.delete_tid >= hist->beg_tid &&
427 	    elm->leaf.base.delete_tid < hist->end_tid) {
428 		if (i == HAMMER_MAX_HISTORY_ELMS) {
429 			hist->nxt_tid = elm->leaf.base.delete_tid;
430 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_TID;
431 			return;
432 		}
433 		hist->hist_ary[i].tid = elm->leaf.base.delete_tid;
434 		hist->hist_ary[i].time32 = elm->leaf.delete_ts;
435 		++hist->count;
436 	}
437 }
438 
439 /*
440  * Acquire synchronization TID
441  */
442 static
443 int
444 hammer_ioc_synctid(hammer_transaction_t trans, hammer_inode_t ip,
445 		   struct hammer_ioc_synctid *std)
446 {
447 	hammer_mount_t hmp = ip->hmp;
448 	int error = 0;
449 
450 	switch(std->op) {
451 	case HAMMER_SYNCTID_NONE:
452 		std->tid = hmp->flusher.tid;	/* inaccurate */
453 		break;
454 	case HAMMER_SYNCTID_ASYNC:
455 		hammer_queue_inodes_flusher(hmp, MNT_NOWAIT);
456 		hammer_flusher_async(hmp, NULL);
457 		std->tid = hmp->flusher.tid;	/* inaccurate */
458 		break;
459 	case HAMMER_SYNCTID_SYNC1:
460 		hammer_queue_inodes_flusher(hmp, MNT_WAIT);
461 		hammer_flusher_sync(hmp);
462 		std->tid = hmp->flusher.tid;
463 		break;
464 	case HAMMER_SYNCTID_SYNC2:
465 		hammer_queue_inodes_flusher(hmp, MNT_WAIT);
466 		hammer_flusher_sync(hmp);
467 		std->tid = hmp->flusher.tid;
468 		hammer_flusher_sync(hmp);
469 		break;
470 	default:
471 		error = EOPNOTSUPP;
472 		break;
473 	}
474 	return(error);
475 }
476 
477 /*
478  * Retrieve version info.
479  *
480  * Load min_version, wip_version, and max_versino.  If cur_version is passed
481  * as 0 then load the current version into cur_version.  Load the description
482  * for cur_version into the description array.
483  *
484  * Returns 0 on success, EINVAL if cur_version is non-zero and set to an
485  * unsupported value.
486  */
487 static
488 int
489 hammer_ioc_get_version(hammer_transaction_t trans, hammer_inode_t ip,
490 		   struct hammer_ioc_version *ver)
491 {
492 	int error = 0;
493 
494 	ver->min_version = HAMMER_VOL_VERSION_MIN;
495 	ver->wip_version = HAMMER_VOL_VERSION_WIP;
496 	ver->max_version = HAMMER_VOL_VERSION_MAX;
497 	if (ver->cur_version == 0)
498 		ver->cur_version = trans->hmp->version;
499 	switch(ver->cur_version) {
500 	case 1:
501 		ksnprintf(ver->description, sizeof(ver->description),
502 			 "First HAMMER release (DragonFly 2.0+)");
503 		break;
504 	case 2:
505 		ksnprintf(ver->description, sizeof(ver->description),
506 			 "New directory entry layout (DragonFly 2.3+)");
507 		break;
508 	case 3:
509 		ksnprintf(ver->description, sizeof(ver->description),
510 			 "New snapshot management (DragonFly 2.5+)");
511 		break;
512 	case 4:
513 		ksnprintf(ver->description, sizeof(ver->description),
514 			 "New undo/flush, faster flush/sync (DragonFly 2.5+)");
515 		break;
516 	default:
517 		ksnprintf(ver->description, sizeof(ver->description),
518 			 "Unknown");
519 		error = EINVAL;
520 		break;
521 	}
522 	return(error);
523 };
524 
525 /*
526  * Set version info
527  */
528 static
529 int
530 hammer_ioc_set_version(hammer_transaction_t trans, hammer_inode_t ip,
531 		   struct hammer_ioc_version *ver)
532 {
533 	hammer_mount_t hmp = trans->hmp;
534 	struct hammer_cursor cursor;
535 	hammer_volume_t volume;
536 	int error;
537 	int over = hmp->version;
538 
539 	/*
540 	 * Generally do not allow downgrades.  However, version 4 can
541 	 * be downgraded to version 3.
542 	 */
543 	if (ver->cur_version < hmp->version) {
544 		if (!(ver->cur_version == 3 && hmp->version == 4))
545 			return(EINVAL);
546 	}
547 	if (ver->cur_version == hmp->version)
548 		return(0);
549 	if (ver->cur_version > HAMMER_VOL_VERSION_MAX)
550 		return(EINVAL);
551 	if (hmp->ronly)
552 		return(EROFS);
553 
554 	/*
555 	 * Update the root volume header and the version cached in
556 	 * the hammer_mount structure.
557 	 */
558 	error = hammer_init_cursor(trans, &cursor, NULL, NULL);
559 	if (error)
560 		goto failed;
561 	hammer_lock_ex(&hmp->flusher.finalize_lock);
562 	hammer_sync_lock_ex(trans);
563 	hmp->version = ver->cur_version;
564 
565 	/*
566 	 * If upgrading from version < 4 to version >= 4 the UNDO FIFO
567 	 * must be reinitialized.
568 	 */
569 	if (over < HAMMER_VOL_VERSION_FOUR &&
570 	    ver->cur_version >= HAMMER_VOL_VERSION_FOUR) {
571 		kprintf("upgrade undo to version 4\n");
572 		error = hammer_upgrade_undo_4(trans);
573 		if (error)
574 			goto failed;
575 	}
576 
577 	/*
578 	 * Adjust the version in the volume header
579 	 */
580 	volume = hammer_get_root_volume(hmp, &error);
581 	KKASSERT(error == 0);
582 	hammer_modify_volume_field(cursor.trans, volume, vol_version);
583 	volume->ondisk->vol_version = ver->cur_version;
584 	hammer_modify_volume_done(volume);
585 	hammer_rel_volume(volume, 0);
586 
587 	hammer_sync_unlock(trans);
588 	hammer_unlock(&hmp->flusher.finalize_lock);
589 failed:
590 	ver->head.error = error;
591 	hammer_done_cursor(&cursor);
592 	return(0);
593 }
594 
595 /*
596  * Get information
597  */
598 static
599 int
600 hammer_ioc_get_info(hammer_transaction_t trans, struct hammer_ioc_info *info) {
601 
602 	struct hammer_volume_ondisk	*od = trans->hmp->rootvol->ondisk;
603 	struct hammer_mount 		*hm = trans->hmp;
604 
605 	/* Fill the structure with the necessary information */
606 	_hammer_checkspace(hm, HAMMER_CHKSPC_WRITE, &info->rsvbigblocks);
607 	info->rsvbigblocks = info->rsvbigblocks >> HAMMER_LARGEBLOCK_BITS;
608 	strlcpy(info->vol_name, od->vol_name, sizeof(od->vol_name));
609 
610 	info->vol_fsid = hm->fsid;
611 	info->vol_fstype = od->vol_fstype;
612 	info->version = hm->version;
613 
614 	info->inodes = od->vol0_stat_inodes;
615 	info->bigblocks = od->vol0_stat_bigblocks;
616 	info->freebigblocks = od->vol0_stat_freebigblocks;
617 	info->nvolumes = hm->nvolumes;
618 
619 	return 0;
620 }
621 
622 /*
623  * Add a snapshot transction id(s) to the list of snapshots.
624  *
625  * NOTE: Records are created with an allocated TID.  If a flush cycle
626  *	 is in progress the record may be synced in the current flush
627  *	 cycle and the volume header will reflect the allocation of the
628  *	 TID, but the synchronization point may not catch up to the
629  *	 TID until the next flush cycle.
630  */
631 static
632 int
633 hammer_ioc_add_snapshot(hammer_transaction_t trans, hammer_inode_t ip,
634 			struct hammer_ioc_snapshot *snap)
635 {
636 	hammer_mount_t hmp = ip->hmp;
637 	struct hammer_btree_leaf_elm leaf;
638 	struct hammer_cursor cursor;
639 	int error;
640 
641 	/*
642 	 * Validate structure
643 	 */
644 	if (snap->count > HAMMER_SNAPS_PER_IOCTL)
645 		return (EINVAL);
646 	if (snap->index > snap->count)
647 		return (EINVAL);
648 
649 	hammer_lock_ex(&hmp->snapshot_lock);
650 again:
651 	/*
652 	 * Look for keys starting after the previous iteration, or at
653 	 * the beginning if snap->count is 0.
654 	 */
655 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
656 	if (error) {
657 		hammer_done_cursor(&cursor);
658 		return(error);
659 	}
660 
661 	cursor.asof = HAMMER_MAX_TID;
662 	cursor.flags |= HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF;
663 
664 	bzero(&leaf, sizeof(leaf));
665 	leaf.base.obj_id = HAMMER_OBJID_ROOT;
666 	leaf.base.rec_type = HAMMER_RECTYPE_SNAPSHOT;
667 	leaf.base.create_tid = hammer_alloc_tid(hmp, 1);
668 	leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
669 	leaf.base.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
670 	leaf.data_len = sizeof(struct hammer_snapshot_data);
671 
672 	while (snap->index < snap->count) {
673 		leaf.base.key = (int64_t)snap->snaps[snap->index].tid;
674 		cursor.key_beg = leaf.base;
675 		error = hammer_btree_lookup(&cursor);
676 		if (error == 0) {
677 			error = EEXIST;
678 			break;
679 		}
680 
681 		cursor.flags &= ~HAMMER_CURSOR_ASOF;
682 		error = hammer_create_at_cursor(&cursor, &leaf,
683 						&snap->snaps[snap->index],
684 						HAMMER_CREATE_MODE_SYS);
685 		if (error == EDEADLK) {
686 			hammer_done_cursor(&cursor);
687 			goto again;
688 		}
689 		cursor.flags |= HAMMER_CURSOR_ASOF;
690 		if (error)
691 			break;
692 		++snap->index;
693 	}
694 	snap->head.error = error;
695 	hammer_done_cursor(&cursor);
696 	hammer_unlock(&hmp->snapshot_lock);
697 	return(0);
698 }
699 
700 /*
701  * Delete snapshot transaction id(s) from the list of snapshots.
702  */
703 static
704 int
705 hammer_ioc_del_snapshot(hammer_transaction_t trans, hammer_inode_t ip,
706 			struct hammer_ioc_snapshot *snap)
707 {
708 	hammer_mount_t hmp = ip->hmp;
709 	struct hammer_cursor cursor;
710 	int error;
711 
712 	/*
713 	 * Validate structure
714 	 */
715 	if (snap->count > HAMMER_SNAPS_PER_IOCTL)
716 		return (EINVAL);
717 	if (snap->index > snap->count)
718 		return (EINVAL);
719 
720 	hammer_lock_ex(&hmp->snapshot_lock);
721 again:
722 	/*
723 	 * Look for keys starting after the previous iteration, or at
724 	 * the beginning if snap->count is 0.
725 	 */
726 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
727 	if (error) {
728 		hammer_done_cursor(&cursor);
729 		return(error);
730 	}
731 
732 	cursor.key_beg.obj_id = HAMMER_OBJID_ROOT;
733 	cursor.key_beg.create_tid = 0;
734 	cursor.key_beg.delete_tid = 0;
735 	cursor.key_beg.obj_type = 0;
736 	cursor.key_beg.rec_type = HAMMER_RECTYPE_SNAPSHOT;
737 	cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
738 	cursor.asof = HAMMER_MAX_TID;
739 	cursor.flags |= HAMMER_CURSOR_ASOF;
740 
741 	while (snap->index < snap->count) {
742 		cursor.key_beg.key = (int64_t)snap->snaps[snap->index].tid;
743 		error = hammer_btree_lookup(&cursor);
744 		if (error)
745 			break;
746 		error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF);
747 		if (error)
748 			break;
749 		error = hammer_delete_at_cursor(&cursor, HAMMER_DELETE_DESTROY,
750 						0, 0, 0, NULL);
751 		if (error == EDEADLK) {
752 			hammer_done_cursor(&cursor);
753 			goto again;
754 		}
755 		if (error)
756 			break;
757 		++snap->index;
758 	}
759 	snap->head.error = error;
760 	hammer_done_cursor(&cursor);
761 	hammer_unlock(&hmp->snapshot_lock);
762 	return(0);
763 }
764 
765 /*
766  * Retrieve as many snapshot ids as possible or until the array is
767  * full, starting after the last transction id passed in.  If count
768  * is 0 we retrieve starting at the beginning.
769  *
770  * NOTE: Because the b-tree key field is signed but transaction ids
771  *       are unsigned the returned list will be signed-sorted instead
772  *	 of unsigned sorted.  The Caller must still sort the aggregate
773  *	 results.
774  */
775 static
776 int
777 hammer_ioc_get_snapshot(hammer_transaction_t trans, hammer_inode_t ip,
778 			struct hammer_ioc_snapshot *snap)
779 {
780 	struct hammer_cursor cursor;
781 	int error;
782 
783 	/*
784 	 * Validate structure
785 	 */
786 	if (snap->index != 0)
787 		return (EINVAL);
788 	if (snap->count > HAMMER_SNAPS_PER_IOCTL)
789 		return (EINVAL);
790 
791 	/*
792 	 * Look for keys starting after the previous iteration, or at
793 	 * the beginning if snap->count is 0.
794 	 */
795 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
796 	if (error) {
797 		hammer_done_cursor(&cursor);
798 		return(error);
799 	}
800 
801 	cursor.key_beg.obj_id = HAMMER_OBJID_ROOT;
802 	cursor.key_beg.create_tid = 0;
803 	cursor.key_beg.delete_tid = 0;
804 	cursor.key_beg.obj_type = 0;
805 	cursor.key_beg.rec_type = HAMMER_RECTYPE_SNAPSHOT;
806 	cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
807 	if (snap->count == 0)
808 		cursor.key_beg.key = HAMMER_MIN_KEY;
809 	else
810 		cursor.key_beg.key = (int64_t)snap->snaps[snap->count - 1].tid + 1;
811 
812 	cursor.key_end = cursor.key_beg;
813 	cursor.key_end.key = HAMMER_MAX_KEY;
814 	cursor.asof = HAMMER_MAX_TID;
815 	cursor.flags |= HAMMER_CURSOR_END_EXCLUSIVE | HAMMER_CURSOR_ASOF;
816 
817 	snap->count = 0;
818 
819 	error = hammer_btree_first(&cursor);
820 	while (error == 0 && snap->count < HAMMER_SNAPS_PER_IOCTL) {
821 		error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF);
822 		if (error)
823 			break;
824 		if (cursor.leaf->base.rec_type == HAMMER_RECTYPE_SNAPSHOT) {
825 			error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF |
826 							      HAMMER_CURSOR_GET_DATA);
827 			snap->snaps[snap->count] = cursor.data->snap;
828 			++snap->count;
829 		}
830 		error = hammer_btree_iterate(&cursor);
831 	}
832 
833 	if (error == ENOENT) {
834 		snap->head.flags |= HAMMER_IOC_SNAPSHOT_EOF;
835 		error = 0;
836 	}
837 	snap->head.error = error;
838 	hammer_done_cursor(&cursor);
839 	return(0);
840 }
841 
842 /*
843  * Retrieve the PFS hammer cleanup utility config record.  This is
844  * different (newer than) the PFS config.
845  */
846 static
847 int
848 hammer_ioc_get_config(hammer_transaction_t trans, hammer_inode_t ip,
849 			struct hammer_ioc_config *config)
850 {
851 	struct hammer_cursor cursor;
852 	int error;
853 
854 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
855 	if (error) {
856 		hammer_done_cursor(&cursor);
857 		return(error);
858 	}
859 
860 	cursor.key_beg.obj_id = HAMMER_OBJID_ROOT;
861 	cursor.key_beg.create_tid = 0;
862 	cursor.key_beg.delete_tid = 0;
863 	cursor.key_beg.obj_type = 0;
864 	cursor.key_beg.rec_type = HAMMER_RECTYPE_CONFIG;
865 	cursor.key_beg.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
866 	cursor.key_beg.key = 0;		/* config space page 0 */
867 
868 	cursor.asof = HAMMER_MAX_TID;
869 	cursor.flags |= HAMMER_CURSOR_ASOF;
870 
871 	error = hammer_btree_lookup(&cursor);
872 	if (error == 0) {
873 		error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF |
874 						      HAMMER_CURSOR_GET_DATA);
875 		if (error == 0)
876 			config->config = cursor.data->config;
877 	}
878 	/* error can be ENOENT */
879 	config->head.error = error;
880 	hammer_done_cursor(&cursor);
881 	return(0);
882 }
883 
884 /*
885  * Retrieve the PFS hammer cleanup utility config record.  This is
886  * different (newer than) the PFS config.
887  *
888  * This is kinda a hack.
889  */
890 static
891 int
892 hammer_ioc_set_config(hammer_transaction_t trans, hammer_inode_t ip,
893 			struct hammer_ioc_config *config)
894 {
895 	struct hammer_btree_leaf_elm leaf;
896 	struct hammer_cursor cursor;
897 	hammer_mount_t hmp = ip->hmp;
898 	int error;
899 
900 again:
901 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
902 	if (error) {
903 		hammer_done_cursor(&cursor);
904 		return(error);
905 	}
906 
907 	bzero(&leaf, sizeof(leaf));
908 	leaf.base.obj_id = HAMMER_OBJID_ROOT;
909 	leaf.base.rec_type = HAMMER_RECTYPE_CONFIG;
910 	leaf.base.create_tid = hammer_alloc_tid(hmp, 1);
911 	leaf.base.btype = HAMMER_BTREE_TYPE_RECORD;
912 	leaf.base.localization = ip->obj_localization + HAMMER_LOCALIZE_INODE;
913 	leaf.base.key = 0;	/* page 0 */
914 	leaf.data_len = sizeof(struct hammer_config_data);
915 
916 	cursor.key_beg = leaf.base;
917 
918 	cursor.asof = HAMMER_MAX_TID;
919 	cursor.flags |= HAMMER_CURSOR_BACKEND | HAMMER_CURSOR_ASOF;
920 
921 	error = hammer_btree_lookup(&cursor);
922 	if (error == 0) {
923 		error = hammer_btree_extract(&cursor, HAMMER_CURSOR_GET_LEAF |
924 						      HAMMER_CURSOR_GET_DATA);
925 		error = hammer_delete_at_cursor(&cursor, HAMMER_DELETE_DESTROY,
926 						0, 0, 0, NULL);
927 		if (error == EDEADLK) {
928 			hammer_done_cursor(&cursor);
929 			goto again;
930 		}
931 	}
932 	if (error == ENOENT)
933 		error = 0;
934 	if (error == 0) {
935 		cursor.flags &= ~HAMMER_CURSOR_ASOF;
936 		cursor.key_beg = leaf.base;
937 		error = hammer_create_at_cursor(&cursor, &leaf,
938 						&config->config,
939 						HAMMER_CREATE_MODE_SYS);
940 		if (error == EDEADLK) {
941 			hammer_done_cursor(&cursor);
942 			goto again;
943 		}
944 	}
945 	config->head.error = error;
946 	hammer_done_cursor(&cursor);
947 	return(0);
948 }
949