quotaio_tree.c revision 0ce0172984c807d3366e8db6e2a2b6ecae314832
1/* 2 * Implementation of new quotafile format 3 * 4 * Jan Kara <jack@suse.cz> - sponsored by SuSE CR 5 */ 6 7#include "config.h" 8#include <sys/types.h> 9#include <errno.h> 10#include <stdio.h> 11#include <stdlib.h> 12#include <string.h> 13#include <unistd.h> 14 15#include "common.h" 16#include "quotaio_tree.h" 17#include "quotaio.h" 18 19typedef char *dqbuf_t; 20 21#define freedqbuf(buf) ext2fs_free_mem(&buf) 22 23static inline dqbuf_t getdqbuf(void) 24{ 25 dqbuf_t buf; 26 if (ext2fs_get_memzero(QT_BLKSIZE, &buf)) { 27 log_err("Failed to allocate dqbuf"); 28 return NULL; 29 } 30 31 return buf; 32} 33 34/* Is given dquot empty? */ 35int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk) 36{ 37 int i; 38 39 for (i = 0; i < info->dqi_entry_size; i++) 40 if (disk[i]) 41 return 0; 42 return 1; 43} 44 45int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info) 46{ 47 return (QT_BLKSIZE - sizeof(struct qt_disk_dqdbheader)) / 48 info->dqi_entry_size; 49} 50 51static int get_index(qid_t id, int depth) 52{ 53 return (id >> ((QT_TREEDEPTH - depth - 1) * 8)) & 0xff; 54} 55 56/* Read given block */ 57static void read_blk(struct quota_handle *h, uint blk, dqbuf_t buf) 58{ 59 int err; 60 61 err = h->e2fs_read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf, 62 QT_BLKSIZE); 63 if (err < 0) 64 log_err("Cannot read block %u: %s", blk, strerror(errno)); 65 else if (err != QT_BLKSIZE) 66 memset(buf + err, 0, QT_BLKSIZE - err); 67} 68 69/* Write block */ 70static int write_blk(struct quota_handle *h, uint blk, dqbuf_t buf) 71{ 72 int err; 73 74 err = h->e2fs_write(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf, 75 QT_BLKSIZE); 76 if (err < 0 && errno != ENOSPC) 77 log_err("Cannot write block (%u): %s", blk, strerror(errno)); 78 if (err != QT_BLKSIZE) 79 return -ENOSPC; 80 return 0; 81} 82 83/* Get free block in file (either from free list or create new one) */ 84static int get_free_dqblk(struct quota_handle *h) 85{ 86 dqbuf_t buf = getdqbuf(); 87 struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; 88 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 89 int blk; 90 91 if (!buf) 92 return -ENOMEM; 93 94 if (info->dqi_free_blk) { 95 blk = info->dqi_free_blk; 96 read_blk(h, blk, buf); 97 info->dqi_free_blk = ext2fs_le32_to_cpu(dh->dqdh_next_free); 98 } else { 99 memset(buf, 0, QT_BLKSIZE); 100 /* Assure block allocation... */ 101 if (write_blk(h, info->dqi_blocks, buf) < 0) { 102 freedqbuf(buf); 103 log_err("Cannot allocate new quota block " 104 "(out of disk space)."); 105 return -ENOSPC; 106 } 107 blk = info->dqi_blocks++; 108 } 109 mark_quotafile_info_dirty(h); 110 freedqbuf(buf); 111 return blk; 112} 113 114/* Put given block to free list */ 115static void put_free_dqblk(struct quota_handle *h, dqbuf_t buf, uint blk) 116{ 117 struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; 118 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 119 120 dh->dqdh_next_free = ext2fs_cpu_to_le32(info->dqi_free_blk); 121 dh->dqdh_prev_free = ext2fs_cpu_to_le32(0); 122 dh->dqdh_entries = ext2fs_cpu_to_le16(0); 123 info->dqi_free_blk = blk; 124 mark_quotafile_info_dirty(h); 125 write_blk(h, blk, buf); 126} 127 128/* Remove given block from the list of blocks with free entries */ 129static void remove_free_dqentry(struct quota_handle *h, dqbuf_t buf, uint blk) 130{ 131 dqbuf_t tmpbuf = getdqbuf(); 132 struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; 133 uint nextblk = ext2fs_le32_to_cpu(dh->dqdh_next_free), prevblk = 134 135 ext2fs_le32_to_cpu(dh->dqdh_prev_free); 136 137 if (!tmpbuf) 138 return; 139 140 if (nextblk) { 141 read_blk(h, nextblk, tmpbuf); 142 ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = 143 dh->dqdh_prev_free; 144 write_blk(h, nextblk, tmpbuf); 145 } 146 if (prevblk) { 147 read_blk(h, prevblk, tmpbuf); 148 ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_next_free = 149 dh->dqdh_next_free; 150 write_blk(h, prevblk, tmpbuf); 151 } else { 152 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = nextblk; 153 mark_quotafile_info_dirty(h); 154 } 155 freedqbuf(tmpbuf); 156 dh->dqdh_next_free = dh->dqdh_prev_free = ext2fs_cpu_to_le32(0); 157 write_blk(h, blk, buf); /* No matter whether write succeeds 158 * block is out of list */ 159} 160 161/* Insert given block to the beginning of list with free entries */ 162static void insert_free_dqentry(struct quota_handle *h, dqbuf_t buf, uint blk) 163{ 164 dqbuf_t tmpbuf = getdqbuf(); 165 struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; 166 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 167 168 if (!tmpbuf) 169 return; 170 171 dh->dqdh_next_free = ext2fs_cpu_to_le32(info->dqi_free_entry); 172 dh->dqdh_prev_free = ext2fs_cpu_to_le32(0); 173 write_blk(h, blk, buf); 174 if (info->dqi_free_entry) { 175 read_blk(h, info->dqi_free_entry, tmpbuf); 176 ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = 177 ext2fs_cpu_to_le32(blk); 178 write_blk(h, info->dqi_free_entry, tmpbuf); 179 } 180 freedqbuf(tmpbuf); 181 info->dqi_free_entry = blk; 182 mark_quotafile_info_dirty(h); 183} 184 185/* Find space for dquot */ 186static uint find_free_dqentry(struct quota_handle *h, struct dquot *dquot, 187 int *err) 188{ 189 int blk, i; 190 struct qt_disk_dqdbheader *dh; 191 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 192 char *ddquot; 193 dqbuf_t buf; 194 195 *err = 0; 196 buf = getdqbuf(); 197 if (!buf) { 198 *err = -ENOMEM; 199 return 0; 200 } 201 202 dh = (struct qt_disk_dqdbheader *)buf; 203 if (info->dqi_free_entry) { 204 blk = info->dqi_free_entry; 205 read_blk(h, blk, buf); 206 } else { 207 blk = get_free_dqblk(h); 208 if (blk < 0) { 209 freedqbuf(buf); 210 *err = blk; 211 return 0; 212 } 213 memset(buf, 0, QT_BLKSIZE); 214 info->dqi_free_entry = blk; 215 mark_quotafile_info_dirty(h); 216 } 217 218 /* Block will be full? */ 219 if (ext2fs_le16_to_cpu(dh->dqdh_entries) + 1 >= 220 qtree_dqstr_in_blk(info)) 221 remove_free_dqentry(h, buf, blk); 222 223 dh->dqdh_entries = 224 ext2fs_cpu_to_le16(ext2fs_le16_to_cpu(dh->dqdh_entries) + 1); 225 /* Find free structure in block */ 226 ddquot = buf + sizeof(struct qt_disk_dqdbheader); 227 for (i = 0; 228 i < qtree_dqstr_in_blk(info) && !qtree_entry_unused(info, ddquot); 229 i++) 230 ddquot += info->dqi_entry_size; 231 232 if (i == qtree_dqstr_in_blk(info)) 233 log_err("find_free_dqentry(): Data block full unexpectedly."); 234 235 write_blk(h, blk, buf); 236 dquot->dq_dqb.u.v2_mdqb.dqb_off = 237 (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) + 238 i * info->dqi_entry_size; 239 freedqbuf(buf); 240 return blk; 241} 242 243/* Insert reference to structure into the trie */ 244static int do_insert_tree(struct quota_handle *h, struct dquot *dquot, 245 uint * treeblk, int depth) 246{ 247 dqbuf_t buf; 248 int newson = 0, newact = 0; 249 u_int32_t *ref; 250 uint newblk; 251 int ret = 0; 252 253 log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth); 254 buf = getdqbuf(); 255 if (!buf) 256 return -ENOMEM; 257 258 if (!*treeblk) { 259 ret = get_free_dqblk(h); 260 if (ret < 0) 261 goto out_buf; 262 *treeblk = ret; 263 memset(buf, 0, QT_BLKSIZE); 264 newact = 1; 265 } else { 266 read_blk(h, *treeblk, buf); 267 } 268 269 ref = (u_int32_t *) buf; 270 newblk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); 271 if (!newblk) 272 newson = 1; 273 if (depth == QT_TREEDEPTH - 1) { 274 if (newblk) 275 log_err("Inserting already present quota entry " 276 "(block %u).", 277 ref[get_index(dquot->dq_id, depth)]); 278 newblk = find_free_dqentry(h, dquot, &ret); 279 } else { 280 ret = do_insert_tree(h, dquot, &newblk, depth + 1); 281 } 282 283 if (newson && ret >= 0) { 284 ref[get_index(dquot->dq_id, depth)] = 285 ext2fs_cpu_to_le32(newblk); 286 write_blk(h, *treeblk, buf); 287 } else if (newact && ret < 0) { 288 put_free_dqblk(h, buf, *treeblk); 289 } 290 291out_buf: 292 freedqbuf(buf); 293 return ret; 294} 295 296/* Wrapper for inserting quota structure into tree */ 297static void dq_insert_tree(struct quota_handle *h, struct dquot *dquot) 298{ 299 uint tmp = QT_TREEOFF; 300 301 if (do_insert_tree(h, dquot, &tmp, 0) < 0) 302 log_err("Cannot write quota (id %u): %s", 303 (uint) dquot->dq_id, strerror(errno)); 304} 305 306/* Write dquot to file */ 307void qtree_write_dquot(struct dquot *dquot) 308{ 309 ssize_t ret; 310 char *ddquot; 311 struct quota_handle *h = dquot->dq_h; 312 struct qtree_mem_dqinfo *info = 313 &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree; 314 log_debug("writing ddquot 1: off=%llu, info->dqi_entry_size=%u", 315 dquot->dq_dqb.u.v2_mdqb.dqb_off, 316 info->dqi_entry_size); 317 ret = ext2fs_get_mem(info->dqi_entry_size, &ddquot); 318 if (ret) { 319 errno = ENOMEM; 320 log_err("Quota write failed (id %u): %s", 321 (uint)dquot->dq_id, strerror(errno)); 322 return; 323 } 324 325 if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) 326 dq_insert_tree(dquot->dq_h, dquot); 327 info->dqi_ops->mem2disk_dqblk(ddquot, dquot); 328 log_debug("writing ddquot 2: off=%llu, info->dqi_entry_size=%u", 329 dquot->dq_dqb.u.v2_mdqb.dqb_off, 330 info->dqi_entry_size); 331 ret = h->e2fs_write(&h->qh_qf, dquot->dq_dqb.u.v2_mdqb.dqb_off, ddquot, 332 info->dqi_entry_size); 333 334 if (ret != info->dqi_entry_size) { 335 if (ret > 0) 336 errno = ENOSPC; 337 log_err("Quota write failed (id %u): %s", 338 (uint)dquot->dq_id, strerror(errno)); 339 } 340 ext2fs_free_mem(&ddquot); 341} 342 343/* Free dquot entry in data block */ 344static void free_dqentry(struct quota_handle *h, struct dquot *dquot, uint blk) 345{ 346 struct qt_disk_dqdbheader *dh; 347 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 348 dqbuf_t buf = getdqbuf(); 349 350 if (!buf) 351 return; 352 353 if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS != blk) 354 log_err("Quota structure has offset to other block (%u) " 355 "than it should (%u).", blk, 356 (uint) (dquot->dq_dqb.u.v2_mdqb.dqb_off >> 357 QT_BLKSIZE_BITS)); 358 359 read_blk(h, blk, buf); 360 dh = (struct qt_disk_dqdbheader *)buf; 361 dh->dqdh_entries = 362 ext2fs_cpu_to_le16(ext2fs_le16_to_cpu(dh->dqdh_entries) - 1); 363 364 if (!ext2fs_le16_to_cpu(dh->dqdh_entries)) { /* Block got free? */ 365 remove_free_dqentry(h, buf, blk); 366 put_free_dqblk(h, buf, blk); 367 } else { 368 memset(buf + (dquot->dq_dqb.u.v2_mdqb.dqb_off & 369 ((1 << QT_BLKSIZE_BITS) - 1)), 370 0, info->dqi_entry_size); 371 372 /* First free entry? */ 373 if (ext2fs_le16_to_cpu(dh->dqdh_entries) == 374 qtree_dqstr_in_blk(info) - 1) 375 /* This will also write data block */ 376 insert_free_dqentry(h, buf, blk); 377 else 378 write_blk(h, blk, buf); 379 } 380 dquot->dq_dqb.u.v2_mdqb.dqb_off = 0; 381 freedqbuf(buf); 382} 383 384/* Remove reference to dquot from tree */ 385static void remove_tree(struct quota_handle *h, struct dquot *dquot, 386 uint * blk, int depth) 387{ 388 dqbuf_t buf = getdqbuf(); 389 uint newblk; 390 u_int32_t *ref = (u_int32_t *) buf; 391 392 if (!buf) 393 return; 394 395 read_blk(h, *blk, buf); 396 newblk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); 397 if (depth == QT_TREEDEPTH - 1) { 398 free_dqentry(h, dquot, newblk); 399 newblk = 0; 400 } else { 401 remove_tree(h, dquot, &newblk, depth + 1); 402 } 403 404 if (!newblk) { 405 int i; 406 407 ref[get_index(dquot->dq_id, depth)] = ext2fs_cpu_to_le32(0); 408 409 /* Block got empty? */ 410 for (i = 0; i < QT_BLKSIZE && !buf[i]; i++); 411 412 /* Don't put the root block into the free block list */ 413 if (i == QT_BLKSIZE && *blk != QT_TREEOFF) { 414 put_free_dqblk(h, buf, *blk); 415 *blk = 0; 416 } else { 417 write_blk(h, *blk, buf); 418 } 419 } 420 freedqbuf(buf); 421} 422 423/* Delete dquot from tree */ 424void qtree_delete_dquot(struct dquot *dquot) 425{ 426 uint tmp = QT_TREEOFF; 427 428 if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) /* Even not allocated? */ 429 return; 430 remove_tree(dquot->dq_h, dquot, &tmp, 0); 431} 432 433/* Find entry in block */ 434static ext2_loff_t find_block_dqentry(struct quota_handle *h, 435 struct dquot *dquot, uint blk) 436{ 437 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 438 dqbuf_t buf = getdqbuf(); 439 int i; 440 char *ddquot = buf + sizeof(struct qt_disk_dqdbheader); 441 442 if (!buf) 443 return -ENOMEM; 444 445 read_blk(h, blk, buf); 446 for (i = 0; 447 i < qtree_dqstr_in_blk(info) && !info->dqi_ops->is_id(ddquot, dquot); 448 i++) 449 ddquot += info->dqi_entry_size; 450 451 if (i == qtree_dqstr_in_blk(info)) 452 log_err("Quota for id %u referenced but not present.", 453 dquot->dq_id); 454 freedqbuf(buf); 455 return (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) + 456 i * info->dqi_entry_size; 457} 458 459/* Find entry for given id in the tree */ 460static ext2_loff_t find_tree_dqentry(struct quota_handle *h, 461 struct dquot *dquot, 462 uint blk, int depth) 463{ 464 dqbuf_t buf = getdqbuf(); 465 ext2_loff_t ret = 0; 466 u_int32_t *ref = (u_int32_t *) buf; 467 468 if (!buf) 469 return -ENOMEM; 470 471 read_blk(h, blk, buf); 472 ret = 0; 473 blk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); 474 if (!blk) /* No reference? */ 475 goto out_buf; 476 if (depth < QT_TREEDEPTH - 1) 477 ret = find_tree_dqentry(h, dquot, blk, depth + 1); 478 else 479 ret = find_block_dqentry(h, dquot, blk); 480out_buf: 481 freedqbuf(buf); 482 return ret; 483} 484 485/* Find entry for given id in the tree - wrapper function */ 486static inline ext2_loff_t find_dqentry(struct quota_handle *h, 487 struct dquot *dquot) 488{ 489 return find_tree_dqentry(h, dquot, QT_TREEOFF, 0); 490} 491 492/* 493 * Read dquot from disk. 494 */ 495struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id) 496{ 497 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; 498 ext2_loff_t offset; 499 ssize_t ret; 500 char *ddquot; 501 struct dquot *dquot = get_empty_dquot(); 502 503 if (!dquot) 504 return NULL; 505 if (ext2fs_get_mem(info->dqi_entry_size, &ddquot)) { 506 ext2fs_free_mem(&dquot); 507 return NULL; 508 } 509 510 dquot->dq_id = id; 511 dquot->dq_h = h; 512 dquot->dq_dqb.u.v2_mdqb.dqb_off = 0; 513 memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk)); 514 515 offset = find_dqentry(h, dquot); 516 if (offset > 0) { 517 dquot->dq_dqb.u.v2_mdqb.dqb_off = offset; 518 ret = h->e2fs_read(&h->qh_qf, offset, ddquot, 519 info->dqi_entry_size); 520 if (ret != info->dqi_entry_size) { 521 if (ret > 0) 522 errno = EIO; 523 log_err("Cannot read quota structure for id %u: %s", 524 dquot->dq_id, strerror(errno)); 525 } 526 info->dqi_ops->disk2mem_dqblk(dquot, ddquot); 527 } 528 ext2fs_free_mem(&ddquot); 529 return dquot; 530} 531 532/* 533 * Scan all dquots in file and call callback on each 534 */ 535#define set_bit(bmp, ind) ((bmp)[(ind) >> 3] |= (1 << ((ind) & 7))) 536#define get_bit(bmp, ind) ((bmp)[(ind) >> 3] & (1 << ((ind) & 7))) 537 538static int report_block(struct dquot *dquot, uint blk, char *bitmap, 539 int (*process_dquot) (struct dquot *, void *), 540 void *data) 541{ 542 struct qtree_mem_dqinfo *info = 543 &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree; 544 dqbuf_t buf = getdqbuf(); 545 struct qt_disk_dqdbheader *dh; 546 char *ddata; 547 int entries, i; 548 549 if (!buf) 550 return 0; 551 552 set_bit(bitmap, blk); 553 read_blk(dquot->dq_h, blk, buf); 554 dh = (struct qt_disk_dqdbheader *)buf; 555 ddata = buf + sizeof(struct qt_disk_dqdbheader); 556 entries = ext2fs_le16_to_cpu(dh->dqdh_entries); 557 for (i = 0; i < qtree_dqstr_in_blk(info); 558 i++, ddata += info->dqi_entry_size) 559 if (!qtree_entry_unused(info, ddata)) { 560 dquot->dq_dqb.u.v2_mdqb.dqb_off = 561 (blk << QT_BLKSIZE_BITS) + 562 sizeof(struct qt_disk_dqdbheader) + 563 i * info->dqi_entry_size; 564 info->dqi_ops->disk2mem_dqblk(dquot, ddata); 565 if (process_dquot(dquot, data) < 0) 566 break; 567 } 568 freedqbuf(buf); 569 return entries; 570} 571 572static void check_reference(struct quota_handle *h, uint blk) 573{ 574 if (blk >= h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks) 575 log_err("Illegal reference (%u >= %u) in %s quota file. " 576 "Quota file is probably corrupted.\n" 577 "Please run e2fsck (8) to fix it.", 578 blk, 579 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks, 580 type2name(h->qh_type)); 581} 582 583static int report_tree(struct dquot *dquot, uint blk, int depth, char *bitmap, 584 int (*process_dquot) (struct dquot *, void *), 585 void *data) 586{ 587 int entries = 0, i; 588 dqbuf_t buf = getdqbuf(); 589 u_int32_t *ref = (u_int32_t *) buf; 590 591 if (!buf) 592 return 0; 593 594 read_blk(dquot->dq_h, blk, buf); 595 if (depth == QT_TREEDEPTH - 1) { 596 for (i = 0; i < QT_BLKSIZE >> 2; i++) { 597 blk = ext2fs_le32_to_cpu(ref[i]); 598 check_reference(dquot->dq_h, blk); 599 if (blk && !get_bit(bitmap, blk)) 600 entries += report_block(dquot, blk, bitmap, 601 process_dquot, data); 602 } 603 } else { 604 for (i = 0; i < QT_BLKSIZE >> 2; i++) { 605 blk = ext2fs_le32_to_cpu(ref[i]); 606 if (blk) { 607 check_reference(dquot->dq_h, blk); 608 entries += report_tree(dquot, blk, depth + 1, 609 bitmap, process_dquot, 610 data); 611 } 612 } 613 } 614 freedqbuf(buf); 615 return entries; 616} 617 618static uint find_set_bits(char *bmp, int blocks) 619{ 620 uint i, used = 0; 621 622 for (i = 0; i < blocks; i++) 623 if (get_bit(bmp, i)) 624 used++; 625 return used; 626} 627 628int qtree_scan_dquots(struct quota_handle *h, 629 int (*process_dquot) (struct dquot *, void *), 630 void *data) 631{ 632 char *bitmap; 633 struct v2_mem_dqinfo *v2info = &h->qh_info.u.v2_mdqi; 634 struct qtree_mem_dqinfo *info = &v2info->dqi_qtree; 635 struct dquot *dquot = get_empty_dquot(); 636 637 if (!dquot) 638 return -1; 639 640 dquot->dq_h = h; 641 if (ext2fs_get_memzero((info->dqi_blocks + 7) >> 3, &bitmap)) { 642 ext2fs_free_mem(&dquot); 643 return -1; 644 } 645 v2info->dqi_used_entries = report_tree(dquot, QT_TREEOFF, 0, bitmap, 646 process_dquot, data); 647 v2info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks); 648 ext2fs_free_mem(&bitmap); 649 ext2fs_free_mem(&dquot); 650 return 0; 651} 652