xref: /dragonfly/sys/vfs/hammer/hammer_dedup.c (revision 10cbe914)
1 /*
2  * Copyright (c) 2010 The DragonFly Project.  All rights reserved.
3  *
4  * This code is derived from software contributed to The DragonFly Project
5  * by Ilya Dryomov <idryomov@gmail.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 
35 #include "hammer.h"
36 
37 static __inline int validate_zone(hammer_off_t data_offset);
38 
39 int
40 hammer_ioc_dedup(hammer_transaction_t trans, hammer_inode_t ip,
41 		 struct hammer_ioc_dedup *dedup)
42 {
43 	struct hammer_cursor cursor1, cursor2;
44 	int error;
45 
46 	/*
47 	 * Enforce hammer filesystem version requirements
48 	 */
49 	if (trans->hmp->version < HAMMER_VOL_VERSION_FIVE) {
50 		kprintf("hammer: Filesystem must be upgraded to v5 "
51 			"before you can run dedup\n");
52 		return (EOPNOTSUPP);
53 	}
54 
55 	/*
56 	 * Cursor1, return an error -> candidate goes to pass2 list
57 	 */
58 	error = hammer_init_cursor(trans, &cursor1, NULL, NULL);
59 	if (error)
60 		goto done_cursor;
61 	cursor1.key_beg = dedup->elm1;
62 	cursor1.flags |= HAMMER_CURSOR_BACKEND;
63 
64 	error = hammer_btree_lookup(&cursor1);
65 	if (error)
66 		goto done_cursor;
67 	error = hammer_btree_extract(&cursor1, HAMMER_CURSOR_GET_LEAF |
68 						HAMMER_CURSOR_GET_DATA);
69 	if (error)
70 		goto done_cursor;
71 
72 	/*
73 	 * Cursor2, return an error -> candidate goes to pass2 list
74 	 */
75 	error = hammer_init_cursor(trans, &cursor2, NULL, NULL);
76 	if (error)
77 		goto done_cursors;
78 	cursor2.key_beg = dedup->elm2;
79 	cursor2.flags |= HAMMER_CURSOR_BACKEND;
80 
81 	error = hammer_btree_lookup(&cursor2);
82 	if (error)
83 		goto done_cursors;
84 	error = hammer_btree_extract(&cursor2, HAMMER_CURSOR_GET_LEAF |
85 						HAMMER_CURSOR_GET_DATA);
86 	if (error)
87 		goto done_cursors;
88 
89 	/*
90 	 * Zone validation. We can't de-dup any of the other zones
91 	 * (BTREE or META) or bad things will happen.
92 	 *
93 	 * Return with error = 0, but set an INVALID_ZONE flag.
94 	 */
95 	error = validate_zone(cursor1.leaf->data_offset) +
96 			    validate_zone(cursor2.leaf->data_offset);
97 	if (error) {
98 		dedup->head.flags |= HAMMER_IOC_DEDUP_INVALID_ZONE;
99 		error = 0;
100 		goto done_cursors;
101 	}
102 
103 	/*
104 	 * Comparison checks
105 	 *
106 	 * If zones don't match or data_len fields aren't the same
107 	 * we consider it to be a comparison failure.
108 	 *
109 	 * Return with error = 0, but set a CMP_FAILURE flag.
110 	 */
111 	if ((cursor1.leaf->data_offset & HAMMER_OFF_ZONE_MASK) !=
112 	    (cursor2.leaf->data_offset & HAMMER_OFF_ZONE_MASK)) {
113 		dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
114 		goto done_cursors;
115 	}
116 	if (cursor1.leaf->data_len != cursor2.leaf->data_len) {
117 		dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
118 		goto done_cursors;
119 	}
120 
121 	/* byte-by-byte comparison to be sure */
122 	if (bcmp(cursor1.data, cursor2.data, cursor1.leaf->data_len)) {
123 		dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
124 		goto done_cursors;
125 	}
126 
127 	/*
128 	 * Upgrade both cursors together to an exclusive lock
129 	 *
130 	 * Return an error -> candidate goes to pass2 list
131 	 */
132 	hammer_sync_lock_sh(trans);
133 	error = hammer_cursor_upgrade2(&cursor1, &cursor2);
134 	if (error) {
135 		hammer_sync_unlock(trans);
136 		goto done_cursors;
137 	}
138 
139 	error = hammer_blockmap_dedup(cursor1.trans,
140 			cursor1.leaf->data_offset, cursor1.leaf->data_len);
141 	if (error) {
142 		if (error == ERANGE) {
143 			/*
144 			 * Return with error = 0, but set an UNDERFLOW flag
145 			 */
146 			dedup->head.flags |= HAMMER_IOC_DEDUP_UNDERFLOW;
147 			error = 0;
148 			goto downgrade_cursors;
149 		} else {
150 			/*
151 			 * Return an error -> block goes to pass2 list
152 			 */
153 			goto downgrade_cursors;
154 		}
155 	}
156 
157 	/*
158 	 * The cursor2's cache must be invalidated before calling
159 	 * hammer_blockmap_free(), otherwise it will not be able to
160 	 * invalidate the underlying data buffer.
161 	 */
162 	hammer_cursor_invalidate_cache(&cursor2);
163 	hammer_blockmap_free(cursor2.trans,
164 			cursor2.leaf->data_offset, cursor2.leaf->data_len);
165 
166 	hammer_modify_node(cursor2.trans, cursor2.node,
167 			&cursor2.leaf->data_offset, sizeof(hammer_off_t));
168 	cursor2.leaf->data_offset = cursor1.leaf->data_offset;
169 	hammer_modify_node_done(cursor2.node);
170 
171 downgrade_cursors:
172 	hammer_cursor_downgrade2(&cursor1, &cursor2);
173 	hammer_sync_unlock(trans);
174 done_cursors:
175 	hammer_done_cursor(&cursor2);
176 done_cursor:
177 	hammer_done_cursor(&cursor1);
178 	return (error);
179 }
180 
181 static __inline int
182 validate_zone(hammer_off_t data_offset)
183 {
184 	switch(data_offset & HAMMER_OFF_ZONE_MASK) {
185 	case HAMMER_ZONE_LARGE_DATA:
186 	case HAMMER_ZONE_SMALL_DATA:
187 		return (0);
188 	default:
189 		return (1);
190 	}
191 }
192