1 // SPDX-License-Identifier: CDDL-1.0
2 /*
3 * This file and its contents are supplied under the terms of the
4 * Common Development and Distribution License ("CDDL"), version 1.0.
5 * You may only use this file in accordance with the terms of version
6 * 1.0 of the CDDL.
7 *
8 * A full copy of the text of the CDDL should have accompanied this
9 * source. A copy of the CDDL is also available via the Internet at
10 * http://www.illumos.org/license/CDDL.
11 */
12
13 /*
14 * Copyright (c) 2026, TrueNAS.
15 */
16
17 #include <stdarg.h>
18 #include <stdio.h>
19 #include <stdlib.h>
20 #include <string.h>
21
22 #include <sys/zfs_context.h>
23 #include <sys/dmu.h>
24 #include <sys/dmu_tx.h>
25 #include <sys/dnode.h>
26 #include <sys/dsl_dataset.h>
27 #include <sys/spa.h>
28 #include <sys/zfeature.h>
29
30 #include "mock_dmu.h"
31 #include "unit.h"
32
33 /*
34 * A mock dbuf. A real dmu_buf_t (first for casting) plus the attached user
35 * data pointer. Block data is stored in a separate allocation so that the
36 * struct address remains stable across block resizes.
37 */
38 struct mock_dbuf {
39 dmu_buf_t mdb_db;
40 dmu_buf_user_t *mdb_user;
41 mock_dnode_t *mdb_owner;
42 void *mdb_data;
43 };
44 typedef struct mock_dbuf mock_dbuf_t;
45
46 /*
47 * A mock dnode. a real dnode_t (must be first for casting) with dn_type
48 * and dn_object set, plus a flat array of mock_dbuf_t indexed by block id.
49 */
50 struct mock_dnode {
51 dnode_t mdn_dn;
52 uint64_t mdn_refcount;
53 size_t mdn_blksize;
54 size_t mdn_nblocks;
55 mock_dbuf_t **mdn_blocks;
56 };
57
58 /*
59 * A mock transaction. We only allocate and zero it, nothing currently uses
60 * any of its internals.
61 */
62 struct mock_dmu_tx {
63 dmu_tx_t mtx_tx;
64 };
65
66 /* Mock dnode */
67
68 static mock_dbuf_t *
mock_dnode_block_alloc(mock_dnode_t * mdn,uint64_t blkid)69 mock_dnode_block_alloc(mock_dnode_t *mdn, uint64_t blkid)
70 {
71 mock_dbuf_t *mdb = kmem_zalloc(sizeof (mock_dbuf_t), KM_SLEEP);
72 mdb->mdb_data = kmem_zalloc(mdn->mdn_blksize, KM_SLEEP);
73
74 mdb->mdb_db.db_object = mdn->mdn_dn.dn_object;
75 mdb->mdb_db.db_offset = blkid * mdn->mdn_blksize;
76 mdb->mdb_db.db_size = mdn->mdn_blksize;
77 mdb->mdb_db.db_data = mdb->mdb_data;
78 mdb->mdb_owner = mdn;
79
80 return (mdb);
81 }
82
83 /* Grow the dbuf array if needed, then return (or create) the dbuf for blkid. */
84 static mock_dbuf_t *
mock_dnode_block_get(mock_dnode_t * mdn,uint64_t blkid)85 mock_dnode_block_get(mock_dnode_t *mdn, uint64_t blkid)
86 {
87 if (blkid >= mdn->mdn_nblocks) {
88 size_t new_n = blkid + 1;
89 mock_dbuf_t **new_blocks =
90 kmem_zalloc(new_n * sizeof (mock_dbuf_t *), KM_SLEEP);
91 if (mdn->mdn_blocks != NULL) {
92 memcpy(new_blocks, mdn->mdn_blocks,
93 mdn->mdn_nblocks * sizeof (mock_dbuf_t *));
94 kmem_free(mdn->mdn_blocks,
95 mdn->mdn_nblocks * sizeof (mock_dbuf_t *));
96 }
97 mdn->mdn_blocks = new_blocks;
98 mdn->mdn_nblocks = new_n;
99 }
100
101 mock_dbuf_t *mdb = mdn->mdn_blocks[blkid];
102 if (mdb == NULL) {
103 mdb = mock_dnode_block_alloc(mdn, blkid);
104 mdn->mdn_blocks[blkid] = mdb;
105 }
106 return (mdb);
107 }
108
109 mock_dnode_t *
mock_dnode_create(size_t blksize,dmu_object_type_t type)110 mock_dnode_create(size_t blksize, dmu_object_type_t type)
111 {
112 ASSERT(IS_P2ALIGNED(blksize, 512));
113
114 mock_dnode_t *mdn = kmem_zalloc(sizeof (mock_dnode_t), KM_SLEEP);
115 mdn->mdn_refcount = 1;
116 mdn->mdn_dn.dn_type = type;
117 mdn->mdn_dn.dn_object = 1; /* arbitrary non-zero object number */
118 mdn->mdn_blksize = blksize;
119
120 return (mdn);
121 }
122
123 void
mock_dnode_destroy(mock_dnode_t * mdn)124 mock_dnode_destroy(mock_dnode_t *mdn)
125 {
126 for (size_t i = 0; i < mdn->mdn_nblocks; i++) {
127 mock_dbuf_t *mdb = mdn->mdn_blocks[i];
128 if (mdb == NULL)
129 continue;
130
131 /*
132 * Call the sync evict callback if one is set, mimicking the
133 * real DMU when a buffer's refcount drops to zero.
134 */
135 if (mdb->mdb_user != NULL &&
136 mdb->mdb_user->dbu_evict_func_sync != NULL)
137 mdb->mdb_user->dbu_evict_func_sync(mdb->mdb_user);
138
139 kmem_free(mdb->mdb_data, mdb->mdb_db.db_size);
140 kmem_free(mdb, sizeof (mock_dbuf_t));
141 }
142
143 kmem_free(mdn->mdn_blocks,
144 mdn->mdn_nblocks * sizeof (mock_dbuf_t *));
145 kmem_free(mdn, sizeof (mock_dnode_t));
146 }
147
148 size_t
mock_dnode_block_count(mock_dnode_t * mdn)149 mock_dnode_block_count(mock_dnode_t *mdn)
150 {
151 return (mdn->mdn_nblocks);
152 }
153
154 const void *
mock_dnode_block_data(mock_dnode_t * mdn,uint64_t blkid)155 mock_dnode_block_data(mock_dnode_t *mdn, uint64_t blkid)
156 {
157 if (blkid >= mdn->mdn_nblocks)
158 return (NULL);
159 return (mdn->mdn_blocks[blkid]->mdb_db.db_data);
160 }
161
162 uint64_t
mock_dnode_refcount(mock_dnode_t * mdn)163 mock_dnode_refcount(mock_dnode_t *mdn)
164 {
165 return (mdn->mdn_refcount);
166 }
167
168 /* Mock transaction */
169
170 mock_dmu_tx_t *
mock_tx_create(void)171 mock_tx_create(void)
172 {
173 return (kmem_zalloc(sizeof (mock_dmu_tx_t), KM_SLEEP));
174 }
175
176 void
mock_tx_destroy(mock_dmu_tx_t * tx)177 mock_tx_destroy(mock_dmu_tx_t *tx)
178 {
179 kmem_free(tx, sizeof (mock_dmu_tx_t));
180 }
181
182 /* DMU stubs, either no-op or light access to mock dnode internals. */
183
184 int
dmu_buf_hold_by_dnode(dnode_t * dn,uint64_t offset,const void * tag,dmu_buf_t ** dbp,dmu_flags_t flags)185 dmu_buf_hold_by_dnode(dnode_t *dn, uint64_t offset, const void *tag,
186 dmu_buf_t **dbp, dmu_flags_t flags)
187 {
188 (void) tag; (void) flags;
189
190 mock_dnode_t *mdn = (mock_dnode_t *)dn;
191 uint64_t blkid = offset / mdn->mdn_blksize;
192 mock_dbuf_t *mdb = mock_dnode_block_get(mdn, blkid);
193
194 *dbp = &mdb->mdb_db;
195 return (0);
196 }
197
198 void
dmu_buf_rele(dmu_buf_t * db,const void * tag)199 dmu_buf_rele(dmu_buf_t *db, const void *tag)
200 {
201 (void) db; (void) tag;
202 }
203
204 void *
dmu_buf_get_user(dmu_buf_t * db)205 dmu_buf_get_user(dmu_buf_t *db)
206 {
207 mock_dbuf_t *mdb = (mock_dbuf_t *)db;
208 return (mdb->mdb_user);
209 }
210
211 void *
dmu_buf_set_user(dmu_buf_t * db,dmu_buf_user_t * new_user)212 dmu_buf_set_user(dmu_buf_t *db, dmu_buf_user_t *new_user)
213 {
214 mock_dbuf_t *mdb = (mock_dbuf_t *)db;
215 if (mdb->mdb_user != NULL)
216 return (mdb->mdb_user); /* existing user wins */
217 mdb->mdb_user = new_user;
218 return (NULL); /* new_user wins */
219 }
220
221 void
dmu_buf_will_dirty(dmu_buf_t * db,dmu_tx_t * tx)222 dmu_buf_will_dirty(dmu_buf_t *db, dmu_tx_t *tx)
223 {
224 (void) db; (void) tx;
225 }
226
227 objset_t *
dmu_buf_get_objset(dmu_buf_t * db)228 dmu_buf_get_objset(dmu_buf_t *db)
229 {
230 mock_dbuf_t *mdb = (mock_dbuf_t *)db;
231
232 /*
233 * We return the mock_dnode_t pointer cast to objset_t so that
234 * dmu_object_set_blocksize() below can recover the dnode without
235 * needing a separate objset structure.
236 */
237 return ((objset_t *)mdb->mdb_owner);
238 }
239
240 int
dmu_object_set_blocksize(objset_t * os,uint64_t object,uint64_t size,int ibs,dmu_tx_t * tx)241 dmu_object_set_blocksize(objset_t *os, uint64_t object, uint64_t size,
242 int ibs, dmu_tx_t *tx)
243 {
244 (void) object; (void) ibs; (void) tx;
245
246 /* os is a mock_dnode_t (see dmu_buf_get_objset() above). */
247 mock_dnode_t *mdn = (mock_dnode_t *)os;
248
249 /*
250 * Resize block 0's data buffer in place so the struct address stays
251 * stable.
252 */
253 mock_dbuf_t *mdb = mdn->mdn_blocks[0];
254 void *new_data = kmem_zalloc(size, KM_SLEEP);
255 memcpy(new_data, mdb->mdb_data,
256 MIN(size, (size_t)mdb->mdb_db.db_size));
257 kmem_free(mdb->mdb_data, mdb->mdb_db.db_size);
258
259 mdb->mdb_data = new_data;
260 mdb->mdb_db.db_size = size;
261 mdb->mdb_db.db_data = new_data;
262 mdn->mdn_blksize = size;
263
264 return (0);
265 }
266
267 boolean_t
dnode_add_ref(dnode_t * dn,const void * tag)268 dnode_add_ref(dnode_t *dn, const void *tag)
269 {
270 (void) tag;
271 mock_dnode_t *mdn = (mock_dnode_t *)dn;
272 if (mdn->mdn_refcount == 0)
273 return (B_FALSE);
274 mdn->mdn_refcount++;
275 return (B_TRUE);
276 }
277
278 void
dnode_rele(dnode_t * dn,const void * tag)279 dnode_rele(dnode_t *dn, const void *tag)
280 {
281 (void) tag;
282 mock_dnode_t *mdn = (mock_dnode_t *)dn;
283 unit_gt(mdn->mdn_refcount, 0);
284 mdn->mdn_refcount--;
285 }
286
287 /*
288 * Misc other stubs. Not strictly DMU mocks, and might move elsewhere later,
289 * but for now this is all we need for our limited test set.
290 */
291
292 spa_t *
dmu_objset_spa(objset_t * os)293 dmu_objset_spa(objset_t *os)
294 {
295 (void) os;
296 return (NULL);
297 }
298
299 int
dmu_free_range(objset_t * os,uint64_t object,uint64_t offset,uint64_t size,dmu_tx_t * tx)300 dmu_free_range(objset_t *os, uint64_t object, uint64_t offset,
301 uint64_t size, dmu_tx_t *tx)
302 {
303 (void) os; (void) object; (void) offset; (void) size; (void) tx;
304 return (0);
305 }
306
307 void
dmu_prefetch_by_dnode(dnode_t * dn,int64_t level,uint64_t offset,uint64_t len,zio_priority_t pri)308 dmu_prefetch_by_dnode(dnode_t *dn, int64_t level, uint64_t offset,
309 uint64_t len, zio_priority_t pri)
310 {
311 (void) dn; (void) level; (void) offset; (void) len; (void) pri;
312 }
313
314 dsl_dataset_t *
dmu_objset_ds(objset_t * os)315 dmu_objset_ds(objset_t *os)
316 {
317 (void) os;
318 return (NULL);
319 }
320
321 boolean_t
dsl_dataset_feature_is_active(dsl_dataset_t * ds,spa_feature_t f)322 dsl_dataset_feature_is_active(dsl_dataset_t *ds, spa_feature_t f)
323 {
324 (void) ds; (void) f;
325 return (B_FALSE);
326 }
327
328 void
dsl_dataset_dirty(dsl_dataset_t * ds,dmu_tx_t * tx)329 dsl_dataset_dirty(dsl_dataset_t *ds, dmu_tx_t *tx)
330 {
331 (void) ds; (void) tx;
332 }
333
334 boolean_t
spa_feature_is_enabled(spa_t * spa,spa_feature_t f)335 spa_feature_is_enabled(spa_t *spa, spa_feature_t f)
336 {
337 (void) spa; (void) f;
338 return (B_FALSE);
339 }
340
341 int
spa_maxblocksize(spa_t * spa)342 spa_maxblocksize(spa_t *spa)
343 {
344 (void) spa;
345 return (SPA_OLD_MAXBLOCKSIZE);
346 }
347
348 const dmu_object_type_info_t dmu_ot[DMU_OT_NUMTYPES];
349
350 void
byteswap_uint64_array(void * buf,size_t size)351 byteswap_uint64_array(void *buf, size_t size)
352 {
353 (void) buf; (void) size;
354 }
355
356 /*
357 * Various objset+object calls; returning error, as they need to use
358 * _by_dnode() variants to get the mock.
359 */
360 int
dnode_hold(objset_t * os,uint64_t object,const void * tag,dnode_t ** dnp)361 dnode_hold(objset_t *os, uint64_t object, const void *tag, dnode_t **dnp)
362 {
363 (void) os; (void) object; (void) tag; (void) dnp;
364 return (EIO);
365 }
366
367 int
dmu_object_free(objset_t * os,uint64_t object,dmu_tx_t * tx)368 dmu_object_free(objset_t *os, uint64_t object, dmu_tx_t *tx)
369 {
370 (void) os; (void) object; (void) tx;
371 return (EIO);
372 }
373
374 uint64_t
dmu_object_alloc_hold(objset_t * os,dmu_object_type_t ot,int blocksize,int indirect_blockshift,dmu_object_type_t bonustype,int bonuslen,int dnodesize,dnode_t ** allocated_dnode,const void * tag,dmu_tx_t * tx)375 dmu_object_alloc_hold(objset_t *os, dmu_object_type_t ot,
376 int blocksize, int indirect_blockshift, dmu_object_type_t bonustype,
377 int bonuslen, int dnodesize, dnode_t **allocated_dnode,
378 const void *tag, dmu_tx_t *tx)
379 {
380 (void) os; (void) ot; (void) blocksize; (void) indirect_blockshift;
381 (void) bonustype; (void) bonuslen; (void) dnodesize;
382 (void) allocated_dnode; (void) tag; (void) tx;
383 return (EIO);
384 }
385
386 int
dmu_object_claim_dnsize(objset_t * os,uint64_t object,dmu_object_type_t ot,int blocksize,dmu_object_type_t bonus_type,int bonus_len,int dnodesize,dmu_tx_t * tx)387 dmu_object_claim_dnsize(objset_t *os, uint64_t object, dmu_object_type_t ot,
388 int blocksize, dmu_object_type_t bonus_type, int bonus_len,
389 int dnodesize, dmu_tx_t *tx)
390 {
391 (void) os; (void) object; (void) ot; (void) blocksize;
392 (void) bonus_type; (void) bonus_len; (void) dnodesize; (void) tx;
393 return (EIO);
394 }
395
396 int
dmu_object_info(objset_t * os,uint64_t object,dmu_object_info_t * doi)397 dmu_object_info(objset_t *os, uint64_t object, dmu_object_info_t *doi)
398 {
399 (void) os; (void) object; (void) doi;
400 return (EIO);
401 }
402
403 int
dmu_prefetch_wait(objset_t * os,uint64_t object,uint64_t offset,uint64_t len)404 dmu_prefetch_wait(objset_t *os, uint64_t object, uint64_t offset,
405 uint64_t len)
406 {
407 (void) os; (void) object; (void) offset; (void) len;
408 return (EIO);
409 }
410