summaryrefslogtreecommitdiff
path: root/quotaio_v2.c
diff options
context:
space:
mode:
Diffstat (limited to 'quotaio_v2.c')
-rw-r--r--quotaio_v2.c734
1 files changed, 734 insertions, 0 deletions
diff --git a/quotaio_v2.c b/quotaio_v2.c
new file mode 100644
index 0000000..f200b3a
--- /dev/null
+++ b/quotaio_v2.c
@@ -0,0 +1,734 @@
+/*
+ * Implementation of new quotafile format
+ */
+
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <asm/byteorder.h>
+
+#include "pot.h"
+#include "common.h"
+#include "quotaio_v2.h"
+#include "dqblk_v2.h"
+#include "quotaio.h"
+
+typedef char *dqbuf_t;
+
+static int v2_init_io(struct quota_handle *h);
+static int v2_new_io(struct quota_handle *h);
+static int v2_write_info(struct quota_handle *h);
+static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id);
+static int v2_commit_dquot(struct dquot *dquot);
+static int v2_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot * dquot));
+static int v2_report(struct quota_handle *h, int verbose);
+
+struct quotafile_ops quotafile_ops_2 = {
+ v2_init_io,
+ v2_new_io,
+ NULL, /* end_io */
+ v2_write_info,
+ v2_read_dquot,
+ v2_commit_dquot,
+ v2_scan_dquots,
+ v2_report
+};
+
+#define getdqbuf() smalloc(V2_DQBLKSIZE)
+#define freedqbuf(buf) free(buf)
+
+/*
+ * Copy dquot from disk to memory
+ */
+static inline void v2_disk2memdqblk(struct util_dqblk *m, struct v2_disk_dqblk *d)
+{
+ m->dqb_ihardlimit = __le32_to_cpu(d->dqb_ihardlimit);
+ m->dqb_isoftlimit = __le32_to_cpu(d->dqb_isoftlimit);
+ m->dqb_bhardlimit = __le32_to_cpu(d->dqb_bhardlimit);
+ m->dqb_bsoftlimit = __le32_to_cpu(d->dqb_bsoftlimit);
+ m->dqb_curinodes = __le32_to_cpu(d->dqb_curinodes);
+ m->dqb_curspace = __le64_to_cpu(d->dqb_curspace);
+ m->dqb_itime = __le64_to_cpu(d->dqb_itime);
+ m->dqb_btime = __le64_to_cpu(d->dqb_btime);
+}
+
+/*
+ * Copy dquot from memory to disk
+ */
+static inline void v2_mem2diskdqblk(struct v2_disk_dqblk *d, struct util_dqblk *m)
+{
+ d->dqb_ihardlimit = __cpu_to_le32(m->dqb_ihardlimit);
+ d->dqb_isoftlimit = __cpu_to_le32(m->dqb_isoftlimit);
+ d->dqb_bhardlimit = __cpu_to_le32(m->dqb_bhardlimit);
+ d->dqb_bsoftlimit = __cpu_to_le32(m->dqb_bsoftlimit);
+ d->dqb_curinodes = __cpu_to_le32(m->dqb_curinodes);
+ d->dqb_curspace = __cpu_to_le64(m->dqb_curspace);
+ d->dqb_itime = __cpu_to_le64(m->dqb_itime);
+ d->dqb_btime = __cpu_to_le64(m->dqb_btime);
+}
+
+/*
+ * Copy dqinfo from disk to memory
+ */
+static inline void v2_disk2memdqinfo(struct util_dqinfo *m, struct v2_disk_dqinfo *d)
+{
+ m->dqi_bgrace = __le32_to_cpu(d->dqi_bgrace);
+ m->dqi_igrace = __le32_to_cpu(d->dqi_igrace);
+ m->u.v2_mdqi.dqi_flags = __le32_to_cpu(d->dqi_flags) & V2_DQF_MASK;
+ m->u.v2_mdqi.dqi_blocks = __le32_to_cpu(d->dqi_blocks);
+ m->u.v2_mdqi.dqi_free_blk = __le32_to_cpu(d->dqi_free_blk);
+ m->u.v2_mdqi.dqi_free_entry = __le32_to_cpu(d->dqi_free_entry);
+}
+
+/*
+ * Copy dqinfo from memory to disk
+ */
+static inline void v2_mem2diskdqinfo(struct v2_disk_dqinfo *d, struct util_dqinfo *m)
+{
+ d->dqi_bgrace = __cpu_to_le32(m->dqi_bgrace);
+ d->dqi_igrace = __cpu_to_le32(m->dqi_igrace);
+ d->dqi_flags = __cpu_to_le32(m->u.v2_mdqi.dqi_flags & V2_DQF_MASK);
+ d->dqi_blocks = __cpu_to_le32(m->u.v2_mdqi.dqi_blocks);
+ d->dqi_free_blk = __cpu_to_le32(m->u.v2_mdqi.dqi_free_blk);
+ d->dqi_free_entry = __cpu_to_le32(m->u.v2_mdqi.dqi_free_entry);
+}
+
+/* Convert kernel quotablock format to utility one */
+static inline void v2_kern2utildqblk(struct util_dqblk *u, struct v2_kern_dqblk *k)
+{
+ u->dqb_ihardlimit = k->dqb_ihardlimit;
+ u->dqb_isoftlimit = k->dqb_isoftlimit;
+ u->dqb_bhardlimit = k->dqb_bhardlimit;
+ u->dqb_bsoftlimit = k->dqb_bsoftlimit;
+ u->dqb_curinodes = k->dqb_curinodes;
+ u->dqb_curspace = k->dqb_curspace;
+ u->dqb_itime = k->dqb_itime;
+ u->dqb_btime = k->dqb_btime;
+}
+
+/* Convert utility quotablock format to kernel one */
+static inline void v2_util2kerndqblk(struct v2_kern_dqblk *k, struct util_dqblk *u)
+{
+ k->dqb_ihardlimit = u->dqb_ihardlimit;
+ k->dqb_isoftlimit = u->dqb_isoftlimit;
+ k->dqb_bhardlimit = u->dqb_bhardlimit;
+ k->dqb_bsoftlimit = u->dqb_bsoftlimit;
+ k->dqb_curinodes = u->dqb_curinodes;
+ k->dqb_curspace = u->dqb_curspace;
+ k->dqb_itime = u->dqb_itime;
+ k->dqb_btime = u->dqb_btime;
+}
+
+/* Is given dquot empty? */
+static int empty_dquot(struct v2_disk_dqblk *d)
+{
+ static struct v2_disk_dqblk fakedquot;
+
+ return !memcmp(d, &fakedquot, sizeof(fakedquot));
+}
+
+/*
+ * Open quotafile
+ */
+static int v2_init_io(struct quota_handle *h)
+{
+ if (QIO_ENABLED(h)) {
+ struct v2_kern_dqinfo kdqinfo;
+
+ if (quotactl(QCMD(Q_V2_GETINFO, h->qh_type), h->qh_quotadev, 0, (void *)&kdqinfo) <
+ 0) return -1;
+ h->qh_info.dqi_bgrace = kdqinfo.dqi_bgrace;
+ h->qh_info.dqi_igrace = kdqinfo.dqi_igrace;
+ h->qh_info.u.v2_mdqi.dqi_flags = kdqinfo.dqi_flags;
+ h->qh_info.u.v2_mdqi.dqi_blocks = kdqinfo.dqi_blocks;
+ h->qh_info.u.v2_mdqi.dqi_free_blk = kdqinfo.dqi_free_blk;
+ h->qh_info.u.v2_mdqi.dqi_free_entry = kdqinfo.dqi_free_entry;
+ }
+ else {
+ struct v2_disk_dqinfo ddqinfo;
+
+ lseek(h->qh_fd, V2_DQINFOOFF, SEEK_SET);
+ if (read(h->qh_fd, &ddqinfo, sizeof(ddqinfo)) != sizeof(ddqinfo))
+ return -1;
+ v2_disk2memdqinfo(&h->qh_info, &ddqinfo);
+ }
+ return 0;
+}
+
+/*
+ * Initialize new quotafile
+ */
+static int v2_new_io(struct quota_handle *h)
+{
+ int file_magics[] = INITQMAGICS;
+ int known_versions[] = INIT_V2_VERSIONS;
+ struct v2_disk_dqheader ddqheader;
+ struct v2_disk_dqinfo ddqinfo;
+
+ /* Write basic quota header */
+ ddqheader.dqh_magic = __cpu_to_le32(file_magics[h->qh_type]);
+ ddqheader.dqh_version = __cpu_to_le32(known_versions[h->qh_type]);
+ lseek(h->qh_fd, 0, SEEK_SET);
+ if (write(h->qh_fd, &ddqheader, sizeof(ddqheader)) != sizeof(ddqheader))
+ return -1;
+ /* Write information about quotafile */
+ h->qh_info.dqi_bgrace = MAX_DQ_TIME;
+ h->qh_info.dqi_igrace = MAX_IQ_TIME;
+ h->qh_info.u.v2_mdqi.dqi_flags = 0;
+ h->qh_info.u.v2_mdqi.dqi_blocks = V2_DQTREEOFF + 1;
+ h->qh_info.u.v2_mdqi.dqi_free_blk = 0;
+ h->qh_info.u.v2_mdqi.dqi_free_entry = 0;
+ v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
+ lseek(h->qh_fd, V2_DQINFOOFF, SEEK_SET);
+ if (write(h->qh_fd, &ddqinfo, sizeof(ddqinfo)) != sizeof(ddqinfo))
+ return -1;
+ return 0;
+}
+
+/*
+ * Write information (grace times to file)
+ */
+static int v2_write_info(struct quota_handle *h)
+{
+ if (QIO_ENABLED(h)) {
+ struct v2_kern_dqinfo kdqinfo;
+
+ kdqinfo.dqi_bgrace = h->qh_info.dqi_bgrace;
+ kdqinfo.dqi_igrace = h->qh_info.dqi_igrace;
+ kdqinfo.dqi_flags = h->qh_info.u.v2_mdqi.dqi_flags;
+ kdqinfo.dqi_blocks = h->qh_info.u.v2_mdqi.dqi_blocks;
+ kdqinfo.dqi_free_blk = h->qh_info.u.v2_mdqi.dqi_free_blk;
+ kdqinfo.dqi_free_entry = h->qh_info.u.v2_mdqi.dqi_free_entry;
+ if (quotactl(QCMD(Q_V2_SETINFO, h->qh_type), h->qh_quotadev, 0, (void *)&kdqinfo) <
+ 0) return -1;
+ }
+ else {
+ struct v2_disk_dqinfo ddqinfo;
+
+ v2_mem2diskdqinfo(&ddqinfo, &h->qh_info);
+ lseek(h->qh_fd, V2_DQINFOOFF, SEEK_SET);
+ if (write(h->qh_fd, &ddqinfo, sizeof(ddqinfo)) != sizeof(ddqinfo))
+ return -1;
+ }
+ return 0;
+}
+
+/* Read given block */
+static void read_blk(struct quota_handle *h, uint blk, dqbuf_t buf)
+{
+ int err;
+
+ lseek(h->qh_fd, blk << V2_DQBLKSIZE_BITS, SEEK_SET);
+ err = read(h->qh_fd, buf, V2_DQBLKSIZE);
+ if (err < 0)
+ die(2, "Can't read block %u: %s\n", blk, strerror(errno));
+ else if (err != V2_DQBLKSIZE)
+ memset(buf + err, 0, V2_DQBLKSIZE - err);
+}
+
+/* Write block */
+static int write_blk(struct quota_handle *h, uint blk, dqbuf_t buf)
+{
+ int err;
+
+ lseek(h->qh_fd, blk << V2_DQBLKSIZE_BITS, SEEK_SET);
+ err = write(h->qh_fd, buf, V2_DQBLKSIZE);
+ if (err < 0 && errno != ENOSPC)
+ die(2, "Can't write block (%u): %s\n", blk, strerror(errno));
+ if (err != V2_DQBLKSIZE)
+ 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 v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf;
+ struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
+ int blk;
+
+ if (info->dqi_free_blk) {
+ blk = info->dqi_free_blk;
+ read_blk(h, blk, buf);
+ info->dqi_free_blk = __le32_to_cpu(dh->dqdh_next_free);
+ }
+ else {
+ memset(buf, 0, V2_DQBLKSIZE);
+ if (write_blk(h, info->dqi_blocks, buf) < 0) { /* Assure block allocation... */
+ freedqbuf(buf);
+ fprintf(stderr, "Can't 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 v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf;
+ struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
+
+ dh->dqdh_next_free = __cpu_to_le32(info->dqi_free_blk);
+ dh->dqdh_prev_free = __cpu_to_le32(0);
+ dh->dqdh_entries = __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, uint blk)
+{
+ dqbuf_t tmpbuf = getdqbuf();
+ struct v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf;
+ uint nextblk = __le32_to_cpu(dh->dqdh_next_free), prevblk =
+
+ __le32_to_cpu(dh->dqdh_prev_free);
+
+ if (nextblk) {
+ read_blk(h, nextblk, tmpbuf);
+ ((struct v2_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = dh->dqdh_prev_free;
+ write_blk(h, nextblk, tmpbuf);
+ }
+ if (prevblk) {
+ read_blk(h, prevblk, tmpbuf);
+ ((struct v2_disk_dqdbheader *)tmpbuf)->dqdh_next_free = dh->dqdh_next_free;
+ write_blk(h, prevblk, tmpbuf);
+ }
+ else {
+ h->qh_info.u.v2_mdqi.dqi_free_entry = nextblk;
+ mark_quotafile_info_dirty(h);
+ }
+ freedqbuf(tmpbuf);
+ dh->dqdh_next_free = dh->dqdh_prev_free = __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, uint blk)
+{
+ dqbuf_t tmpbuf = getdqbuf();
+ struct v2_disk_dqdbheader *dh = (struct v2_disk_dqdbheader *)buf;
+ struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
+
+ dh->dqdh_next_free = __cpu_to_le32(info->dqi_free_entry);
+ dh->dqdh_prev_free = __cpu_to_le32(0);
+ write_blk(h, blk, buf);
+ if (info->dqi_free_entry) {
+ read_blk(h, info->dqi_free_entry, tmpbuf);
+ ((struct v2_disk_dqdbheader *)tmpbuf)->dqdh_prev_free = __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 uint find_free_dqentry(struct quota_handle *h, struct dquot *dquot, int *err)
+{
+ int blk, i;
+ struct v2_disk_dqdbheader *dh;
+ struct v2_disk_dqblk *ddquot;
+ struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
+ dqbuf_t buf;
+
+ *err = 0;
+ buf = getdqbuf();
+ dh = (struct v2_disk_dqdbheader *)buf;
+ ddquot = V2_GETENTRIES(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, V2_DQBLKSIZE);
+ info->dqi_free_entry = blk;
+ mark_quotafile_info_dirty(h);
+ }
+ if (__le16_to_cpu(dh->dqdh_entries) + 1 >= V2_DQSTRINBLK) /* Block will be full? */
+ remove_free_dqentry(h, buf, blk);
+ dh->dqdh_entries = __cpu_to_le16(__le16_to_cpu(dh->dqdh_entries) + 1);
+ /* Find free structure in block */
+ for (i = 0; i < V2_DQSTRINBLK && !empty_dquot(ddquot + i); i++);
+ if (i == V2_DQSTRINBLK)
+ 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 << V2_DQBLKSIZE_BITS) + sizeof(struct v2_disk_dqdbheader) +
+
+ i * sizeof(struct v2_disk_dqblk);
+ 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, V2_DQBLKSIZE);
+ newact = 1;
+ }
+ else
+ read_blk(h, *treeblk, buf);
+ ref = (u_int32_t *) buf;
+ newblk = __le32_to_cpu(ref[V2_GETIDINDEX(dquot->dq_id, depth)]);
+ if (!newblk)
+ newson = 1;
+ if (depth == V2_DQTREEDEPTH - 1) {
+ if (newblk)
+ die(2, "Inserting already present quota entry (block %u).\n",
+ ref[V2_GETIDINDEX(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[V2_GETIDINDEX(dquot->dq_id, depth)] = __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 inline void dq_insert_tree(struct quota_handle *h, struct dquot *dquot)
+{
+ int tmp = V2_DQTREEOFF;
+
+ if (do_insert_tree(h, dquot, &tmp, 0) < 0)
+ die(2, "Can't write quota (id %u): %s\n", (uint) dquot->dq_id, strerror(errno));
+}
+
+/* Write dquot to file */
+static void v2_write_dquot(struct dquot *dquot)
+{
+ ssize_t ret;
+ struct v2_disk_dqblk ddquot;
+
+ 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);
+ v2_mem2diskdqblk(&ddquot, &dquot->dq_dqb);
+ ddquot.dqb_id = __cpu_to_le32(dquot->dq_id);
+ ret = write(dquot->dq_h->qh_fd, (char *)&ddquot, sizeof(struct v2_disk_dqblk));
+ if (ret != sizeof(struct v2_disk_dqblk)) {
+ 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 v2_disk_dqdbheader *dh;
+ dqbuf_t buf = getdqbuf();
+
+ if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> V2_DQBLKSIZE_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 >> V2_DQBLKSIZE_BITS));
+ read_blk(h, blk, buf);
+ dh = (struct v2_disk_dqdbheader *)buf;
+ dh->dqdh_entries = __cpu_to_le16(__le16_to_cpu(dh->dqdh_entries) - 1);
+ if (!__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 << V2_DQBLKSIZE_BITS) - 1)), 0,
+ sizeof(struct v2_disk_dqblk));
+
+ if (__le16_to_cpu(dh->dqdh_entries) == V2_DQSTRINBLK - 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 = __le32_to_cpu(ref[V2_GETIDINDEX(dquot->dq_id, depth)]);
+ if (depth == V2_DQTREEDEPTH - 1) {
+ free_dqentry(h, dquot, newblk);
+ newblk = 0;
+ }
+ else
+ remove_tree(h, dquot, &newblk, depth + 1);
+ if (!newblk) {
+ int i;
+
+ ref[V2_GETIDINDEX(dquot->dq_id, depth)] = __cpu_to_le32(0);
+ for (i = 0; i < V2_DQBLKSIZE && !buf[i]; i++); /* Block got empty? */
+ if (i == V2_DQBLKSIZE) {
+ put_free_dqblk(h, buf, *blk);
+ *blk = 0;
+ }
+ else
+ write_blk(h, *blk, buf);
+ }
+ freedqbuf(buf);
+}
+
+/* Delete dquot from tree */
+static void v2_delete_dquot(struct dquot *dquot)
+{
+ uint tmp = V2_DQTREEOFF;
+
+ 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)
+{
+ dqbuf_t buf = getdqbuf();
+ int i;
+ struct v2_disk_dqblk *ddquot = V2_GETENTRIES(buf);
+
+ read_blk(h, blk, buf);
+ if (dquot->dq_id)
+ for (i = 0; i < V2_DQSTRINBLK && __le32_to_cpu(ddquot[i].dqb_id) != dquot->dq_id;
+ i++);
+ else { /* ID 0 as a bit more complicated searching... */
+ for (i = 0; i < V2_DQSTRINBLK; i++)
+ if (!__le32_to_cpu(ddquot[i].dqb_id) && !empty_dquot(ddquot + i))
+ break;
+ }
+ if (i == V2_DQSTRINBLK)
+ die(2, "Quota for id %u referenced but not present.\n", dquot->dq_id);
+ freedqbuf(buf);
+ return (blk << V2_DQBLKSIZE_BITS) + sizeof(struct v2_disk_dqdbheader) +
+
+ i * sizeof(struct v2_disk_dqblk);
+}
+
+/* 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 = __le32_to_cpu(ref[V2_GETIDINDEX(dquot->dq_id, depth)]);
+ if (!blk) /* No reference? */
+ goto out_buf;
+ if (depth < V2_DQTREEDEPTH - 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, V2_DQTREEOFF, 0);
+}
+
+/*
+ * Read dquot (either from disk or from kernel)
+ * User can use errno to detect error when NULL is returned
+ */
+static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id)
+{
+ loff_t offset;
+ ssize_t ret;
+ struct v2_disk_dqblk ddquot;
+ 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));
+
+ if (QIO_ENABLED(h)) {
+ struct v2_kern_dqblk kdqblk;
+
+ if (quotactl(QCMD(Q_V2_GETQUOTA, h->qh_type), h->qh_quotadev, id, (void *)&kdqblk) <
+ 0) {
+ free(dquot);
+ return NULL;
+ }
+ v2_kern2utildqblk(&dquot->dq_dqb, &kdqblk);
+ return dquot;
+ }
+ 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, (char *)&ddquot, sizeof(struct v2_disk_dqblk));
+ if (ret != sizeof(struct v2_disk_dqblk)) {
+ if (ret > 0)
+ errno = EIO;
+ die(2, "Can't read quota structure for id %u: %s\n", dquot->dq_id,
+ strerror(errno));
+ }
+ v2_disk2memdqblk(&dquot->dq_dqb, &ddquot);
+ }
+ return dquot;
+}
+
+/*
+ * Commit changes of dquot to disk - it might also mean deleting it when quota became fake one and user has no blocks...
+ * User can process use 'errno' to detect error
+ */
+static int v2_commit_dquot(struct dquot *dquot)
+{
+ struct util_dqblk *b = &dquot->dq_dqb;
+
+ if (QIO_ENABLED(dquot->dq_h)) {
+ struct v2_kern_dqblk kdqblk;
+
+ v2_util2kerndqblk(&kdqblk, &dquot->dq_dqb);
+ if (quotactl
+ (QCMD(Q_V2_SETQUOTA, dquot->dq_h->qh_type), dquot->dq_h->qh_quotadev,
+ dquot->dq_id, (void *)&kdqblk) < 0)
+ return -1;
+ }
+ if (!b->dqb_curspace && !b->dqb_curinodes && !b->dqb_bsoftlimit && !b->dqb_isoftlimit
+ && !b->dqb_bhardlimit && !b->dqb_ihardlimit)
+ v2_delete_dquot(dquot);
+ else
+ v2_write_dquot(dquot);
+ return 0;
+}
+
+/*
+ * 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 *))
+{
+ dqbuf_t buf = getdqbuf();
+ struct v2_disk_dqdbheader *dh;
+ struct v2_disk_dqblk *ddata;
+ int entries, i;
+
+ set_bit(bitmap, blk);
+ read_blk(dquot->dq_h, blk, buf);
+ dh = (struct v2_disk_dqdbheader *)buf;
+ ddata = V2_GETENTRIES(buf);
+ entries = __le16_to_cpu(dh->dqdh_entries);
+ for (i = 0; i < V2_DQSTRINBLK; i++)
+ if (!empty_dquot(ddata + i)) {
+ v2_disk2memdqblk(&dquot->dq_dqb, ddata + i);
+ dquot->dq_id = __le32_to_cpu(ddata[i].dqb_id);
+ if (process_dquot(dquot) < 0)
+ break;
+ }
+ freedqbuf(buf);
+ return entries;
+}
+
+static int report_tree(struct dquot *dquot, uint blk, int depth, char *bitmap,
+ int (*process_dquot) (struct dquot *))
+{
+ 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 == V2_DQTREEDEPTH - 1) {
+ for (i = 0; i < V2_DQBLKSIZE >> 2; i++) {
+ blk = __le32_to_cpu(ref[i]);
+ if (blk && !get_bit(bitmap, blk))
+ entries += report_block(dquot, blk, bitmap, process_dquot);
+ }
+ }
+ else {
+ for (i = 0; i < V2_DQBLKSIZE >> 2; i++)
+ if ((blk = __le32_to_cpu(ref[i])))
+ 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;
+}
+
+static int v2_scan_dquots(struct quota_handle *h, int (*process_dquot) (struct dquot * dquot))
+{
+ char *bitmap;
+ struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
+ struct dquot *dquot = get_empty_dquot();
+
+ if (QIO_ENABLED(h)) /* Kernel uses same file? */
+ if (quotactl(QCMD(Q_SYNC, h->qh_type), h->qh_quotadev, 0, NULL) < 0)
+ die(4, _("Can't sync quotas on device %s: %s\n"), h->qh_quotadev,
+ strerror(errno));
+ dquot->dq_h = h;
+ bitmap = smalloc((info->dqi_blocks + 7) >> 3);
+ memset(bitmap, 0, (info->dqi_blocks + 7) >> 3);
+ info->dqi_used_entries = report_tree(dquot, V2_DQTREEOFF, 0, bitmap, process_dquot);
+ info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks);
+ free(bitmap);
+ free(dquot);
+ return 0;
+}
+
+/* Report information about quotafile */
+static int v2_report(struct quota_handle *h, int verbose)
+{
+ struct v2_mem_dqinfo *info = &h->qh_info.u.v2_mdqi;
+
+ if (verbose)
+ printf
+ ("Statistics:\nTotal blocks: %u\nData blocks: %u\nEntries: %u\nUsed average: %f\n",
+ info->dqi_blocks, info->dqi_data_blocks, info->dqi_used_entries,
+ ((float)info->dqi_used_entries) / info->dqi_data_blocks);
+ return 0;
+}