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