/* * Implementation of new quotafile format * * Jan Kara <jack@suse.cz> - sponsored by SuSE CR */ #include "config.h" #include <sys/types.h> #include <errno.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include "common.h" #include "quotaio_tree.h" #include "quotaio.h" typedef char *dqbuf_t; #define freedqbuf(buf) ext2fs_free_mem(&buf) static inline dqbuf_t getdqbuf(void) { dqbuf_t buf; if (ext2fs_get_memzero(QT_BLKSIZE, &buf)) { log_err("Failed to allocate dqbuf"); return NULL; } return buf; } /* Is given dquot empty? */ int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk) { unsigned int i; for (i = 0; i < info->dqi_entry_size; i++) if (disk[i]) return 0; return 1; } int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info) { return (QT_BLKSIZE - sizeof(struct qt_disk_dqdbheader)) / info->dqi_entry_size; } static int get_index(qid_t id, int depth) { return (id >> ((QT_TREEDEPTH - depth - 1) * 8)) & 0xff; } static inline void mark_quotafile_info_dirty(struct quota_handle *h) { h->qh_io_flags |= IOFL_INFODIRTY; } /* Read given block */ static void read_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf) { int err; err = h->e2fs_read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf, QT_BLKSIZE); if (err < 0) log_err("Cannot read block %u: %s", blk, strerror(errno)); else if (err != QT_BLKSIZE) memset(buf + err, 0, QT_BLKSIZE - err); } /* Write block */ static int write_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf) { int err; err = h->e2fs_write(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf, QT_BLKSIZE); if (err < 0 && errno != ENOSPC) log_err("Cannot write block (%u): %s", blk, strerror(errno)); if (err != QT_BLKSIZE) return -ENOSPC; return 0; } /* Get free block in file (either from free list or create new one) */ static int get_free_dqblk(struct quota_handle *h) { dqbuf_t buf = getdqbuf(); struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; int blk; if (!buf) return -ENOMEM; if (info->dqi_free_blk) { blk = info->dqi_free_blk; read_blk(h, blk, buf); info->dqi_free_blk = ext2fs_le32_to_cpu(dh->dqdh_next_free); } else { memset(buf, 0, QT_BLKSIZE); /* Assure block allocation... */ if (write_blk(h, info->dqi_blocks, buf) < 0) { freedqbuf(buf); log_err("Cannot allocate new quota block " "(out of disk space)."); return -ENOSPC; } blk = info->dqi_blocks++; } mark_quotafile_info_dirty(h); freedqbuf(buf); return blk; } /* Put given block to free list */ static void put_free_dqblk(struct quota_handle *h, dqbuf_t buf, unsigned int blk) { struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; dh->dqdh_next_free = ext2fs_cpu_to_le32(info->dqi_free_blk); dh->dqdh_prev_free = ext2fs_cpu_to_le32(0); dh->dqdh_entries = ext2fs_cpu_to_le16(0); info->dqi_free_blk = blk; mark_quotafile_info_dirty(h); write_blk(h, blk, buf); } /* Remove given block from the list of blocks with free entries */ static void remove_free_dqentry(struct quota_handle *h, dqbuf_t buf, unsigned int blk) { dqbuf_t tmpbuf = getdqbuf(); struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; unsigned int nextblk = ext2fs_le32_to_cpu(dh->dqdh_next_free), prevblk = ext2fs_le32_to_cpu(dh->dqdh_prev_free); if (!tmpbuf) return; if (nextblk) { read_blk(h, nextblk, tmpbuf); ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = dh->dqdh_prev_free; write_blk(h, nextblk, tmpbuf); } if (prevblk) { read_blk(h, prevblk, tmpbuf); ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_next_free = dh->dqdh_next_free; write_blk(h, prevblk, tmpbuf); } else { h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = nextblk; mark_quotafile_info_dirty(h); } freedqbuf(tmpbuf); dh->dqdh_next_free = dh->dqdh_prev_free = ext2fs_cpu_to_le32(0); write_blk(h, blk, buf); /* No matter whether write succeeds * block is out of list */ } /* Insert given block to the beginning of list with free entries */ static void insert_free_dqentry(struct quota_handle *h, dqbuf_t buf, unsigned int blk) { dqbuf_t tmpbuf = getdqbuf(); struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; if (!tmpbuf) return; dh->dqdh_next_free = ext2fs_cpu_to_le32(info->dqi_free_entry); dh->dqdh_prev_free = ext2fs_cpu_to_le32(0); write_blk(h, blk, buf); if (info->dqi_free_entry) { read_blk(h, info->dqi_free_entry, tmpbuf); ((struct qt_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = ext2fs_cpu_to_le32(blk); write_blk(h, info->dqi_free_entry, tmpbuf); } freedqbuf(tmpbuf); info->dqi_free_entry = blk; mark_quotafile_info_dirty(h); } /* Find space for dquot */ static unsigned int find_free_dqentry(struct quota_handle *h, struct dquot *dquot, int *err) { int blk, i; struct qt_disk_dqdbheader *dh; struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; char *ddquot; dqbuf_t buf; *err = 0; buf = getdqbuf(); if (!buf) { *err = -ENOMEM; return 0; } dh = (struct qt_disk_dqdbheader *)buf; if (info->dqi_free_entry) { blk = info->dqi_free_entry; read_blk(h, blk, buf); } else { blk = get_free_dqblk(h); if (blk < 0) { freedqbuf(buf); *err = blk; return 0; } memset(buf, 0, QT_BLKSIZE); info->dqi_free_entry = blk; mark_quotafile_info_dirty(h); } /* Block will be full? */ if (ext2fs_le16_to_cpu(dh->dqdh_entries) + 1 >= qtree_dqstr_in_blk(info)) remove_free_dqentry(h, buf, blk); dh->dqdh_entries = ext2fs_cpu_to_le16(ext2fs_le16_to_cpu(dh->dqdh_entries) + 1); /* Find free structure in block */ ddquot = buf + sizeof(struct qt_disk_dqdbheader); for (i = 0; i < qtree_dqstr_in_blk(info) && !qtree_entry_unused(info, ddquot); i++) ddquot += info->dqi_entry_size; if (i == qtree_dqstr_in_blk(info)) log_err("find_free_dqentry(): Data block full unexpectedly."); write_blk(h, blk, buf); dquot->dq_dqb.u.v2_mdqb.dqb_off = (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) + i * info->dqi_entry_size; freedqbuf(buf); return blk; } /* Insert reference to structure into the trie */ static int do_insert_tree(struct quota_handle *h, struct dquot *dquot, unsigned int * treeblk, int depth) { dqbuf_t buf; int newson = 0, newact = 0; __le32 *ref; unsigned int newblk; int ret = 0; log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth); buf = getdqbuf(); if (!buf) return -ENOMEM; if (!*treeblk) { ret = get_free_dqblk(h); if (ret < 0) goto out_buf; *treeblk = ret; memset(buf, 0, QT_BLKSIZE); newact = 1; } else { read_blk(h, *treeblk, buf); } ref = (__le32 *) buf; newblk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); if (!newblk) newson = 1; if (depth == QT_TREEDEPTH - 1) { if (newblk) log_err("Inserting already present quota entry " "(block %u).", ref[get_index(dquot->dq_id, depth)]); newblk = find_free_dqentry(h, dquot, &ret); } else { ret = do_insert_tree(h, dquot, &newblk, depth + 1); } if (newson && ret >= 0) { ref[get_index(dquot->dq_id, depth)] = ext2fs_cpu_to_le32(newblk); write_blk(h, *treeblk, buf); } else if (newact && ret < 0) { put_free_dqblk(h, buf, *treeblk); } out_buf: freedqbuf(buf); return ret; } /* Wrapper for inserting quota structure into tree */ static void dq_insert_tree(struct quota_handle *h, struct dquot *dquot) { unsigned int tmp = QT_TREEOFF; if (do_insert_tree(h, dquot, &tmp, 0) < 0) log_err("Cannot write quota (id %u): %s", (unsigned int) dquot->dq_id, strerror(errno)); } /* Write dquot to file */ void qtree_write_dquot(struct dquot *dquot) { errcode_t retval; unsigned int ret; char *ddquot; struct quota_handle *h = dquot->dq_h; struct qtree_mem_dqinfo *info = &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree; log_debug("writing ddquot 1: off=%llu, info->dqi_entry_size=%u", dquot->dq_dqb.u.v2_mdqb.dqb_off, info->dqi_entry_size); retval = ext2fs_get_mem(info->dqi_entry_size, &ddquot); if (retval) { errno = ENOMEM; log_err("Quota write failed (id %u): %s", (unsigned int)dquot->dq_id, strerror(errno)); return; } memset(ddquot, 0, info->dqi_entry_size); if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) dq_insert_tree(dquot->dq_h, dquot); info->dqi_ops->mem2disk_dqblk(ddquot, dquot); log_debug("writing ddquot 2: off=%llu, info->dqi_entry_size=%u", dquot->dq_dqb.u.v2_mdqb.dqb_off, info->dqi_entry_size); ret = h->e2fs_write(&h->qh_qf, dquot->dq_dqb.u.v2_mdqb.dqb_off, ddquot, info->dqi_entry_size); if (ret != info->dqi_entry_size) { if (ret > 0) errno = ENOSPC; log_err("Quota write failed (id %u): %s", (unsigned int)dquot->dq_id, strerror(errno)); } ext2fs_free_mem(&ddquot); } /* Free dquot entry in data block */ static void free_dqentry(struct quota_handle *h, struct dquot *dquot, unsigned int blk) { struct qt_disk_dqdbheader *dh; struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; dqbuf_t buf = getdqbuf(); if (!buf) return; if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS != blk) log_err("Quota structure has offset to other block (%u) " "than it should (%u).", blk, (unsigned int) (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS)); read_blk(h, blk, buf); dh = (struct qt_disk_dqdbheader *)buf; dh->dqdh_entries = ext2fs_cpu_to_le16(ext2fs_le16_to_cpu(dh->dqdh_entries) - 1); if (!ext2fs_le16_to_cpu(dh->dqdh_entries)) { /* Block got free? */ remove_free_dqentry(h, buf, blk); put_free_dqblk(h, buf, blk); } else { memset(buf + (dquot->dq_dqb.u.v2_mdqb.dqb_off & ((1 << QT_BLKSIZE_BITS) - 1)), 0, info->dqi_entry_size); /* First free entry? */ if (ext2fs_le16_to_cpu(dh->dqdh_entries) == qtree_dqstr_in_blk(info) - 1) /* This will also write data block */ insert_free_dqentry(h, buf, blk); else write_blk(h, blk, buf); } dquot->dq_dqb.u.v2_mdqb.dqb_off = 0; freedqbuf(buf); } /* Remove reference to dquot from tree */ static void remove_tree(struct quota_handle *h, struct dquot *dquot, unsigned int * blk, int depth) { dqbuf_t buf = getdqbuf(); unsigned int newblk; __le32 *ref = (__le32 *) buf; if (!buf) return; read_blk(h, *blk, buf); newblk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); if (depth == QT_TREEDEPTH - 1) { free_dqentry(h, dquot, newblk); newblk = 0; } else { remove_tree(h, dquot, &newblk, depth + 1); } if (!newblk) { int i; ref[get_index(dquot->dq_id, depth)] = ext2fs_cpu_to_le32(0); /* Block got empty? */ for (i = 0; i < QT_BLKSIZE && !buf[i]; i++); /* Don't put the root block into the free block list */ if (i == QT_BLKSIZE && *blk != QT_TREEOFF) { put_free_dqblk(h, buf, *blk); *blk = 0; } else { write_blk(h, *blk, buf); } } freedqbuf(buf); } /* Delete dquot from tree */ void qtree_delete_dquot(struct dquot *dquot) { unsigned int tmp = QT_TREEOFF; if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) /* Even not allocated? */ return; remove_tree(dquot->dq_h, dquot, &tmp, 0); } /* Find entry in block */ static ext2_loff_t find_block_dqentry(struct quota_handle *h, struct dquot *dquot, unsigned int blk) { struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; dqbuf_t buf = getdqbuf(); int i; char *ddquot = buf + sizeof(struct qt_disk_dqdbheader); if (!buf) return -ENOMEM; read_blk(h, blk, buf); for (i = 0; i < qtree_dqstr_in_blk(info) && !info->dqi_ops->is_id(ddquot, dquot); i++) ddquot += info->dqi_entry_size; if (i == qtree_dqstr_in_blk(info)) log_err("Quota for id %u referenced but not present.", dquot->dq_id); freedqbuf(buf); return (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) + i * info->dqi_entry_size; } /* Find entry for given id in the tree */ static ext2_loff_t find_tree_dqentry(struct quota_handle *h, struct dquot *dquot, unsigned int blk, int depth) { dqbuf_t buf = getdqbuf(); ext2_loff_t ret = 0; __le32 *ref = (__le32 *) buf; if (!buf) return -ENOMEM; read_blk(h, blk, buf); ret = 0; blk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); if (!blk) /* No reference? */ goto out_buf; if (depth < QT_TREEDEPTH - 1) ret = find_tree_dqentry(h, dquot, blk, depth + 1); else ret = find_block_dqentry(h, dquot, blk); out_buf: freedqbuf(buf); return ret; } /* Find entry for given id in the tree - wrapper function */ static inline ext2_loff_t find_dqentry(struct quota_handle *h, struct dquot *dquot) { return find_tree_dqentry(h, dquot, QT_TREEOFF, 0); } /* * Read dquot from disk. */ struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id) { struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; ext2_loff_t offset; unsigned int ret; char *ddquot; struct dquot *dquot = get_empty_dquot(); if (!dquot) return NULL; if (ext2fs_get_mem(info->dqi_entry_size, &ddquot)) { ext2fs_free_mem(&dquot); return NULL; } dquot->dq_id = id; dquot->dq_h = h; dquot->dq_dqb.u.v2_mdqb.dqb_off = 0; memset(&dquot->dq_dqb, 0, sizeof(struct util_dqblk)); offset = find_dqentry(h, dquot); if (offset > 0) { dquot->dq_dqb.u.v2_mdqb.dqb_off = offset; ret = h->e2fs_read(&h->qh_qf, offset, ddquot, info->dqi_entry_size); if (ret != info->dqi_entry_size) { if (ret > 0) errno = EIO; log_err("Cannot read quota structure for id %u: %s", dquot->dq_id, strerror(errno)); } info->dqi_ops->disk2mem_dqblk(dquot, ddquot); } ext2fs_free_mem(&ddquot); return dquot; } /* * Scan all dquots in file and call callback on each */ #define set_bit(bmp, ind) ((bmp)[(ind) >> 3] |= (1 << ((ind) & 7))) #define get_bit(bmp, ind) ((bmp)[(ind) >> 3] & (1 << ((ind) & 7))) static int report_block(struct dquot *dquot, unsigned int blk, char *bitmap, int (*process_dquot) (struct dquot *, void *), void *data) { struct qtree_mem_dqinfo *info = &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree; dqbuf_t buf = getdqbuf(); struct qt_disk_dqdbheader *dh; char *ddata; int entries, i; if (!buf) return 0; set_bit(bitmap, blk); read_blk(dquot->dq_h, blk, buf); dh = (struct qt_disk_dqdbheader *)buf; ddata = buf + sizeof(struct qt_disk_dqdbheader); entries = ext2fs_le16_to_cpu(dh->dqdh_entries); for (i = 0; i < qtree_dqstr_in_blk(info); i++, ddata += info->dqi_entry_size) if (!qtree_entry_unused(info, ddata)) { dquot->dq_dqb.u.v2_mdqb.dqb_off = (blk << QT_BLKSIZE_BITS) + sizeof(struct qt_disk_dqdbheader) + i * info->dqi_entry_size; info->dqi_ops->disk2mem_dqblk(dquot, ddata); if (process_dquot(dquot, data) < 0) break; } freedqbuf(buf); return entries; } static void check_reference(struct quota_handle *h, unsigned int blk) { if (blk >= h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks) log_err("Illegal reference (%u >= %u) in %s quota file. " "Quota file is probably corrupted.\n" "Please run e2fsck (8) to fix it.", blk, h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks, quota_type2name(h->qh_type)); } static int report_tree(struct dquot *dquot, unsigned int blk, int depth, char *bitmap, int (*process_dquot) (struct dquot *, void *), void *data) { int entries = 0, i; dqbuf_t buf = getdqbuf(); __le32 *ref = (__le32 *) buf; if (!buf) return 0; read_blk(dquot->dq_h, blk, buf); if (depth == QT_TREEDEPTH - 1) { for (i = 0; i < QT_BLKSIZE >> 2; i++) { blk = ext2fs_le32_to_cpu(ref[i]); check_reference(dquot->dq_h, blk); if (blk && !get_bit(bitmap, blk)) entries += report_block(dquot, blk, bitmap, process_dquot, data); } } else { for (i = 0; i < QT_BLKSIZE >> 2; i++) { blk = ext2fs_le32_to_cpu(ref[i]); if (blk) { check_reference(dquot->dq_h, blk); entries += report_tree(dquot, blk, depth + 1, bitmap, process_dquot, data); } } } freedqbuf(buf); return entries; } static unsigned int find_set_bits(char *bmp, int blocks) { unsigned int used = 0; int i; for (i = 0; i < blocks; i++) if (get_bit(bmp, i)) used++; return used; } int qtree_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot *, void *), void *data) { char *bitmap; struct v2_mem_dqinfo *v2info = &h->qh_info.u.v2_mdqi; struct qtree_mem_dqinfo *info = &v2info->dqi_qtree; struct dquot *dquot = get_empty_dquot(); if (!dquot) return -1; dquot->dq_h = h; if (ext2fs_get_memzero((info->dqi_blocks + 7) >> 3, &bitmap)) { ext2fs_free_mem(&dquot); return -1; } v2info->dqi_used_entries = report_tree(dquot, QT_TREEOFF, 0, bitmap, process_dquot, data); v2info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks); ext2fs_free_mem(&bitmap); ext2fs_free_mem(&dquot); return 0; }