Commit | Line | Data |
---|---|---|
b4d0d230 | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
63a4681f DH |
2 | /* AFS filesystem directory editing |
3 | * | |
4 | * Copyright (C) 2018 Red Hat, Inc. All Rights Reserved. | |
5 | * Written by David Howells (dhowells@redhat.com) | |
63a4681f DH |
6 | */ |
7 | ||
8 | #include <linux/kernel.h> | |
9 | #include <linux/fs.h> | |
10 | #include <linux/namei.h> | |
11 | #include <linux/pagemap.h> | |
12 | #include <linux/iversion.h> | |
13 | #include "internal.h" | |
14 | #include "xdr_fs.h" | |
15 | ||
16 | /* | |
17 | * Find a number of contiguous clear bits in a directory block bitmask. | |
18 | * | |
19 | * There are 64 slots, which means we can load the entire bitmap into a | |
20 | * variable. The first bit doesn't count as it corresponds to the block header | |
21 | * slot. nr_slots is between 1 and 9. | |
22 | */ | |
23 | static int afs_find_contig_bits(union afs_xdr_dir_block *block, unsigned int nr_slots) | |
24 | { | |
25 | u64 bitmap; | |
26 | u32 mask; | |
27 | int bit, n; | |
28 | ||
29 | bitmap = (u64)block->hdr.bitmap[0] << 0 * 8; | |
30 | bitmap |= (u64)block->hdr.bitmap[1] << 1 * 8; | |
31 | bitmap |= (u64)block->hdr.bitmap[2] << 2 * 8; | |
32 | bitmap |= (u64)block->hdr.bitmap[3] << 3 * 8; | |
33 | bitmap |= (u64)block->hdr.bitmap[4] << 4 * 8; | |
34 | bitmap |= (u64)block->hdr.bitmap[5] << 5 * 8; | |
35 | bitmap |= (u64)block->hdr.bitmap[6] << 6 * 8; | |
36 | bitmap |= (u64)block->hdr.bitmap[7] << 7 * 8; | |
37 | bitmap >>= 1; /* The first entry is metadata */ | |
38 | bit = 1; | |
39 | mask = (1 << nr_slots) - 1; | |
40 | ||
41 | do { | |
42 | if (sizeof(unsigned long) == 8) | |
43 | n = ffz(bitmap); | |
44 | else | |
45 | n = ((u32)bitmap) != 0 ? | |
46 | ffz((u32)bitmap) : | |
47 | ffz((u32)(bitmap >> 32)) + 32; | |
48 | bitmap >>= n; | |
49 | bit += n; | |
50 | ||
51 | if ((bitmap & mask) == 0) { | |
52 | if (bit > 64 - nr_slots) | |
53 | return -1; | |
54 | return bit; | |
55 | } | |
56 | ||
57 | n = __ffs(bitmap); | |
58 | bitmap >>= n; | |
59 | bit += n; | |
60 | } while (bitmap); | |
61 | ||
62 | return -1; | |
63 | } | |
64 | ||
65 | /* | |
66 | * Set a number of contiguous bits in the directory block bitmap. | |
67 | */ | |
68 | static void afs_set_contig_bits(union afs_xdr_dir_block *block, | |
69 | int bit, unsigned int nr_slots) | |
70 | { | |
51590df4 | 71 | u64 mask; |
63a4681f DH |
72 | |
73 | mask = (1 << nr_slots) - 1; | |
74 | mask <<= bit; | |
75 | ||
63a4681f DH |
76 | block->hdr.bitmap[0] |= (u8)(mask >> 0 * 8); |
77 | block->hdr.bitmap[1] |= (u8)(mask >> 1 * 8); | |
78 | block->hdr.bitmap[2] |= (u8)(mask >> 2 * 8); | |
79 | block->hdr.bitmap[3] |= (u8)(mask >> 3 * 8); | |
80 | block->hdr.bitmap[4] |= (u8)(mask >> 4 * 8); | |
81 | block->hdr.bitmap[5] |= (u8)(mask >> 5 * 8); | |
82 | block->hdr.bitmap[6] |= (u8)(mask >> 6 * 8); | |
83 | block->hdr.bitmap[7] |= (u8)(mask >> 7 * 8); | |
63a4681f DH |
84 | } |
85 | ||
86 | /* | |
87 | * Clear a number of contiguous bits in the directory block bitmap. | |
88 | */ | |
89 | static void afs_clear_contig_bits(union afs_xdr_dir_block *block, | |
90 | int bit, unsigned int nr_slots) | |
91 | { | |
51590df4 | 92 | u64 mask; |
63a4681f DH |
93 | |
94 | mask = (1 << nr_slots) - 1; | |
95 | mask <<= bit; | |
96 | ||
63a4681f DH |
97 | block->hdr.bitmap[0] &= ~(u8)(mask >> 0 * 8); |
98 | block->hdr.bitmap[1] &= ~(u8)(mask >> 1 * 8); | |
99 | block->hdr.bitmap[2] &= ~(u8)(mask >> 2 * 8); | |
100 | block->hdr.bitmap[3] &= ~(u8)(mask >> 3 * 8); | |
101 | block->hdr.bitmap[4] &= ~(u8)(mask >> 4 * 8); | |
102 | block->hdr.bitmap[5] &= ~(u8)(mask >> 5 * 8); | |
103 | block->hdr.bitmap[6] &= ~(u8)(mask >> 6 * 8); | |
104 | block->hdr.bitmap[7] &= ~(u8)(mask >> 7 * 8); | |
63a4681f DH |
105 | } |
106 | ||
255ed636 DH |
107 | /* |
108 | * Get a new directory folio. | |
109 | */ | |
110 | static struct folio *afs_dir_get_folio(struct afs_vnode *vnode, pgoff_t index) | |
111 | { | |
874c8ca1 | 112 | struct address_space *mapping = vnode->netfs.inode.i_mapping; |
255ed636 DH |
113 | struct folio *folio; |
114 | ||
115 | folio = __filemap_get_folio(mapping, index, | |
116 | FGP_LOCK | FGP_ACCESSED | FGP_CREAT, | |
117 | mapping->gfp_mask); | |
58f5f669 | 118 | if (IS_ERR(folio)) { |
255ed636 | 119 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); |
58f5f669 CH |
120 | return NULL; |
121 | } | |
122 | if (!folio_test_private(folio)) | |
255ed636 | 123 | folio_attach_private(folio, (void *)1); |
255ed636 DH |
124 | return folio; |
125 | } | |
126 | ||
63a4681f DH |
127 | /* |
128 | * Scan a directory block looking for a dirent of the right name. | |
129 | */ | |
130 | static int afs_dir_scan_block(union afs_xdr_dir_block *block, struct qstr *name, | |
131 | unsigned int blocknum) | |
132 | { | |
133 | union afs_xdr_dirent *de; | |
134 | u64 bitmap; | |
135 | int d, len, n; | |
136 | ||
137 | _enter(""); | |
138 | ||
139 | bitmap = (u64)block->hdr.bitmap[0] << 0 * 8; | |
140 | bitmap |= (u64)block->hdr.bitmap[1] << 1 * 8; | |
141 | bitmap |= (u64)block->hdr.bitmap[2] << 2 * 8; | |
142 | bitmap |= (u64)block->hdr.bitmap[3] << 3 * 8; | |
143 | bitmap |= (u64)block->hdr.bitmap[4] << 4 * 8; | |
144 | bitmap |= (u64)block->hdr.bitmap[5] << 5 * 8; | |
145 | bitmap |= (u64)block->hdr.bitmap[6] << 6 * 8; | |
146 | bitmap |= (u64)block->hdr.bitmap[7] << 7 * 8; | |
147 | ||
148 | for (d = (blocknum == 0 ? AFS_DIR_RESV_BLOCKS0 : AFS_DIR_RESV_BLOCKS); | |
149 | d < AFS_DIR_SLOTS_PER_BLOCK; | |
150 | d++) { | |
151 | if (!((bitmap >> d) & 1)) | |
152 | continue; | |
153 | de = &block->dirents[d]; | |
154 | if (de->u.valid != 1) | |
155 | continue; | |
156 | ||
157 | /* The block was NUL-terminated by afs_dir_check_page(). */ | |
158 | len = strlen(de->u.name); | |
159 | if (len == name->len && | |
160 | memcmp(de->u.name, name->name, name->len) == 0) | |
161 | return d; | |
162 | ||
163 | n = round_up(12 + len + 1 + 4, AFS_DIR_DIRENT_SIZE); | |
164 | n /= AFS_DIR_DIRENT_SIZE; | |
165 | d += n - 1; | |
166 | } | |
167 | ||
168 | return -1; | |
169 | } | |
170 | ||
171 | /* | |
172 | * Initialise a new directory block. Note that block 0 is special and contains | |
173 | * some extra metadata. | |
174 | */ | |
175 | static void afs_edit_init_block(union afs_xdr_dir_block *meta, | |
176 | union afs_xdr_dir_block *block, int block_num) | |
177 | { | |
178 | memset(block, 0, sizeof(*block)); | |
179 | block->hdr.npages = htons(1); | |
180 | block->hdr.magic = AFS_DIR_MAGIC; | |
181 | block->hdr.bitmap[0] = 1; | |
182 | ||
183 | if (block_num == 0) { | |
184 | block->hdr.bitmap[0] = 0xff; | |
185 | block->hdr.bitmap[1] = 0x1f; | |
186 | memset(block->meta.alloc_ctrs, | |
187 | AFS_DIR_SLOTS_PER_BLOCK, | |
188 | sizeof(block->meta.alloc_ctrs)); | |
189 | meta->meta.alloc_ctrs[0] = | |
190 | AFS_DIR_SLOTS_PER_BLOCK - AFS_DIR_RESV_BLOCKS0; | |
191 | } | |
192 | ||
193 | if (block_num < AFS_DIR_BLOCKS_WITH_CTR) | |
194 | meta->meta.alloc_ctrs[block_num] = | |
195 | AFS_DIR_SLOTS_PER_BLOCK - AFS_DIR_RESV_BLOCKS; | |
196 | } | |
197 | ||
198 | /* | |
199 | * Edit a directory's file data to add a new directory entry. Doing this after | |
200 | * create, mkdir, symlink, link or rename if the data version number is | |
201 | * incremented by exactly one avoids the need to re-download the entire | |
202 | * directory contents. | |
203 | * | |
204 | * The caller must hold the inode locked. | |
205 | */ | |
206 | void afs_edit_dir_add(struct afs_vnode *vnode, | |
207 | struct qstr *name, struct afs_fid *new_fid, | |
208 | enum afs_edit_dir_reason why) | |
209 | { | |
210 | union afs_xdr_dir_block *meta, *block; | |
63a4681f | 211 | union afs_xdr_dirent *de; |
255ed636 | 212 | struct folio *folio0, *folio; |
63a4681f DH |
213 | unsigned int need_slots, nr_blocks, b; |
214 | pgoff_t index; | |
215 | loff_t i_size; | |
63a4681f DH |
216 | int slot; |
217 | ||
218 | _enter(",,{%d,%s},", name->len, name->name); | |
219 | ||
874c8ca1 | 220 | i_size = i_size_read(&vnode->netfs.inode); |
63a4681f DH |
221 | if (i_size > AFS_DIR_BLOCK_SIZE * AFS_DIR_MAX_BLOCKS || |
222 | (i_size & (AFS_DIR_BLOCK_SIZE - 1))) { | |
223 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
224 | return; | |
225 | } | |
226 | ||
255ed636 DH |
227 | folio0 = afs_dir_get_folio(vnode, 0); |
228 | if (!folio0) { | |
63a4681f DH |
229 | _leave(" [fgp]"); |
230 | return; | |
231 | } | |
232 | ||
233 | /* Work out how many slots we're going to need. */ | |
366911cd | 234 | need_slots = afs_dir_calc_slots(name->len); |
63a4681f | 235 | |
255ed636 | 236 | meta = kmap_local_folio(folio0, 0); |
63a4681f DH |
237 | if (i_size == 0) |
238 | goto new_directory; | |
239 | nr_blocks = i_size / AFS_DIR_BLOCK_SIZE; | |
240 | ||
255ed636 | 241 | /* Find a block that has sufficient slots available. Each folio |
63a4681f DH |
242 | * contains two or more directory blocks. |
243 | */ | |
244 | for (b = 0; b < nr_blocks + 1; b++) { | |
255ed636 DH |
245 | /* If the directory extended into a new folio, then we need to |
246 | * tack a new folio on the end. | |
63a4681f DH |
247 | */ |
248 | index = b / AFS_DIR_BLOCKS_PER_PAGE; | |
255ed636 DH |
249 | if (nr_blocks >= AFS_DIR_MAX_BLOCKS) |
250 | goto error; | |
251 | if (index >= folio_nr_pages(folio0)) { | |
252 | folio = afs_dir_get_folio(vnode, index); | |
253 | if (!folio) | |
63a4681f | 254 | goto error; |
255ed636 DH |
255 | } else { |
256 | folio = folio0; | |
63a4681f DH |
257 | } |
258 | ||
255ed636 DH |
259 | block = kmap_local_folio(folio, b * AFS_DIR_BLOCK_SIZE - folio_file_pos(folio)); |
260 | ||
63a4681f DH |
261 | /* Abandon the edit if we got a callback break. */ |
262 | if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags)) | |
263 | goto invalidated; | |
264 | ||
63a4681f DH |
265 | _debug("block %u: %2u %3u %u", |
266 | b, | |
267 | (b < AFS_DIR_BLOCKS_WITH_CTR) ? meta->meta.alloc_ctrs[b] : 99, | |
268 | ntohs(block->hdr.npages), | |
269 | ntohs(block->hdr.magic)); | |
270 | ||
271 | /* Initialise the block if necessary. */ | |
272 | if (b == nr_blocks) { | |
273 | _debug("init %u", b); | |
274 | afs_edit_init_block(meta, block, b); | |
9d37e1ca | 275 | afs_set_i_size(vnode, (b + 1) * AFS_DIR_BLOCK_SIZE); |
63a4681f DH |
276 | } |
277 | ||
255ed636 | 278 | /* Only lower dir blocks have a counter in the header. */ |
63a4681f DH |
279 | if (b >= AFS_DIR_BLOCKS_WITH_CTR || |
280 | meta->meta.alloc_ctrs[b] >= need_slots) { | |
281 | /* We need to try and find one or more consecutive | |
282 | * slots to hold the entry. | |
283 | */ | |
284 | slot = afs_find_contig_bits(block, need_slots); | |
285 | if (slot >= 0) { | |
286 | _debug("slot %u", slot); | |
287 | goto found_space; | |
288 | } | |
289 | } | |
290 | ||
255ed636 DH |
291 | kunmap_local(block); |
292 | if (folio != folio0) { | |
293 | folio_unlock(folio); | |
294 | folio_put(folio); | |
63a4681f DH |
295 | } |
296 | } | |
297 | ||
298 | /* There are no spare slots of sufficient size, yet the operation | |
299 | * succeeded. Download the directory again. | |
300 | */ | |
301 | trace_afs_edit_dir(vnode, why, afs_edit_dir_create_nospc, 0, 0, 0, 0, name->name); | |
302 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
303 | goto out_unmap; | |
304 | ||
305 | new_directory: | |
306 | afs_edit_init_block(meta, meta, 0); | |
307 | i_size = AFS_DIR_BLOCK_SIZE; | |
9d37e1ca | 308 | afs_set_i_size(vnode, i_size); |
63a4681f | 309 | slot = AFS_DIR_RESV_BLOCKS0; |
255ed636 DH |
310 | folio = folio0; |
311 | block = kmap_local_folio(folio, 0); | |
63a4681f DH |
312 | nr_blocks = 1; |
313 | b = 0; | |
314 | ||
315 | found_space: | |
316 | /* Set the dirent slot. */ | |
317 | trace_afs_edit_dir(vnode, why, afs_edit_dir_create, b, slot, | |
318 | new_fid->vnode, new_fid->unique, name->name); | |
319 | de = &block->dirents[slot]; | |
320 | de->u.valid = 1; | |
321 | de->u.unused[0] = 0; | |
322 | de->u.hash_next = 0; // TODO: Really need to maintain this | |
323 | de->u.vnode = htonl(new_fid->vnode); | |
324 | de->u.unique = htonl(new_fid->unique); | |
325 | memcpy(de->u.name, name->name, name->len + 1); | |
326 | de->u.name[name->len] = 0; | |
327 | ||
328 | /* Adjust the bitmap. */ | |
329 | afs_set_contig_bits(block, slot, need_slots); | |
255ed636 DH |
330 | kunmap_local(block); |
331 | if (folio != folio0) { | |
332 | folio_unlock(folio); | |
333 | folio_put(folio); | |
63a4681f DH |
334 | } |
335 | ||
336 | /* Adjust the allocation counter. */ | |
337 | if (b < AFS_DIR_BLOCKS_WITH_CTR) | |
338 | meta->meta.alloc_ctrs[b] -= need_slots; | |
339 | ||
874c8ca1 | 340 | inode_inc_iversion_raw(&vnode->netfs.inode); |
63a4681f DH |
341 | afs_stat_v(vnode, n_dir_cr); |
342 | _debug("Insert %s in %u[%u]", name->name, b, slot); | |
343 | ||
344 | out_unmap: | |
255ed636 DH |
345 | kunmap_local(meta); |
346 | folio_unlock(folio0); | |
347 | folio_put(folio0); | |
63a4681f DH |
348 | _leave(""); |
349 | return; | |
350 | ||
351 | invalidated: | |
352 | trace_afs_edit_dir(vnode, why, afs_edit_dir_create_inval, 0, 0, 0, 0, name->name); | |
353 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
255ed636 DH |
354 | kunmap_local(block); |
355 | if (folio != folio0) { | |
356 | folio_unlock(folio); | |
357 | folio_put(folio); | |
63a4681f DH |
358 | } |
359 | goto out_unmap; | |
360 | ||
361 | error: | |
362 | trace_afs_edit_dir(vnode, why, afs_edit_dir_create_error, 0, 0, 0, 0, name->name); | |
363 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
364 | goto out_unmap; | |
365 | } | |
366 | ||
367 | /* | |
368 | * Edit a directory's file data to remove a new directory entry. Doing this | |
369 | * after unlink, rmdir or rename if the data version number is incremented by | |
370 | * exactly one avoids the need to re-download the entire directory contents. | |
371 | * | |
372 | * The caller must hold the inode locked. | |
373 | */ | |
374 | void afs_edit_dir_remove(struct afs_vnode *vnode, | |
375 | struct qstr *name, enum afs_edit_dir_reason why) | |
376 | { | |
63a4681f DH |
377 | union afs_xdr_dir_block *meta, *block; |
378 | union afs_xdr_dirent *de; | |
255ed636 | 379 | struct folio *folio0, *folio; |
63a4681f DH |
380 | unsigned int need_slots, nr_blocks, b; |
381 | pgoff_t index; | |
382 | loff_t i_size; | |
383 | int slot; | |
384 | ||
385 | _enter(",,{%d,%s},", name->len, name->name); | |
386 | ||
874c8ca1 | 387 | i_size = i_size_read(&vnode->netfs.inode); |
63a4681f DH |
388 | if (i_size < AFS_DIR_BLOCK_SIZE || |
389 | i_size > AFS_DIR_BLOCK_SIZE * AFS_DIR_MAX_BLOCKS || | |
390 | (i_size & (AFS_DIR_BLOCK_SIZE - 1))) { | |
391 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
392 | return; | |
393 | } | |
394 | nr_blocks = i_size / AFS_DIR_BLOCK_SIZE; | |
395 | ||
255ed636 DH |
396 | folio0 = afs_dir_get_folio(vnode, 0); |
397 | if (!folio0) { | |
63a4681f DH |
398 | _leave(" [fgp]"); |
399 | return; | |
400 | } | |
401 | ||
402 | /* Work out how many slots we're going to discard. */ | |
366911cd | 403 | need_slots = afs_dir_calc_slots(name->len); |
63a4681f | 404 | |
255ed636 | 405 | meta = kmap_local_folio(folio0, 0); |
63a4681f | 406 | |
255ed636 | 407 | /* Find a block that has sufficient slots available. Each folio |
63a4681f DH |
408 | * contains two or more directory blocks. |
409 | */ | |
410 | for (b = 0; b < nr_blocks; b++) { | |
411 | index = b / AFS_DIR_BLOCKS_PER_PAGE; | |
255ed636 DH |
412 | if (index >= folio_nr_pages(folio0)) { |
413 | folio = afs_dir_get_folio(vnode, index); | |
414 | if (!folio) | |
63a4681f | 415 | goto error; |
63a4681f | 416 | } else { |
255ed636 | 417 | folio = folio0; |
63a4681f DH |
418 | } |
419 | ||
255ed636 DH |
420 | block = kmap_local_folio(folio, b * AFS_DIR_BLOCK_SIZE - folio_file_pos(folio)); |
421 | ||
63a4681f DH |
422 | /* Abandon the edit if we got a callback break. */ |
423 | if (!test_bit(AFS_VNODE_DIR_VALID, &vnode->flags)) | |
424 | goto invalidated; | |
425 | ||
63a4681f DH |
426 | if (b > AFS_DIR_BLOCKS_WITH_CTR || |
427 | meta->meta.alloc_ctrs[b] <= AFS_DIR_SLOTS_PER_BLOCK - 1 - need_slots) { | |
428 | slot = afs_dir_scan_block(block, name, b); | |
429 | if (slot >= 0) | |
430 | goto found_dirent; | |
431 | } | |
432 | ||
255ed636 DH |
433 | kunmap_local(block); |
434 | if (folio != folio0) { | |
435 | folio_unlock(folio); | |
436 | folio_put(folio); | |
63a4681f DH |
437 | } |
438 | } | |
439 | ||
440 | /* Didn't find the dirent to clobber. Download the directory again. */ | |
441 | trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_noent, | |
442 | 0, 0, 0, 0, name->name); | |
443 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
444 | goto out_unmap; | |
445 | ||
446 | found_dirent: | |
447 | de = &block->dirents[slot]; | |
448 | ||
449 | trace_afs_edit_dir(vnode, why, afs_edit_dir_delete, b, slot, | |
450 | ntohl(de->u.vnode), ntohl(de->u.unique), | |
451 | name->name); | |
452 | ||
453 | memset(de, 0, sizeof(*de) * need_slots); | |
454 | ||
455 | /* Adjust the bitmap. */ | |
456 | afs_clear_contig_bits(block, slot, need_slots); | |
255ed636 DH |
457 | kunmap_local(block); |
458 | if (folio != folio0) { | |
459 | folio_unlock(folio); | |
460 | folio_put(folio); | |
63a4681f DH |
461 | } |
462 | ||
463 | /* Adjust the allocation counter. */ | |
464 | if (b < AFS_DIR_BLOCKS_WITH_CTR) | |
465 | meta->meta.alloc_ctrs[b] += need_slots; | |
466 | ||
874c8ca1 | 467 | inode_set_iversion_raw(&vnode->netfs.inode, vnode->status.data_version); |
63a4681f DH |
468 | afs_stat_v(vnode, n_dir_rm); |
469 | _debug("Remove %s from %u[%u]", name->name, b, slot); | |
470 | ||
471 | out_unmap: | |
255ed636 DH |
472 | kunmap_local(meta); |
473 | folio_unlock(folio0); | |
474 | folio_put(folio0); | |
63a4681f DH |
475 | _leave(""); |
476 | return; | |
477 | ||
478 | invalidated: | |
479 | trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_inval, | |
480 | 0, 0, 0, 0, name->name); | |
481 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
255ed636 DH |
482 | kunmap_local(block); |
483 | if (folio != folio0) { | |
484 | folio_unlock(folio); | |
485 | folio_put(folio); | |
63a4681f DH |
486 | } |
487 | goto out_unmap; | |
488 | ||
489 | error: | |
490 | trace_afs_edit_dir(vnode, why, afs_edit_dir_delete_error, | |
491 | 0, 0, 0, 0, name->name); | |
492 | clear_bit(AFS_VNODE_DIR_VALID, &vnode->flags); | |
493 | goto out_unmap; | |
494 | } |