xref: /dragonfly/sys/vfs/hammer/hammer_dedup.c (revision 75a74ed8)
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 int
38 hammer_ioc_dedup(hammer_transaction_t trans, hammer_inode_t ip,
39 		 struct hammer_ioc_dedup *dedup)
40 {
41 	struct hammer_cursor cursor1, cursor2;
42 	int error;
43 	int seq;
44 
45 	/*
46 	 * Enforce hammer filesystem version requirements
47 	 */
48 	if (trans->hmp->version < HAMMER_VOL_VERSION_FIVE) {
49 		hkprintf("Filesystem must be upgraded to v5 "
50 			"before you can run dedup\n");
51 		return (EOPNOTSUPP);
52 	}
53 
54 	/*
55 	 * Cursor1, return an error -> candidate goes to pass2 list
56 	 */
57 	error = hammer_init_cursor(trans, &cursor1, NULL, NULL);
58 	if (error)
59 		goto done_cursor;
60 	cursor1.key_beg = dedup->elm1;
61 	cursor1.flags |= HAMMER_CURSOR_BACKEND;
62 
63 	error = hammer_btree_lookup(&cursor1);
64 	if (error)
65 		goto done_cursor;
66 	error = hammer_btree_extract_data(&cursor1);
67 	if (error)
68 		goto done_cursor;
69 
70 	/*
71 	 * Cursor2, return an error -> candidate goes to pass2 list
72 	 */
73 	error = hammer_init_cursor(trans, &cursor2, NULL, NULL);
74 	if (error)
75 		goto done_cursors;
76 	cursor2.key_beg = dedup->elm2;
77 	cursor2.flags |= HAMMER_CURSOR_BACKEND;
78 
79 	error = hammer_btree_lookup(&cursor2);
80 	if (error)
81 		goto done_cursors;
82 	error = hammer_btree_extract_data(&cursor2);
83 	if (error)
84 		goto done_cursors;
85 
86 	/*
87 	 * Zone validation.
88 	 * We can only de-dup data zones or bad things will happen.
89 	 *
90 	 * Return with error = 0, but set an INVALID_ZONE flag.
91 	 */
92 	if (!hammer_is_zone_data(cursor1.leaf->data_offset) ||
93 	    !hammer_is_zone_data(cursor2.leaf->data_offset)) {
94 		dedup->head.flags |= HAMMER_IOC_DEDUP_INVALID_ZONE;
95 		goto done_cursors;
96 	}
97 
98 	/*
99 	 * Comparison checks
100 	 *
101 	 * If zones don't match or data_len fields aren't the same
102 	 * we consider it to be a comparison failure.
103 	 *
104 	 * Return with error = 0, but set a CMP_FAILURE flag.
105 	 */
106 	if (HAMMER_ZONE(cursor1.leaf->data_offset) !=
107 	    HAMMER_ZONE(cursor2.leaf->data_offset)) {
108 		dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
109 		goto done_cursors;
110 	}
111 	if (cursor1.leaf->data_len != cursor2.leaf->data_len) {
112 		dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
113 		goto done_cursors;
114 	}
115 
116 	/* byte-by-byte comparison to be sure */
117 	if (bcmp(cursor1.data, cursor2.data, cursor1.leaf->data_len)) {
118 		dedup->head.flags |= HAMMER_IOC_DEDUP_CMP_FAILURE;
119 		goto done_cursors;
120 	}
121 
122 	/*
123 	 * Upgrade both cursors together to an exclusive lock
124 	 *
125 	 * Return an error -> candidate goes to pass2 list
126 	 */
127 	hammer_sync_lock_sh(trans);
128 	error = hammer_cursor_upgrade2(&cursor1, &cursor2);
129 	if (error) {
130 		hammer_sync_unlock(trans);
131 		goto done_cursors;
132 	}
133 
134 	error = hammer_blockmap_dedup(cursor1.trans,
135 			cursor1.leaf->data_offset, cursor1.leaf->data_len);
136 	if (error) {
137 		if (error == ERANGE) {
138 			/* Return with error = 0, but set an UNDERFLOW flag */
139 			dedup->head.flags |= HAMMER_IOC_DEDUP_UNDERFLOW;
140 			error = 0;
141 		}
142 
143 		/* Return all other errors -> block goes to pass2 list */
144 		goto downgrade_cursors;
145 	}
146 
147 	/*
148 	 * The cursor2's cache must be invalidated before calling
149 	 * hammer_blockmap_free(), otherwise it will not be able to
150 	 * invalidate the underlying data buffer.
151 	 */
152 	hammer_cursor_invalidate_cache(&cursor2);
153 	hammer_blockmap_free(cursor2.trans,
154 			cursor2.leaf->data_offset, cursor2.leaf->data_len);
155 
156 	hammer_modify_node(cursor2.trans, cursor2.node,
157 			&cursor2.leaf->data_offset, sizeof(hammer_off_t));
158 	cursor2.leaf->data_offset = cursor1.leaf->data_offset;
159 	hammer_modify_node_done(cursor2.node);
160 
161 downgrade_cursors:
162 	hammer_cursor_downgrade2(&cursor1, &cursor2);
163 	hammer_sync_unlock(trans);
164 done_cursors:
165 	hammer_done_cursor(&cursor2);
166 done_cursor:
167 	hammer_done_cursor(&cursor1);
168 
169 	/*
170 	 * Avoid deadlocking the buffer cache
171 	 */
172 	seq = trans->hmp->flusher.done;
173 	while (hammer_flusher_meta_halflimit(trans->hmp) ||
174 	       hammer_flusher_undo_exhausted(trans, 2)) {
175 		hammer_flusher_wait(trans->hmp, seq);
176 		seq = hammer_flusher_async_one(trans->hmp);
177 	}
178 	return (error);
179 }
180