/* * Implementation of new quotafile format * * Jan Kara - sponsored by SuSE CR */ #include "config.h" #include #include #include #include #include #include #include #include "pot.h" #include "common.h" #include "quota_tree.h" #include "quotaio.h" #include "quotasys.h" #include "quotaio_generic.h" typedef char *dqbuf_t; #define getdqbuf() smalloc(QT_BLKSIZE) #define freedqbuf(buf) free(buf) /* Is given dquot empty? */ int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk) { 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; } /* Read given block */ static void read_blk(struct quota_handle *h, uint blk, dqbuf_t buf) { int err; lseek(h->qh_fd, blk << QT_BLKSIZE_BITS, SEEK_SET); err = read(h->qh_fd, buf, QT_BLKSIZE); if (err < 0) die(2, _("Cannot read block %u: %s\n"), 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, uint blk, dqbuf_t buf) { int err; lseek(h->qh_fd, blk << QT_BLKSIZE_BITS, SEEK_SET); err = write(h->qh_fd, buf, QT_BLKSIZE); if (err < 0 && errno != ENOSPC) die(2, _("Cannot write block (%u): %s\n"), 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 (info->dqi_free_blk) { blk = info->dqi_free_blk; read_blk(h, blk, buf); info->dqi_free_blk = le32toh(dh->dqdh_next_free); } else { memset(buf, 0, QT_BLKSIZE); if (write_blk(h, info->dqi_blocks, buf) < 0) { /* Assure block allocation... */ freedqbuf(buf); errstr(_("Cannot allocate new quota block (out of disk space).\n")); 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, uint 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 = htole32(info->dqi_free_blk); dh->dqdh_prev_free = htole32(0); dh->dqdh_entries = htole16(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, uint blk) { dqbuf_t tmpbuf = getdqbuf(); struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf; uint nextblk = le32toh(dh->dqdh_next_free), prevblk = le32toh(dh->dqdh_prev_free); 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 = htole32(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, uint 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; dh->dqdh_next_free = htole32(info->dqi_free_entry); dh->dqdh_prev_free = htole32(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 = htole32(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 uint 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(); 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); } if (le16toh(dh->dqdh_entries) + 1 >= qtree_dqstr_in_blk(info)) /* Block will be full? */ remove_free_dqentry(h, buf, blk); dh->dqdh_entries = htole16(le16toh(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)) die(2, _("find_free_dqentry(): Data block full but it shouldn't.\n")); 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, uint * treeblk, int depth) { dqbuf_t buf; int newson = 0, newact = 0; u_int32_t *ref; uint newblk; int ret = 0; buf = getdqbuf(); 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 = (u_int32_t *) buf; newblk = le32toh(ref[get_index(dquot->dq_id, depth)]); if (!newblk) newson = 1; if (depth == QT_TREEDEPTH - 1) { if (newblk) die(2, _("Inserting already present quota entry (block %u).\n"), 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)] = htole32(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) { uint tmp = QT_TREEOFF; if (do_insert_tree(h, dquot, &tmp, 0) < 0) die(2, _("Cannot write quota (id %u): %s\n"), (uint) dquot->dq_id, strerror(errno)); } /* Write dquot to file */ void qtree_write_dquot(struct dquot *dquot) { ssize_t ret; struct qtree_mem_dqinfo *info = &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree; char *ddquot = smalloc(info->dqi_entry_size); if (!dquot->dq_dqb.u.v2_mdqb.dqb_off) dq_insert_tree(dquot->dq_h, dquot); lseek(dquot->dq_h->qh_fd, dquot->dq_dqb.u.v2_mdqb.dqb_off, SEEK_SET); info->dqi_ops->mem2disk_dqblk(ddquot, dquot); ret = write(dquot->dq_h->qh_fd, ddquot, info->dqi_entry_size); free(ddquot); if (ret != info->dqi_entry_size) { if (ret > 0) errno = ENOSPC; die(2, _("Quota write failed (id %u): %s\n"), (uint)dquot->dq_id, strerror(errno)); } } /* Free dquot entry in data block */ static void free_dqentry(struct quota_handle *h, struct dquot *dquot, uint blk) { struct qt_disk_dqdbheader *dh; struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree; dqbuf_t buf = getdqbuf(); if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS != blk) die(2, _("Quota structure has offset to other block (%u) than it should (%u).\n"), blk, (uint) (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 = htole16(le16toh(dh->dqdh_entries) - 1); if (!le16toh(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); if (le16toh(dh->dqdh_entries) == qtree_dqstr_in_blk(info) - 1) /* First free entry? */ insert_free_dqentry(h, buf, blk); /* This will also write data block */ 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, uint * blk, int depth) { dqbuf_t buf = getdqbuf(); uint newblk; u_int32_t *ref = (u_int32_t *) buf; read_blk(h, *blk, buf); newblk = le32toh(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)] = htole32(0); for (i = 0; i < QT_BLKSIZE && !buf[i]; i++); /* Block got empty? */ /* 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) { uint 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 loff_t find_block_dqentry(struct quota_handle *h, struct dquot *dquot, uint 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); 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)) die(2, _("Quota for id %u referenced but not present.\n"), 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 loff_t find_tree_dqentry(struct quota_handle *h, struct dquot *dquot, uint blk, int depth) { dqbuf_t buf = getdqbuf(); loff_t ret = 0; u_int32_t *ref = (u_int32_t *) buf; read_blk(h, blk, buf); ret = 0; blk = le32toh(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 loff_t find_dqentry(struct quota_handle *h, struct dquot *dquot) { return find_tree_dqentry(h, dquot, QT_TREEOFF, 0); } /* * Read dquot (either from disk or from kernel) * User can use errno to detect errstr when NULL is returned */ 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; loff_t offset; ssize_t ret; char *ddquot = smalloc(info->dqi_entry_size); struct dquot *dquot = get_empty_dquot(); 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; lseek(h->qh_fd, offset, SEEK_SET); ret = read(h->qh_fd, ddquot, info->dqi_entry_size); if (ret != info->dqi_entry_size) { if (ret > 0) errno = EIO; free(ddquot); die(2, _("Cannot read quota structure for id %u: %s\n"), dquot->dq_id, strerror(errno)); } info->dqi_ops->disk2mem_dqblk(dquot, ddquot); } free(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, uint blk, char *bitmap, int (*process_dquot) (struct dquot *, char *)) { 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; 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 = le16toh(dh->dqdh_entries); for (i = 0; i < qtree_dqstr_in_blk(info); i++, ddata += info->dqi_entry_size) if (!qtree_entry_unused(info, ddata)) { info->dqi_ops->disk2mem_dqblk(dquot, ddata); if (process_dquot(dquot, NULL) < 0) break; } freedqbuf(buf); return entries; } static void check_reference(struct quota_handle *h, uint blk) { if (blk >= h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks) die(2, _("Illegal reference (%u >= %u) in %s quota file on %s. Quota file is probably corrupted.\nPlease run quotacheck(8) and try again.\n"), blk, h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks, type2name(h->qh_type), h->qh_quotadev); } static int report_tree(struct dquot *dquot, uint blk, int depth, char *bitmap, int (*process_dquot) (struct dquot *, char *)) { int entries = 0, i; dqbuf_t buf = getdqbuf(); u_int32_t *ref = (u_int32_t *) buf; read_blk(dquot->dq_h, blk, buf); if (depth == QT_TREEDEPTH - 1) { for (i = 0; i < QT_BLKSIZE >> 2; i++) { blk = le32toh(ref[i]); check_reference(dquot->dq_h, blk); if (blk && !get_bit(bitmap, blk)) entries += report_block(dquot, blk, bitmap, process_dquot); } } else { for (i = 0; i < QT_BLKSIZE >> 2; i++) if ((blk = le32toh(ref[i]))) { check_reference(dquot->dq_h, blk); entries += report_tree(dquot, blk, depth + 1, bitmap, process_dquot); } } freedqbuf(buf); return entries; } static uint find_set_bits(char *bmp, int blocks) { uint i, used = 0; 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 *, char *)) { 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(); dquot->dq_h = h; bitmap = smalloc((info->dqi_blocks + 7) >> 3); memset(bitmap, 0, (info->dqi_blocks + 7) >> 3); v2info->dqi_used_entries = report_tree(dquot, QT_TREEOFF, 0, bitmap, process_dquot); v2info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks); free(bitmap); free(dquot); return 0; }