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
hammer_ioc_dedup(hammer_transaction_t trans,hammer_inode_t ip,struct hammer_ioc_dedup * dedup)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