summaryrefslogtreecommitdiff
path: root/quotacheck_v2.c
diff options
context:
space:
mode:
Diffstat (limited to 'quotacheck_v2.c')
-rw-r--r--quotacheck_v2.c344
1 files changed, 344 insertions, 0 deletions
diff --git a/quotacheck_v2.c b/quotacheck_v2.c
new file mode 100644
index 0000000..31a558f
--- /dev/null
+++ b/quotacheck_v2.c
@@ -0,0 +1,344 @@
+/*
+ *
+ * Checking routines for new VFS quota format
+ *
+ */
+
+#include <stdio.h>
+#include <unistd.h>
+#include <string.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <asm/byteorder.h>
+
+#include "pot.h"
+#include "common.h"
+#include "quota.h"
+#include "quotaio.h"
+#include "quotaio_v2.h"
+#include "quotacheck.h"
+
+#define getdqbuf() smalloc(V2_DQBLKSIZE)
+#define freedqbuf(buf) free(buf)
+
+#define SET_BLK(blk) (blkbmp[(blk) >> 3] |= 1 << ((blk) & 7))
+#define GET_BLK(blk) (blkbmp[(blk) >> 3] & (1 << ((blk) & 7)))
+
+typedef char *dqbuf_t;
+
+static const int magics[MAXQUOTAS] = INITQMAGICS; /* Magics we should look for */
+static const int known_versions[MAXQUOTAS] = INITKNOWNVERSIONS; /* Versions we accept */
+static char *blkbmp; /* Bitmap of checked blocks */
+
+static int check_blkref(uint blk, uint blocks)
+{
+ if (blk >= blocks)
+ return -1;
+ if (blk && blk < V2_DQTREEOFF)
+ return -1;
+ return 0;
+}
+
+/* Load and check basic info about quotas */
+static int check_info(char *filename, int fd, int type)
+{
+ struct v2_disk_dqinfo dinfo;
+ uint blocks, dflags, freeblk, freeent;
+ off_t filesize;
+ int err;
+
+ debug(FL_VERBOSE, _("Checking quotafile info...\n"));
+ lseek(fd, V2_DQINFOOFF, SEEK_SET);
+ err = read(fd, &dinfo, sizeof(struct v2_disk_dqinfo));
+
+ if (err < 0) {
+ fprintf(stderr, _("Can't read info from quota file %s: %s\n"), filename, strerror(errno));
+ return -1;
+ }
+ if (err != sizeof(struct v2_disk_dqinfo)) {
+ fprintf(stderr, _("WARNING: Quota file %s was probably truncated. Can't save quota settings...\n"),
+ filename);
+ return -1;
+ }
+
+ blocks = __le32_to_cpu(dinfo.dqi_blocks);
+ freeblk = __le32_to_cpu(dinfo.dqi_free_blk);
+ freeent = __le32_to_cpu(dinfo.dqi_free_entry);
+ dflags = __le32_to_cpu(dinfo.dqi_flags);
+ filesize = lseek(fd, 0, SEEK_END);
+ if (check_blkref(freeblk, blocks) < 0 || dflags & ~V2_DQF_MASK ||
+ check_blkref(freeent, blocks) < 0 || (filesize + V2_DQBLKSIZE - 1) >> V2_DQBLKSIZE_BITS != blocks) {
+ fprintf(stderr, _("WARNING: Quota file info was corrupted.\n"));
+ debug(FL_DEBUG, _("Size of file: %lu\nBlocks: %u Free block: %u Block with free entry: %u Flags: %x\n"),
+ (unsigned long)filesize, blocks, freeblk, freeent, dflags);
+ old_info[type].dqi_bgrace = MAX_DQ_TIME;
+ old_info[type].dqi_igrace = MAX_IQ_TIME;
+ old_info[type].u.v2_mdqi.dqi_blocks =
+ (filesize + V2_DQBLKSIZE - 1) >> V2_DQBLKSIZE_BITS;
+ old_info[type].u.v2_mdqi.dqi_flags = 0;
+ printf(_("Setting grace times and other flags to default values.\nAssuming number of blocks is %u.\n"),
+ old_info[type].u.v2_mdqi.dqi_blocks);
+ }
+ else {
+ old_info[type].dqi_bgrace = __le32_to_cpu(dinfo.dqi_bgrace);
+ old_info[type].dqi_igrace = __le32_to_cpu(dinfo.dqi_igrace);
+ old_info[type].u.v2_mdqi.dqi_blocks = blocks;
+ old_info[type].u.v2_mdqi.dqi_flags = dflags;
+ }
+ old_info[type].u.v2_mdqi.dqi_free_blk = old_info[type].u.v2_mdqi.dqi_free_entry = 0; /* This won't be needed */
+ debug(FL_DEBUG, _("File info done.\n"));
+ return 0;
+}
+
+/* Print error message */
+static void blk_corrupted(int *corrupted, uint * lblk, uint blk, char *fmtstr, ...)
+{
+ va_list args;
+
+ if (!*corrupted) {
+ if (!(flags & (FL_VERBOSE | FL_DEBUG)))
+ fprintf(stderr, _("Corrupted blocks: "));
+ }
+ if (flags & (FL_VERBOSE | FL_DEBUG)) {
+ va_start(args, fmtstr);
+ fprintf(stderr, _("Block %u: "), blk);
+ vfprintf(stderr, fmtstr, args);
+ fputc('\n', stderr);
+ va_end(args);
+ }
+ else if (*lblk != blk) {
+ if (!*corrupted)
+ fprintf(stderr, "%u", blk);
+ else
+ fprintf(stderr, ", %u", blk);
+ }
+ *corrupted = 1;
+ *lblk = blk;
+ fflush(stderr);
+}
+
+/* Convert dist quota format to utility one - copy just needed fields */
+static inline void disk2utildqblk(struct util_dqblk *u, struct v2_disk_dqblk *d)
+{
+ u->dqb_ihardlimit = __le32_to_cpu(d->dqb_ihardlimit);
+ u->dqb_isoftlimit = __le32_to_cpu(d->dqb_isoftlimit);
+ u->dqb_bhardlimit = __le32_to_cpu(d->dqb_bhardlimit);
+ u->dqb_bsoftlimit = __le32_to_cpu(d->dqb_bsoftlimit);
+ u->dqb_itime = __le64_to_cpu(d->dqb_itime);
+ u->dqb_btime = __le64_to_cpu(d->dqb_btime);
+}
+
+/* Check whether given dquot is empty */
+static int empty_dquot(struct v2_disk_dqblk *d)
+{
+ static struct v2_disk_dqblk fakedq;
+
+ return !memcmp(&fakedq, d, sizeof(fakedq));
+}
+
+/* Put one entry info memory */
+static int buffer_entry(dqbuf_t buf, uint blk, int *corrupted, uint * lblk, int cnt, int type)
+{
+ struct util_dqblk mdq, *fdq;
+ qid_t id;
+ struct dquot *cd;
+
+ disk2utildqblk(&mdq, ((struct v2_disk_dqblk *)(((char *)buf) + sizeof(struct v2_disk_dqdbheader))) + cnt);
+ id = __le32_to_cpu(((struct v2_disk_dqblk *)(((char *)buf) + sizeof(struct v2_disk_dqdbheader)))[cnt].dqb_id);
+ cd = lookup_dquot(id, type);
+ if (cd != NODQUOT) {
+ fdq = &cd->dq_dqb;
+ if (mdq.dqb_bhardlimit != fdq->dqb_bhardlimit
+ || mdq.dqb_bsoftlimit != fdq->dqb_bsoftlimit
+ || mdq.dqb_ihardlimit != fdq->dqb_ihardlimit
+ || mdq.dqb_isoftlimit != fdq->dqb_isoftlimit) {
+ blk_corrupted(corrupted, lblk, blk, _("Duplicated entries."));
+ if (flags & FL_GUESSDQ) {
+ if (!(flags & (FL_DEBUG | FL_VERBOSE)))
+ fputc('\n', stderr);
+ fprintf(stderr, _("Found more structures for ID %u. Using values: BHARD: %Ld BSOFT: %Ld IHARD: %Ld ISOFT: %Ld\n"),
+ (uint) id, (long long)fdq->dqb_bhardlimit, (long long)fdq->dqb_bsoftlimit,
+ (long long)fdq->dqb_ihardlimit, (long long)fdq->dqb_isoftlimit);
+ return 0;
+ }
+ else if (flags & FL_INTERACTIVE) {
+ fprintf(stderr, _("\nFound more structures for ID %u. Values: BHARD: %Ld/%Ld BSOFT: %Ld/%Ld IHARD: %Ld/%Ld ISOFT: %Ld/%Ld\n"),
+ (uint) id, (long long)fdq->dqb_bhardlimit, (long long)mdq.dqb_bhardlimit,
+ (long long)fdq->dqb_bsoftlimit, (long long)mdq.dqb_bsoftlimit,
+ (long long)fdq->dqb_ihardlimit, (long long)mdq.dqb_ihardlimit,
+ (long long)fdq->dqb_isoftlimit, (long long)mdq.dqb_isoftlimit);
+ if (ask_yn(_("Should I use new values"), 0)) {
+ fdq->dqb_bhardlimit = mdq.dqb_bhardlimit;
+ fdq->dqb_bsoftlimit = mdq.dqb_bsoftlimit;
+ fdq->dqb_ihardlimit = mdq.dqb_ihardlimit;
+ fdq->dqb_isoftlimit = mdq.dqb_isoftlimit;
+ fdq->dqb_btime = mdq.dqb_btime;
+ fdq->dqb_itime = mdq.dqb_itime;
+ }
+ }
+ else {
+ fprintf(stderr, _("ID %u has more structures. User intervention needed (use -i for interactive mode or -n for automatic answer).\n"),
+ (uint) id);
+ return -1;
+ }
+ }
+ else if (mdq.dqb_itime != fdq->dqb_itime || mdq.dqb_btime != fdq->dqb_btime) {
+ if (fdq->dqb_btime < mdq.dqb_btime)
+ fdq->dqb_btime = mdq.dqb_btime;
+ if (fdq->dqb_itime < mdq.dqb_itime)
+ fdq->dqb_itime = mdq.dqb_itime;
+ }
+ }
+ else {
+ cd = add_dquot(id, type);
+ fdq = &cd->dq_dqb;
+ fdq->dqb_bhardlimit = mdq.dqb_bhardlimit;
+ fdq->dqb_bsoftlimit = mdq.dqb_bsoftlimit;
+ fdq->dqb_ihardlimit = mdq.dqb_ihardlimit;
+ fdq->dqb_isoftlimit = mdq.dqb_isoftlimit;
+ fdq->dqb_btime = mdq.dqb_btime;
+ fdq->dqb_itime = mdq.dqb_itime;
+ }
+ return 0;
+}
+
+static void check_read_blk(int fd, uint blk, dqbuf_t buf)
+{
+ size_t rd;
+
+ lseek(fd, blk << V2_DQBLKSIZE_BITS, SEEK_SET);
+ rd = read(fd, buf, V2_DQBLKSIZE);
+ if (rd < 0)
+ die(2, _("Can't read block %u: %s\n"), blk, strerror(errno));
+ if (rd != V2_DQBLKSIZE) {
+ debug(FL_VERBOSE | FL_DEBUG, _("Block %u is truncated.\n"), blk);
+ memset(buf + rd, 0, V2_DQBLKSIZE - rd);
+ }
+}
+
+static int check_tree_ref(uint blk, uint ref, uint blocks, int check_use, uint * corrupted,
+ uint * lblk)
+{
+ if (check_blkref(ref, blocks) < 0)
+ blk_corrupted(corrupted, lblk, blk, _("Reference to illegal block %u"), ref);
+ if (!ref)
+ return 0;
+ if (!check_use || !GET_BLK(ref))
+ return 0;
+ blk_corrupted(corrupted, lblk, blk, _("Block %u in tree referenced twice"), ref);
+ return -1;
+}
+
+/* Check block with structures */
+static int check_data_blk(int fd, uint blk, int type, uint blocks, uint * corrupted, uint * lblk)
+{
+ dqbuf_t buf = getdqbuf();
+ struct v2_disk_dqdbheader *head = (struct v2_disk_dqdbheader *)buf;
+ int i;
+ struct v2_disk_dqblk *dd = (struct v2_disk_dqblk *)(head + 1);
+
+ SET_BLK(blk);
+ check_read_blk(fd, blk, buf);
+ if (check_blkref(__le32_to_cpu(head->dqdh_next_free), blocks) < 0)
+ blk_corrupted(corrupted, lblk, blk, _("Illegal free block reference to block %u"),
+ __le32_to_cpu(head->dqdh_next_free));
+ if (__le16_to_cpu(head->dqdh_entries) > V2_DQSTRINBLK)
+ blk_corrupted(corrupted, lblk, blk, _("Corrupted number of used entries (%u)"),
+ (uint) __le16_to_cpu(head->dqdh_entries));
+ for (i = 0; i < V2_DQSTRINBLK; i++)
+ if (!empty_dquot(dd + i))
+ if (buffer_entry(buf, blk, corrupted, lblk, i, type) < 0) {
+ freedqbuf(buf);
+ return -1;
+ }
+ freedqbuf(buf);
+ return 0;
+}
+
+/* Check one tree block */
+static int check_tree_blk(int fd, uint blk, int depth, int type, uint blocks, uint * corrupted,
+ uint * lblk)
+{
+ dqbuf_t buf = getdqbuf();
+ u_int32_t *r = (u_int32_t *) buf;
+ int i;
+
+ SET_BLK(blk);
+ check_read_blk(fd, blk, buf);
+ for (i = 0; i < V2_DQBLKSIZE >> 2; i++)
+ if (depth < V2_DQTREEDEPTH - 1) {
+ if (check_tree_ref(blk, __le32_to_cpu(r[i]), blocks, 1, corrupted, lblk) >= 0 &&
+ __le32_to_cpu(r[i])) /* Isn't block OK? */
+ if (check_tree_blk(fd, __le32_to_cpu(r[i]), depth + 1, type, blocks, corrupted, lblk) < 0) {
+ freedqbuf(buf);
+ return -1;
+ }
+ }
+ else if (check_tree_ref(blk, __le32_to_cpu(r[i]), blocks, 0, corrupted, lblk) >= 0 && __le32_to_cpu(r[i]))
+ if (check_data_blk(fd, __le32_to_cpu(r[i]), type, blocks, corrupted, lblk) < 0) {
+ freedqbuf(buf);
+ return -1;
+ }
+ freedqbuf(buf);
+ return 0;
+}
+
+/* Check basic header */
+static int check_header(char *filename, int fd, int type)
+{
+ int err;
+ struct v2_disk_dqheader head;
+
+ debug(FL_DEBUG, _("Checking quotafile headers...\n"));
+ lseek(fd, 0, SEEK_SET);
+ err = read(fd, &head, sizeof(head));
+ if (err < 0)
+ die(1, _("Can't read header from quotafile %s: %s\n"), filename, strerror(errno));
+ if (err != sizeof(head)) {
+ fprintf(stderr, _("WARNING: Quotafile %s was probably truncated. Can't save quota settings...\n"),
+ filename);
+ return -1;
+ }
+ if (__le32_to_cpu(head.dqh_magic) != magics[type] || __le32_to_cpu(head.dqh_version) > known_versions[type])
+ fprintf(stderr, _("WARNING: Quota file %s has corrupted headers\n"), filename);
+ debug(FL_DEBUG, _("Headers checked.\n"));
+ return 0;
+}
+
+/* Load data from file to memory */
+int v2_buffer_file(char *filename, int fd, int type)
+{
+ uint blocks, lastblk = 0;
+ int corrupted = 0, ret = 0;
+
+ old_info[type].dqi_bgrace = MAX_DQ_TIME;
+ old_info[type].dqi_igrace = MAX_IQ_TIME;
+ if (flags & FL_NEWFILE)
+ return 0;
+ if (check_header(filename, fd, type) < 0)
+ return 0;
+ if (check_info(filename, fd, type) < 0)
+ return 0;
+ debug(FL_DEBUG | FL_VERBOSE, _("Headers of file %s checked. Going to load data...\n"),
+ filename);
+ blocks = old_info[type].u.v2_mdqi.dqi_blocks;
+ blkbmp = xmalloc((blocks + 7) >> 3);
+ memset(blkbmp, 0, (blocks + 7) >> 3);
+ if (check_tree_ref(0, V2_DQTREEOFF, blocks, 1, &corrupted, &lastblk) >= 0)
+ ret = check_tree_blk(fd, V2_DQTREEOFF, 0, type, blocks, &corrupted, &lastblk);
+ else
+ fprintf(stderr, _("Can't gather quota data. Tree root node corrupted.\n"));
+#ifdef DEBUG_MALLOC
+ free_mem += (blocks + 7) >> 3;
+#endif
+ free(blkbmp);
+ if (corrupted) {
+ if (!(flags & (FL_VERBOSE | FL_DEBUG)))
+ fputc('\n', stderr);
+ fprintf(stderr, _("WARNING: Some data might be changed due to corruption.\n"));
+ }
+ else
+ debug(FL_DEBUG | FL_VERBOSE, _("Not found any corrupted blocks. Congratulations.\n"));
+ return ret;
+}