2 * Implementation of new quotafile format
4 * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
16 #include "quotaio_tree.h"
19 typedef char *dqbuf_t;
21 #define freedqbuf(buf) ext2fs_free_mem(&buf)
23 static inline dqbuf_t getdqbuf(void)
26 if (ext2fs_get_memzero(QT_BLKSIZE, &buf)) {
27 log_err("Failed to allocate dqbuf");
34 /* Is given dquot empty? */
35 int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk)
39 for (i = 0; i < info->dqi_entry_size; i++)
45 int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info)
47 return (QT_BLKSIZE - sizeof(struct qt_disk_dqdbheader)) /
51 static int get_index(qid_t id, int depth)
53 return (id >> ((QT_TREEDEPTH - depth - 1) * 8)) & 0xff;
56 /* Read given block */
57 static void read_blk(struct quota_handle *h, uint blk, dqbuf_t buf)
61 err = h->e2fs_read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
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);
70 static int write_blk(struct quota_handle *h, uint blk, dqbuf_t buf)
74 err = h->e2fs_write(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
76 if (err < 0 && errno != ENOSPC)
77 log_err("Cannot write block (%u): %s", blk, strerror(errno));
78 if (err != QT_BLKSIZE)
83 /* Get free block in file (either from free list or create new one) */
84 static int get_free_dqblk(struct quota_handle *h)
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;
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);
99 memset(buf, 0, QT_BLKSIZE);
100 /* Assure block allocation... */
101 if (write_blk(h, info->dqi_blocks, buf) < 0) {
103 log_err("Cannot allocate new quota block "
104 "(out of disk space).");
107 blk = info->dqi_blocks++;
109 mark_quotafile_info_dirty(h);
114 /* Put given block to free list */
115 static void put_free_dqblk(struct quota_handle *h, dqbuf_t buf, uint blk)
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;
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);
128 /* Remove given block from the list of blocks with free entries */
129 static void remove_free_dqentry(struct quota_handle *h, dqbuf_t buf, uint blk)
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 =
135 ext2fs_le32_to_cpu(dh->dqdh_prev_free);
141 read_blk(h, nextblk, tmpbuf);
142 ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free =
144 write_blk(h, nextblk, tmpbuf);
147 read_blk(h, prevblk, tmpbuf);
148 ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_next_free =
150 write_blk(h, prevblk, tmpbuf);
152 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = nextblk;
153 mark_quotafile_info_dirty(h);
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 */
161 /* Insert given block to the beginning of list with free entries */
162 static void insert_free_dqentry(struct quota_handle *h, dqbuf_t buf, uint blk)
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;
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);
181 info->dqi_free_entry = blk;
182 mark_quotafile_info_dirty(h);
185 /* Find space for dquot */
186 static uint find_free_dqentry(struct quota_handle *h, struct dquot *dquot,
190 struct qt_disk_dqdbheader *dh;
191 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
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);
207 blk = get_free_dqblk(h);
213 memset(buf, 0, QT_BLKSIZE);
214 info->dqi_free_entry = blk;
215 mark_quotafile_info_dirty(h);
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);
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);
228 i < qtree_dqstr_in_blk(info) && !qtree_entry_unused(info, ddquot);
230 ddquot += info->dqi_entry_size;
232 if (i == qtree_dqstr_in_blk(info))
233 log_err("find_free_dqentry(): Data block full unexpectedly.");
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;
243 /* Insert reference to structure into the trie */
244 static int do_insert_tree(struct quota_handle *h, struct dquot *dquot,
245 uint * treeblk, int depth)
248 int newson = 0, newact = 0;
253 log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth);
259 ret = get_free_dqblk(h);
263 memset(buf, 0, QT_BLKSIZE);
266 read_blk(h, *treeblk, buf);
269 ref = (u_int32_t *) buf;
270 newblk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
273 if (depth == QT_TREEDEPTH - 1) {
275 log_err("Inserting already present quota entry "
277 ref[get_index(dquot->dq_id, depth)]);
278 newblk = find_free_dqentry(h, dquot, &ret);
280 ret = do_insert_tree(h, dquot, &newblk, depth + 1);
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);
296 /* Wrapper for inserting quota structure into tree */
297 static void dq_insert_tree(struct quota_handle *h, struct dquot *dquot)
299 uint tmp = QT_TREEOFF;
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));
306 /* Write dquot to file */
307 void qtree_write_dquot(struct dquot *dquot)
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);
320 log_err("Quota write failed (id %u): %s",
321 (uint)dquot->dq_id, strerror(errno));
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);
334 if (ret != info->dqi_entry_size) {
337 log_err("Quota write failed (id %u): %s",
338 (uint)dquot->dq_id, strerror(errno));
340 ext2fs_free_mem(&ddquot);
343 /* Free dquot entry in data block */
344 static void free_dqentry(struct quota_handle *h, struct dquot *dquot, uint blk)
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();
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 >>
359 read_blk(h, blk, buf);
360 dh = (struct qt_disk_dqdbheader *)buf;
362 ext2fs_cpu_to_le16(ext2fs_le16_to_cpu(dh->dqdh_entries) - 1);
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);
368 memset(buf + (dquot->dq_dqb.u.v2_mdqb.dqb_off &
369 ((1 << QT_BLKSIZE_BITS) - 1)),
370 0, info->dqi_entry_size);
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);
378 write_blk(h, blk, buf);
380 dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
384 /* Remove reference to dquot from tree */
385 static void remove_tree(struct quota_handle *h, struct dquot *dquot,
386 uint * blk, int depth)
388 dqbuf_t buf = getdqbuf();
390 u_int32_t *ref = (u_int32_t *) buf;
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);
401 remove_tree(h, dquot, &newblk, depth + 1);
407 ref[get_index(dquot->dq_id, depth)] = ext2fs_cpu_to_le32(0);
409 /* Block got empty? */
410 for (i = 0; i < QT_BLKSIZE && !buf[i]; i++);
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);
417 write_blk(h, *blk, buf);
423 /* Delete dquot from tree */
424 void qtree_delete_dquot(struct dquot *dquot)
426 uint tmp = QT_TREEOFF;
428 if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) /* Even not allocated? */
430 remove_tree(dquot->dq_h, dquot, &tmp, 0);
433 /* Find entry in block */
434 static ext2_loff_t find_block_dqentry(struct quota_handle *h,
435 struct dquot *dquot, uint blk)
437 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
438 dqbuf_t buf = getdqbuf();
440 char *ddquot = buf + sizeof(struct qt_disk_dqdbheader);
445 read_blk(h, blk, buf);
447 i < qtree_dqstr_in_blk(info) && !info->dqi_ops->is_id(ddquot, dquot);
449 ddquot += info->dqi_entry_size;
451 if (i == qtree_dqstr_in_blk(info))
452 log_err("Quota for id %u referenced but not present.",
455 return (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) +
456 i * info->dqi_entry_size;
459 /* Find entry for given id in the tree */
460 static ext2_loff_t find_tree_dqentry(struct quota_handle *h,
464 dqbuf_t buf = getdqbuf();
466 u_int32_t *ref = (u_int32_t *) buf;
471 read_blk(h, blk, buf);
473 blk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
474 if (!blk) /* No reference? */
476 if (depth < QT_TREEDEPTH - 1)
477 ret = find_tree_dqentry(h, dquot, blk, depth + 1);
479 ret = find_block_dqentry(h, dquot, blk);
485 /* Find entry for given id in the tree - wrapper function */
486 static inline ext2_loff_t find_dqentry(struct quota_handle *h,
489 return find_tree_dqentry(h, dquot, QT_TREEOFF, 0);
493 * Read dquot from disk.
495 struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id)
497 struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
501 struct dquot *dquot = get_empty_dquot();
505 if (ext2fs_get_mem(info->dqi_entry_size, &ddquot)) {
506 ext2fs_free_mem(&dquot);
512 dquot->dq_dqb.u.v2_mdqb.dqb_off = 0;
513 memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk));
515 offset = find_dqentry(h, dquot);
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) {
523 log_err("Cannot read quota structure for id %u: %s",
524 dquot->dq_id, strerror(errno));
526 info->dqi_ops->disk2mem_dqblk(dquot, ddquot);
528 ext2fs_free_mem(&ddquot);
533 * Scan all dquots in file and call callback on each
535 #define set_bit(bmp, ind) ((bmp)[(ind) >> 3] |= (1 << ((ind) & 7)))
536 #define get_bit(bmp, ind) ((bmp)[(ind) >> 3] & (1 << ((ind) & 7)))
538 static int report_block(struct dquot *dquot, uint blk, char *bitmap,
539 int (*process_dquot) (struct dquot *, void *),
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;
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)
572 static void check_reference(struct quota_handle *h, uint blk)
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.",
579 h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks,
580 type2name(h->qh_type));
583 static int report_tree(struct dquot *dquot, uint blk, int depth, char *bitmap,
584 int (*process_dquot) (struct dquot *, void *),
588 dqbuf_t buf = getdqbuf();
589 u_int32_t *ref = (u_int32_t *) buf;
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);
604 for (i = 0; i < QT_BLKSIZE >> 2; i++) {
605 blk = ext2fs_le32_to_cpu(ref[i]);
607 check_reference(dquot->dq_h, blk);
608 entries += report_tree(dquot, blk, depth + 1,
609 bitmap, process_dquot,
618 static uint find_set_bits(char *bmp, int blocks)
622 for (i = 0; i < blocks; i++)
628 int qtree_scan_dquots(struct quota_handle *h,
629 int (*process_dquot) (struct dquot *, void *),
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();
641 if (ext2fs_get_memzero((info->dqi_blocks + 7) >> 3, &bitmap)) {
642 ext2fs_free_mem(&dquot);
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);