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