xref: /dragonfly/sys/vfs/hammer/hammer_ioctl.c (revision 9ddb8543)
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 
52 
53 
54 int
55 hammer_ioctl(hammer_inode_t ip, u_long com, caddr_t data, int fflag,
56 	     struct ucred *cred)
57 {
58 	struct hammer_transaction trans;
59 	int error;
60 
61 	error = priv_check_cred(cred, PRIV_HAMMER_IOCTL, 0);
62 
63 	hammer_start_transaction(&trans, ip->hmp);
64 
65 	switch(com) {
66 	case HAMMERIOC_PRUNE:
67 		if (error == 0) {
68 			error = hammer_ioc_prune(&trans, ip,
69 					(struct hammer_ioc_prune *)data);
70 		}
71 		break;
72 	case HAMMERIOC_GETHISTORY:
73 		error = hammer_ioc_gethistory(&trans, ip,
74 					(struct hammer_ioc_history *)data);
75 		break;
76 	case HAMMERIOC_REBLOCK:
77 		if (error == 0) {
78 			error = hammer_ioc_reblock(&trans, ip,
79 					(struct hammer_ioc_reblock *)data);
80 		}
81 		break;
82 	case HAMMERIOC_REBALANCE:
83 		if (error == 0) {
84 			error = hammer_ioc_rebalance(&trans, ip,
85 					(struct hammer_ioc_rebalance *)data);
86 		}
87 		break;
88 	case HAMMERIOC_SYNCTID:
89 		error = hammer_ioc_synctid(&trans, ip,
90 					(struct hammer_ioc_synctid *)data);
91 		break;
92 	case HAMMERIOC_GET_PSEUDOFS:
93 		error = hammer_ioc_get_pseudofs(&trans, ip,
94 				    (struct hammer_ioc_pseudofs_rw *)data);
95 		break;
96 	case HAMMERIOC_SET_PSEUDOFS:
97 		if (error == 0) {
98 			error = hammer_ioc_set_pseudofs(&trans, ip, cred,
99 				    (struct hammer_ioc_pseudofs_rw *)data);
100 		}
101 		break;
102 	case HAMMERIOC_UPG_PSEUDOFS:
103 		if (error == 0) {
104 			error = hammer_ioc_upgrade_pseudofs(&trans, ip,
105 				    (struct hammer_ioc_pseudofs_rw *)data);
106 		}
107 		break;
108 	case HAMMERIOC_DGD_PSEUDOFS:
109 		if (error == 0) {
110 			error = hammer_ioc_downgrade_pseudofs(&trans, ip,
111 				    (struct hammer_ioc_pseudofs_rw *)data);
112 		}
113 		break;
114 	case HAMMERIOC_RMR_PSEUDOFS:
115 		if (error == 0) {
116 			error = hammer_ioc_destroy_pseudofs(&trans, ip,
117 				    (struct hammer_ioc_pseudofs_rw *)data);
118 		}
119 		break;
120 	case HAMMERIOC_WAI_PSEUDOFS:
121 		if (error == 0) {
122 			error = hammer_ioc_wait_pseudofs(&trans, ip,
123 				    (struct hammer_ioc_pseudofs_rw *)data);
124 		}
125 		break;
126 	case HAMMERIOC_MIRROR_READ:
127 		if (error == 0) {
128 			error = hammer_ioc_mirror_read(&trans, ip,
129 				    (struct hammer_ioc_mirror_rw *)data);
130 		}
131 		break;
132 	case HAMMERIOC_MIRROR_WRITE:
133 		if (error == 0) {
134 			error = hammer_ioc_mirror_write(&trans, ip,
135 				    (struct hammer_ioc_mirror_rw *)data);
136 		}
137 		break;
138 	case HAMMERIOC_GET_VERSION:
139 		error = hammer_ioc_get_version(&trans, ip,
140 				    (struct hammer_ioc_version *)data);
141 		break;
142 	case HAMMERIOC_GET_INFO:
143 		error = hammer_ioc_get_info(&trans,
144 				    (struct hammer_ioc_info *)data);
145 		break;
146 	case HAMMERIOC_SET_VERSION:
147 		if (error == 0) {
148 			error = hammer_ioc_set_version(&trans, ip,
149 					    (struct hammer_ioc_version *)data);
150 		}
151 		break;
152 	case HAMMERIOC_EXPAND:
153 		if (error == 0) {
154 			error = priv_check_cred(cred, PRIV_HAMMER_EXPAND, 0);
155 			if (error == 0)
156 				error = hammer_ioc_expand(&trans, ip,
157 					    (struct hammer_ioc_expand *)data);
158 		}
159 		break;
160 
161 	default:
162 		error = EOPNOTSUPP;
163 		break;
164 	}
165 	hammer_done_transaction(&trans);
166 	return (error);
167 }
168 
169 /*
170  * Iterate through an object's inode or an object's records and record
171  * modification TIDs.
172  */
173 static void add_history(hammer_inode_t ip, struct hammer_ioc_history *hist,
174 			hammer_btree_elm_t elm);
175 
176 static
177 int
178 hammer_ioc_gethistory(hammer_transaction_t trans, hammer_inode_t ip,
179 		      struct hammer_ioc_history *hist)
180 {
181 	struct hammer_cursor cursor;
182 	hammer_btree_elm_t elm;
183 	int error;
184 
185 	/*
186 	 * Validate the structure and initialize for return.
187 	 */
188 	if (hist->beg_tid > hist->end_tid)
189 		return(EINVAL);
190 	if (hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) {
191 		if (hist->key > hist->nxt_key)
192 			return(EINVAL);
193 	}
194 
195 	hist->obj_id = ip->obj_id;
196 	hist->count = 0;
197 	hist->nxt_tid = hist->end_tid;
198 	hist->head.flags &= ~HAMMER_IOC_HISTORY_NEXT_TID;
199 	hist->head.flags &= ~HAMMER_IOC_HISTORY_NEXT_KEY;
200 	hist->head.flags &= ~HAMMER_IOC_HISTORY_EOF;
201 	hist->head.flags &= ~HAMMER_IOC_HISTORY_UNSYNCED;
202 	if ((ip->flags & HAMMER_INODE_MODMASK) &
203 	    ~(HAMMER_INODE_ATIME | HAMMER_INODE_MTIME)) {
204 		hist->head.flags |= HAMMER_IOC_HISTORY_UNSYNCED;
205 	}
206 
207 	/*
208 	 * Setup the cursor.  We can't handle undeletable records
209 	 * (create_tid of 0) at the moment.  A create_tid of 0 has
210 	 * a special meaning and cannot be specified in the cursor.
211 	 */
212 	error = hammer_init_cursor(trans, &cursor, &ip->cache[0], NULL);
213 	if (error) {
214 		hammer_done_cursor(&cursor);
215 		return(error);
216 	}
217 
218 	cursor.key_beg.obj_id = hist->obj_id;
219 	cursor.key_beg.create_tid = hist->beg_tid;
220 	cursor.key_beg.delete_tid = 0;
221 	cursor.key_beg.obj_type = 0;
222 	if (cursor.key_beg.create_tid == HAMMER_MIN_TID)
223 		cursor.key_beg.create_tid = 1;
224 
225 	cursor.key_end.obj_id = hist->obj_id;
226 	cursor.key_end.create_tid = hist->end_tid;
227 	cursor.key_end.delete_tid = 0;
228 	cursor.key_end.obj_type = 0;
229 
230 	cursor.flags |= HAMMER_CURSOR_END_EXCLUSIVE;
231 
232 	if (hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) {
233 		/*
234 		 * key-range within the file.  For a regular file the
235 		 * on-disk key represents BASE+LEN, not BASE, so the
236 		 * first possible record containing the offset 'key'
237 		 * has an on-disk key of (key + 1).
238 		 */
239 		cursor.key_beg.key = hist->key;
240 		cursor.key_end.key = HAMMER_MAX_KEY;
241 		cursor.key_beg.localization = ip->obj_localization +
242 					      HAMMER_LOCALIZE_MISC;
243 		cursor.key_end.localization = ip->obj_localization +
244 					      HAMMER_LOCALIZE_MISC;
245 
246 		switch(ip->ino_data.obj_type) {
247 		case HAMMER_OBJTYPE_REGFILE:
248 			++cursor.key_beg.key;
249 			cursor.key_beg.rec_type = HAMMER_RECTYPE_DATA;
250 			break;
251 		case HAMMER_OBJTYPE_DIRECTORY:
252 			cursor.key_beg.rec_type = HAMMER_RECTYPE_DIRENTRY;
253 			cursor.key_beg.localization = ip->obj_localization +
254 						hammer_dir_localization(ip);
255 			cursor.key_end.localization = ip->obj_localization +
256 						hammer_dir_localization(ip);
257 			break;
258 		case HAMMER_OBJTYPE_DBFILE:
259 			cursor.key_beg.rec_type = HAMMER_RECTYPE_DB;
260 			break;
261 		default:
262 			error = EINVAL;
263 			break;
264 		}
265 		cursor.key_end.rec_type = cursor.key_beg.rec_type;
266 	} else {
267 		/*
268 		 * The inode itself.
269 		 */
270 		cursor.key_beg.key = 0;
271 		cursor.key_end.key = 0;
272 		cursor.key_beg.rec_type = HAMMER_RECTYPE_INODE;
273 		cursor.key_end.rec_type = HAMMER_RECTYPE_INODE;
274 		cursor.key_beg.localization = ip->obj_localization +
275 					      HAMMER_LOCALIZE_INODE;
276 		cursor.key_end.localization = ip->obj_localization +
277 					      HAMMER_LOCALIZE_INODE;
278 	}
279 
280 	error = hammer_btree_first(&cursor);
281 	while (error == 0) {
282 		elm = &cursor.node->ondisk->elms[cursor.index];
283 
284 		add_history(ip, hist, elm);
285 		if (hist->head.flags & (HAMMER_IOC_HISTORY_NEXT_TID |
286 				        HAMMER_IOC_HISTORY_NEXT_KEY |
287 				        HAMMER_IOC_HISTORY_EOF)) {
288 			break;
289 		}
290 		error = hammer_btree_iterate(&cursor);
291 	}
292 	if (error == ENOENT) {
293 		hist->head.flags |= HAMMER_IOC_HISTORY_EOF;
294 		error = 0;
295 	}
296 	hammer_done_cursor(&cursor);
297 	return(error);
298 }
299 
300 /*
301  * Add the scanned element to the ioctl return structure.  Some special
302  * casing is required for regular files to accomodate how data ranges are
303  * stored on-disk.
304  */
305 static void
306 add_history(hammer_inode_t ip, struct hammer_ioc_history *hist,
307 	    hammer_btree_elm_t elm)
308 {
309 	int i;
310 
311 	if (elm->base.btype != HAMMER_BTREE_TYPE_RECORD)
312 		return;
313 	if ((hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) &&
314 	    ip->ino_data.obj_type == HAMMER_OBJTYPE_REGFILE) {
315 		/*
316 		 * Adjust nxt_key
317 		 */
318 		if (hist->nxt_key > elm->leaf.base.key - elm->leaf.data_len &&
319 		    hist->key < elm->leaf.base.key - elm->leaf.data_len) {
320 			hist->nxt_key = elm->leaf.base.key - elm->leaf.data_len;
321 		}
322 		if (hist->nxt_key > elm->leaf.base.key)
323 			hist->nxt_key = elm->leaf.base.key;
324 
325 		/*
326 		 * Record is beyond MAXPHYS, there won't be any more records
327 		 * in the iteration covering the requested offset (key).
328 		 */
329 		if (elm->leaf.base.key >= MAXPHYS &&
330 		    elm->leaf.base.key - MAXPHYS > hist->key) {
331 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_KEY;
332 		}
333 
334 		/*
335 		 * Data-range of record does not cover the key.
336 		 */
337 		if (elm->leaf.base.key - elm->leaf.data_len > hist->key)
338 			return;
339 
340 	} else if (hist->head.flags & HAMMER_IOC_HISTORY_ATKEY) {
341 		/*
342 		 * Adjust nxt_key
343 		 */
344 		if (hist->nxt_key > elm->leaf.base.key &&
345 		    hist->key < elm->leaf.base.key) {
346 			hist->nxt_key = elm->leaf.base.key;
347 		}
348 
349 		/*
350 		 * Record is beyond the requested key.
351 		 */
352 		if (elm->leaf.base.key > hist->key)
353 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_KEY;
354 	}
355 
356 	/*
357 	 * Add create_tid if it is in-bounds.
358 	 */
359 	i = hist->count;
360 	if ((i == 0 ||
361 	     elm->leaf.base.create_tid != hist->hist_ary[i - 1].tid) &&
362 	    elm->leaf.base.create_tid >= hist->beg_tid &&
363 	    elm->leaf.base.create_tid < hist->end_tid) {
364 		if (hist->count == HAMMER_MAX_HISTORY_ELMS) {
365 			hist->nxt_tid = elm->leaf.base.create_tid;
366 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_TID;
367 			return;
368 		}
369 		hist->hist_ary[i].tid = elm->leaf.base.create_tid;
370 		hist->hist_ary[i].time32 = elm->leaf.create_ts;
371 		++hist->count;
372 	}
373 
374 	/*
375 	 * Add delete_tid if it is in-bounds.  Note that different portions
376 	 * of the history may have overlapping data ranges with different
377 	 * delete_tid's.  If this case occurs the delete_tid may match the
378 	 * create_tid of a following record.  XXX
379 	 *
380 	 *	[        ]
381 	 *            [     ]
382 	 */
383 	i = hist->count;
384 	if (elm->leaf.base.delete_tid &&
385 	    elm->leaf.base.delete_tid >= hist->beg_tid &&
386 	    elm->leaf.base.delete_tid < hist->end_tid) {
387 		if (i == HAMMER_MAX_HISTORY_ELMS) {
388 			hist->nxt_tid = elm->leaf.base.delete_tid;
389 			hist->head.flags |= HAMMER_IOC_HISTORY_NEXT_TID;
390 			return;
391 		}
392 		hist->hist_ary[i].tid = elm->leaf.base.delete_tid;
393 		hist->hist_ary[i].time32 = elm->leaf.delete_ts;
394 		++hist->count;
395 	}
396 }
397 
398 /*
399  * Acquire synchronization TID
400  */
401 static
402 int
403 hammer_ioc_synctid(hammer_transaction_t trans, hammer_inode_t ip,
404 		   struct hammer_ioc_synctid *std)
405 {
406 	hammer_mount_t hmp = ip->hmp;
407 	int error = 0;
408 
409 	switch(std->op) {
410 	case HAMMER_SYNCTID_NONE:
411 		std->tid = hmp->flusher.tid;	/* inaccurate */
412 		break;
413 	case HAMMER_SYNCTID_ASYNC:
414 		hammer_queue_inodes_flusher(hmp, MNT_NOWAIT);
415 		hammer_flusher_async(hmp, NULL);
416 		std->tid = hmp->flusher.tid;	/* inaccurate */
417 		break;
418 	case HAMMER_SYNCTID_SYNC1:
419 		hammer_queue_inodes_flusher(hmp, MNT_WAIT);
420 		hammer_flusher_sync(hmp);
421 		std->tid = hmp->flusher.tid;
422 		break;
423 	case HAMMER_SYNCTID_SYNC2:
424 		hammer_queue_inodes_flusher(hmp, MNT_WAIT);
425 		hammer_flusher_sync(hmp);
426 		std->tid = hmp->flusher.tid;
427 		hammer_flusher_sync(hmp);
428 		break;
429 	default:
430 		error = EOPNOTSUPP;
431 		break;
432 	}
433 	return(error);
434 }
435 
436 /*
437  * Retrieve version info.
438  *
439  * Load min_version, wip_version, and max_versino.  If cur_version is passed
440  * as 0 then load the current version into cur_version.  Load the description
441  * for cur_version into the description array.
442  *
443  * Returns 0 on success, EINVAL if cur_version is non-zero and set to an
444  * unsupported value.
445  */
446 static
447 int
448 hammer_ioc_get_version(hammer_transaction_t trans, hammer_inode_t ip,
449 		   struct hammer_ioc_version *ver)
450 {
451 	int error = 0;
452 
453 	ver->min_version = HAMMER_VOL_VERSION_MIN;
454 	ver->wip_version = HAMMER_VOL_VERSION_WIP;
455 	ver->max_version = HAMMER_VOL_VERSION_MAX;
456 	if (ver->cur_version == 0)
457 		ver->cur_version = trans->hmp->version;
458 	switch(ver->cur_version) {
459 	case 1:
460 		ksnprintf(ver->description, sizeof(ver->description),
461 			 "2.0 - First HAMMER release");
462 		break;
463 	case 2:
464 		ksnprintf(ver->description, sizeof(ver->description),
465 			 "2.3 - New directory entry layout");
466 		break;
467 	default:
468 		ksnprintf(ver->description, sizeof(ver->description),
469 			 "Unknown");
470 		error = EINVAL;
471 		break;
472 	}
473 	return(error);
474 };
475 
476 /*
477  * Set version info
478  */
479 static
480 int
481 hammer_ioc_set_version(hammer_transaction_t trans, hammer_inode_t ip,
482 		   struct hammer_ioc_version *ver)
483 {
484 	struct hammer_cursor cursor;
485 	hammer_volume_t volume;
486 	int error;
487 
488 	if (ver->cur_version < trans->hmp->version)
489 		return(EINVAL);
490 	if (ver->cur_version == trans->hmp->version)
491 		return(0);
492 	if (ver->cur_version > HAMMER_VOL_VERSION_MAX)
493 		return(EINVAL);
494 	if (trans->hmp->ronly)
495 		return(EROFS);
496 
497 	/*
498 	 * Update the root volume header and the version cached in
499 	 * the hammer_mount structure.
500 	 */
501 	error = hammer_init_cursor(trans, &cursor, NULL, NULL);
502 	if (error)
503 		goto failed;
504 	hammer_sync_lock_sh(trans);
505 
506 	volume = hammer_get_root_volume(cursor.trans->hmp, &error);
507 	KKASSERT(error == 0);
508 	hammer_modify_volume_field(cursor.trans, volume, vol_version);
509 	volume->ondisk->vol_version = ver->cur_version;
510 	cursor.trans->hmp->version = ver->cur_version;
511 	hammer_modify_volume_done(volume);
512 	hammer_rel_volume(volume, 0);
513 
514 	hammer_sync_unlock(trans);
515 failed:
516 	ver->head.error = error;
517 	hammer_done_cursor(&cursor);
518 	return(0);
519 }
520 
521 /*
522  * Get information
523  */
524 static
525 int
526 hammer_ioc_get_info(hammer_transaction_t trans, struct hammer_ioc_info *info) {
527 
528 	struct hammer_volume_ondisk	*od = trans->hmp->rootvol->ondisk;
529 	struct hammer_mount 		*hm = trans->hmp;
530 
531 	/* Fill the structure with the necessary information */
532 	_hammer_checkspace(hm, HAMMER_CHKSPC_WRITE, &info->rsvbigblocks);
533 	info->rsvbigblocks = info->rsvbigblocks >> HAMMER_LARGEBLOCK_BITS;
534 	strlcpy(info->vol_name, od->vol_name, sizeof(od->vol_name));
535 
536 	info->vol_fsid = hm->fsid;
537 	info->vol_fstype = od->vol_fstype;
538 	info->version = hm->version;
539 
540 	info->inodes = od->vol0_stat_inodes;
541 	info->bigblocks = od->vol0_stat_bigblocks;
542 	info->freebigblocks = od->vol0_stat_freebigblocks;
543 	info->nvolumes = hm->nvolumes;
544 
545 	return 0;
546 }
547 
548