| /* |
| * 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 <endian.h> |
| |
| #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; |
| } |