0bb74b51be069df5ecb472c49c43a3d86fe2b06a
[dragonfly.git] / sys / vfs / hammer / hammer_dedup.c
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 }