diff options
author | Aditya Kali <adityakali@google.com> | 2011-07-20 11:40:02 -0700 |
---|---|---|
committer | Theodore Ts'o <tytso@mit.edu> | 2011-08-31 15:21:40 -0400 |
commit | f239fefc14226f655477179801c734749a04d4b4 (patch) | |
tree | 5d614dcaf12abbf9dc00cf8b1f3f95eb61662e3a /lib/quota | |
parent | a4aff9ca5bcc3df76dcb3d49765674feba3d7654 (diff) | |
download | e2fsprogs-f239fefc14226f655477179801c734749a04d4b4.tar.gz |
e2fsprogs: add quota library to e2fsprogs
This patch adds the quota library (ported form Jan Kara's quota-tools) in
e2fsprogs in order to make quotas as a first class supported feature in Ext4.
This patch also provides interface in lib/quota/mkquota.h that will be used by
mke2fs, tune2fs, e2fsck, etc. to initialize and update quota files.
This first version of the quota library does not support reading existing quota
files. This support will be added in the near future.
Thanks to Jan Kara for his work on quota-tools. Most of the files in this patch
are taken as-is from quota tools and were simply modified to work with
libext2fs in e2fsprogs.
Signed-off-by: Aditya Kali <adityakali@google.com>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>
Diffstat (limited to 'lib/quota')
-rw-r--r-- | lib/quota/Makefile.in | 160 | ||||
-rw-r--r-- | lib/quota/common.c | 62 | ||||
-rw-r--r-- | lib/quota/common.h | 78 | ||||
-rw-r--r-- | lib/quota/dqblk_v2.h | 43 | ||||
-rw-r--r-- | lib/quota/mkquota.c | 400 | ||||
-rw-r--r-- | lib/quota/mkquota.h | 66 | ||||
-rw-r--r-- | lib/quota/quota.h | 190 | ||||
-rw-r--r-- | lib/quota/quota.pc.in | 11 | ||||
-rw-r--r-- | lib/quota/quotaio.c | 359 | ||||
-rw-r--r-- | lib/quota/quotaio.h | 163 | ||||
-rw-r--r-- | lib/quota/quotaio_tree.c | 574 | ||||
-rw-r--r-- | lib/quota/quotaio_tree.h | 63 | ||||
-rw-r--r-- | lib/quota/quotaio_v2.c | 313 | ||||
-rw-r--r-- | lib/quota/quotaio_v2.h | 103 |
14 files changed, 2585 insertions, 0 deletions
diff --git a/lib/quota/Makefile.in b/lib/quota/Makefile.in new file mode 100644 index 00000000..0005ac02 --- /dev/null +++ b/lib/quota/Makefile.in @@ -0,0 +1,160 @@ +# Makefile for the QUOTA library +# + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +top_builddir = ../.. +my_dir = lib/quota +INSTALL = @INSTALL@ + +@MCONFIG@ + +all:: + +SMANPAGES= + + +OBJS= common.o mkquota.o quotaio.o quotaio_v2.o quotaio_tree.o dict.o + +SRCS= $(srcdir)/common.c \ + $(srcdir)/mkquota.c \ + $(srcdir)/quotaio.c \ + $(srcdir)/quotaio_tree.c \ + $(srcdir)/quotaio_v2.c \ + $(srcdir)/../../e2fsck/dict.c + +LIBRARY= libquota +LIBDIR= quota + +ELF_VERSION = 1.0 +ELF_SO_VERSION = 1 +ELF_IMAGE = libquota +ELF_MYDIR = quota +ELF_INSTALL_DIR = $(root_libdir) +ELF_OTHER_LIBS = + +BSDLIB_VERSION = 1.0 +BSDLIB_IMAGE = libquota +BSDLIB_MYDIR = quota +BSDLIB_INSTALL_DIR = $(root_libdir) + +@MAKEFILE_LIBRARY@ +@MAKEFILE_ELF@ +@MAKEFILE_BSDLIB@ +@MAKEFILE_PROFILE@ +@MAKEFILE_CHECKER@ + +.c.o: + $(E) " CC $<" + $(Q) $(CC) $(ALL_CFLAGS) -c $< -o $@ +@PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $< +@CHECKER_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -checker -g -o checker/$*.o -c $< +@ELF_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -fPIC -o elfshared/$*.o -c $< +@BSDLIB_CMT@ $(Q) $(CC) $(ALL_CFLAGS) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c $< + +all:: $(SMANPAGES) quota.pc + +quota.pc: $(srcdir)/quota.pc.in $(top_builddir)/config.status + $(E) " CONFIG.STATUS $@" + $(Q) cd $(top_builddir); CONFIG_FILES=lib/quota/quota.pc ./config.status + +dict.o: + $(E) " CC $<" + $(Q) $(CC) -c $(ALL_CFLAGS) $(top_srcdir)/e2fsck/dict.c -o $@ +@PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/dict.o -c \ +@PROFILE_CMT@ $(top_srcdir)/e2fsck/dict.c +@CHECKER_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -checker -g -o checker/$*.o -c \ +@CHECKER_CMT@ $(top_srcdir)/e2fsck/dict.c +@ELF_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -fPIC -o elfshared/$*.o -c \ +@ELF_CMT@ $(top_srcdir)/e2fsck/dict.c +@BSDLIB_CMT@ $(Q) $(CC) $(ALL_CFLAGS) $(BSDLIB_PIC_FLAG) -o pic/$*.o -c \ +@BSDLIB_CMT@ $(top_srcdir)/e2fsck/dict.c + +installdirs:: + $(E) " MKINSTALLDIRS $(libdir) $(includedir)/quota $(man3dir)" + $(Q) $(MKINSTALLDIRS) $(DESTDIR)$(libdir) \ + $(DESTDIR)$(includedir)/quota $(DESTDIR)$(man3dir) \ + $(DESTDIR)$(libdir)/pkgconfig + +install:: all installdirs + $(E) " INSTALL_DATA $(libdir)/libquota.a" + $(Q) $(INSTALL_DATA) libquota.a $(DESTDIR)$(libdir)/libquota.a + -$(Q) $(RANLIB) $(DESTDIR)$(libdir)/libquota.a + $(Q) $(CHMOD) $(LIBMODE) $(DESTDIR)$(libdir)/libquota.a + $(E) " INSTALL_DATA $(includedir)/quota/quota.h" + $(Q) $(INSTALL_DATA) mkquota.h $(DESTDIR)$(includedir)/quota/mkquota.h + $(Q) for i in $(SMANPAGES); do \ + $(RM) -f $(DESTDIR)$(man3dir)/$$i.gz; \ + echo " INSTALL_DATA $(man3dir)/$$i"; \ + $(INSTALL_DATA) $$i $(DESTDIR)$(man3dir)/$$i; \ + done + $(E) " INSTALL_DATA $(libdir)/pkgconfig/quota.pc" + $(Q) $(INSTALL_DATA) quota.pc $(DESTDIR)$(libdir)/pkgconfig/quota.pc + +uninstall:: + $(RM) -f $(DESTDIR)$(libdir)/libquota.a \ + $(DESTDIR)$(libdir)/pkgconfig/quota.pc + for i in $(SMANPAGES); do \ + $(RM) -f $(DESTDIR)$(man3dir)/$$i; \ + done + +clean:: + $(RM) -f \#* *.s *.o *.a *~ *.bak core profiled/* checker/* + $(RM) -f ../libquota.a ../libquota_p.a $(SMANPAGES) + +#check:: tst_uuid +# LD_LIBRARY_PATH=$(LIB) DYLD_LIBRARY_PATH=$(LIB) ./tst_uuid + +mostlyclean:: clean +distclean:: clean + $(RM) -f .depend Makefile quota.pc \ + $(srcdir)/TAGS $(srcdir)/Makefile.in.old + +# +# Hack to parallel makes recognize dependencies correctly. +# +../../lib/libquota.a: libquota.a +../../lib/libquota.so: image +../../lib/libquota.dylib: image + +$(OBJS): + +# +++ Dependency line eater +++ +# +# Makefile dependencies follow. This must be the last section in +# the Makefile.in file +# +common.o: $(srcdir)/common.c $(srcdir)/common.h +mkquota.o: $(srcdir)/mkquota.c $(top_srcdir)/lib/ext2fs/ext2_fs.h \ + $(top_builddir)/lib/ext2fs/ext2_types.h $(top_srcdir)/lib/ext2fs/ext2fs.h \ + $(top_srcdir)/lib/ext2fs/ext3_extents.h $(top_srcdir)/lib/et/com_err.h \ + $(top_srcdir)/lib/ext2fs/ext2_io.h $(top_builddir)/lib/ext2fs/ext2_err.h \ + $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/bitops.h \ + $(top_srcdir)/lib/e2p/e2p.h $(srcdir)/quota.h $(srcdir)/quotaio.h \ + $(srcdir)/dqblk_v2.h $(srcdir)/quotaio_tree.h $(srcdir)/quotaio_v2.h \ + $(srcdir)/mkquota.h $(top_srcdir)/lib/../e2fsck/dict.h $(srcdir)/common.h +quotaio.o: $(srcdir)/quotaio.c $(srcdir)/common.h $(srcdir)/quotaio.h \ + $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \ + $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \ + $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \ + $(top_builddir)/lib/ext2fs/ext2_err.h \ + $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/bitops.h \ + $(srcdir)/quota.h $(srcdir)/dqblk_v2.h $(srcdir)/quotaio_tree.h +quotaio_tree.o: $(srcdir)/quotaio_tree.c $(srcdir)/common.h \ + $(srcdir)/quotaio_tree.h $(srcdir)/quota.h $(srcdir)/quotaio.h \ + $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \ + $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \ + $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \ + $(top_builddir)/lib/ext2fs/ext2_err.h \ + $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/bitops.h \ + $(srcdir)/dqblk_v2.h +quotaio_v2.o: $(srcdir)/quotaio_v2.c $(srcdir)/common.h \ + $(srcdir)/quotaio_v2.h $(srcdir)/quota.h $(srcdir)/dqblk_v2.h \ + $(srcdir)/quotaio_tree.h $(srcdir)/quotaio.h \ + $(top_srcdir)/lib/ext2fs/ext2fs.h $(top_builddir)/lib/ext2fs/ext2_types.h \ + $(top_srcdir)/lib/ext2fs/ext2_fs.h $(top_srcdir)/lib/ext2fs/ext3_extents.h \ + $(top_srcdir)/lib/et/com_err.h $(top_srcdir)/lib/ext2fs/ext2_io.h \ + $(top_builddir)/lib/ext2fs/ext2_err.h \ + $(top_srcdir)/lib/ext2fs/ext2_ext_attr.h $(top_srcdir)/lib/ext2fs/bitops.h +dict.o: $(srcdir)/../../e2fsck/dict.c $(srcdir)/../../e2fsck/dict.h diff --git a/lib/quota/common.c b/lib/quota/common.c new file mode 100644 index 00000000..4538513b --- /dev/null +++ b/lib/quota/common.c @@ -0,0 +1,62 @@ +/* + * Common things for all utilities + * + * Jan Kara <jack@suse.cz> - sponsored by SuSE CR + * + * Jani Jaakkola <jjaakkol@cs.helsinki.fi> - syslog support + */ + +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <syslog.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "common.h" + +void *smalloc(size_t size) +{ + void *ret = malloc(size); + + if (!ret) { + fputs("Not enough memory.\n", stderr); + exit(3); + } + return ret; +} + +void *srealloc(void *ptr, size_t size) +{ + void *ret = realloc(ptr, size); + + if (!ret) { + fputs("Not enough memory.\n", stderr); + exit(3); + } + return ret; +} + +void sstrncpy(char *d, const char *s, size_t len) +{ + strncpy(d, s, len); + d[len - 1] = 0; +} + +void sstrncat(char *d, const char *s, size_t len) +{ + strncat(d, s, len); + d[len - 1] = 0; +} + +char *sstrdup(const char *s) +{ + char *r = strdup(s); + + if (!r) { + puts("Not enough memory."); + exit(3); + } + return r; +} diff --git a/lib/quota/common.h b/lib/quota/common.h new file mode 100644 index 00000000..48f191f6 --- /dev/null +++ b/lib/quota/common.h @@ -0,0 +1,78 @@ +/* + * + * Various things common for all utilities + * + */ + +#ifndef __QUOTA_COMMON_H__ +#define __QUOTA_COMMON_H__ + +#ifndef __attribute__ +# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__ +# define __attribute__(x) +# endif +#endif + +#ifdef ENABLE_NLS +#include <libintl.h> +#include <locale.h> +#define _(a) (gettext (a)) +#ifdef gettext_noop +#define N_(a) gettext_noop (a) +#else +#define N_(a) (a) +#endif +#define P_(singular, plural, n) (ngettext (singular, plural, n)) +#ifndef NLS_CAT_NAME +#define NLS_CAT_NAME "e2fsprogs" +#endif +#ifndef LOCALEDIR +#define LOCALEDIR "/usr/share/locale" +#endif +#else +#define _(a) (a) +#define N_(a) a +#define P_(singular, plural, n) ((n) == 1 ? (singular) : (plural)) +#endif + +#define log_fatal(exit_code, format, ...) do { \ + fprintf(stderr, _("[FATAL] %s:%d:%s:: " format "\n"), \ + __FILE__, __LINE__, __func__, __VA_ARGS__); \ + exit(exit_code); \ + } while (0) + +#define log_err(format, ...) fprintf(stderr, \ + _("[ERROR] %s:%d:%s:: " format "\n"), \ + __FILE__, __LINE__, __func__, __VA_ARGS__) + +#ifdef DEBUG_QUOTA +# define log_debug(format, ...) fprintf(stderr, \ + _("[DEBUG] %s:%d:%s:: " format "\n"), \ + __FILE__, __LINE__, __func__, __VA_ARGS__) +#else +# define log_debug(format, ...) +#endif + +#define BUG_ON(x) do { if ((x)) { \ + fprintf(stderr, \ + _("BUG_ON: %s:%d:: ##x"), \ + __FILE__, __LINE__); \ + exit(2); \ + } } while (0) + +/* malloc() with error check */ +void *smalloc(size_t); + +/* realloc() with error check */ +void *srealloc(void *, size_t); + +/* Safe strncpy - always finishes string */ +void sstrncpy(char *, const char *, size_t); + +/* Safe strncat - always finishes string */ +void sstrncat(char *, const char *, size_t); + +/* Safe version of strdup() */ +char *sstrdup(const char *s); + +#endif /* __QUOTA_COMMON_H__ */ diff --git a/lib/quota/dqblk_v2.h b/lib/quota/dqblk_v2.h new file mode 100644 index 00000000..ca07902c --- /dev/null +++ b/lib/quota/dqblk_v2.h @@ -0,0 +1,43 @@ +/* + * Header file for disk format of new quotafile format + * + * Jan Kara <jack@suse.cz> - sponsored by SuSE CR + */ + +#ifndef __QUOTA_DQBLK_V2_H__ +#define __QUOTA_DQBLK_V2_H__ + +#include <sys/types.h> +#include "quotaio_tree.h" + +#define Q_V2_GETQUOTA 0x0D00 /* Get limits and usage */ +#define Q_V2_SETQUOTA 0x0E00 /* Set limits and usage */ +#define Q_V2_SETUSE 0x0F00 /* Set only usage */ +#define Q_V2_SETQLIM 0x0700 /* Set only limits */ +#define Q_V2_GETINFO 0x0900 /* Get information about quota */ +#define Q_V2_SETINFO 0x0A00 /* Set information about quota */ +#define Q_V2_SETGRACE 0x0B00 /* Set just grace times in quotafile + * information */ +#define Q_V2_SETFLAGS 0x0C00 /* Set just flags in quotafile information */ +#define Q_V2_GETSTATS 0x1100 /* get collected stats (before proc was used) */ + +/* Structure for format specific information */ +struct v2_mem_dqinfo { + struct qtree_mem_dqinfo dqi_qtree; + uint dqi_flags; /* Flags set in quotafile */ + uint dqi_used_entries; /* Number of entries in file - + updated by scan_dquots */ + uint dqi_data_blocks; /* Number of data blocks in file - + updated by scan_dquots */ +}; + +struct v2_mem_dqblk { + loff_t dqb_off; /* Offset of dquot in file */ +}; + +struct quotafile_ops; /* Will be defined later in quotaio.h */ + +/* Operations above this format */ +extern struct quotafile_ops quotafile_ops_2; + +#endif /* __QUOTA_DQBLK_V2_H__ */ diff --git a/lib/quota/mkquota.c b/lib/quota/mkquota.c new file mode 100644 index 00000000..cbc76f7e --- /dev/null +++ b/lib/quota/mkquota.c @@ -0,0 +1,400 @@ +/* + * mkquota.c --- create quota files for a filesystem + * + * Aditya Kali <adityakali@google.com> + */ +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "e2p/e2p.h" + +#include "quota.h" +#include "quotaio.h" +#include "quotaio_v2.h" +#include "quotaio_tree.h" +#include "mkquota.h" +#include "common.h" + +/* Needed for architectures where sizeof(int) != sizeof(void *) */ +#define UINT_TO_VOIDPTR(val) ((void *)(intptr_t)(val)) +#define VOIDPTR_TO_UINT(ptr) ((unsigned int)(intptr_t)(ptr)) + +static void print_inode(struct ext2_inode *inode) +{ + if (!inode) + return; + + fprintf(stderr, " i_mode = %d\n", inode->i_mode); + fprintf(stderr, " i_uid = %d\n", inode->i_uid); + fprintf(stderr, " i_size = %d\n", inode->i_size); + fprintf(stderr, " i_atime = %d\n", inode->i_atime); + fprintf(stderr, " i_ctime = %d\n", inode->i_ctime); + fprintf(stderr, " i_mtime = %d\n", inode->i_mtime); + fprintf(stderr, " i_dtime = %d\n", inode->i_dtime); + fprintf(stderr, " i_gid = %d\n", inode->i_gid); + fprintf(stderr, " i_links_count = %d\n", inode->i_links_count); + fprintf(stderr, " i_blocks = %d\n", inode->i_blocks); + fprintf(stderr, " i_flags = %d\n", inode->i_flags); + + return; +} + +int is_quota_on(ext2_filsys fs, int type) +{ + char tmp[1024]; + qid_t id = (type == USRQUOTA) ? getuid() : getgid(); + + if (!quotactl(QCMD(Q_V2_GETQUOTA, type), fs->device_name, id, tmp)) + return 1; + return 0; +} + +/* + * Returns 0 if not able to find the quota file, otherwise returns its + * inode number. + */ +int quota_file_exists(ext2_filsys fs, int qtype, int fmt) +{ + char qf_name[256]; + errcode_t ret; + ext2_ino_t ino; + + if (qtype >= MAXQUOTAS || fmt > QFMT_VFS_V1) + return -EINVAL; + + get_qf_name(qtype, fmt, qf_name); + + ret = ext2fs_lookup(fs, EXT2_ROOT_INO, qf_name, strlen(qf_name), 0, + &ino); + if (ret) + return 0; + + return ino; +} + +/* + * Set the value for reserved quota inode number field in superblock. + */ +void set_sb_quota_inum(ext2_filsys fs, ext2_ino_t ino, int qtype) +{ + ext2_ino_t *inump; + + inump = (qtype == USRQUOTA) ? &fs->super->s_usr_quota_inum : + &fs->super->s_grp_quota_inum; + + log_debug("setting quota ino in superblock: ino=%u, type=%d", ino, + qtype); + *inump = ino; + ext2fs_mark_super_dirty(fs); +} + +errcode_t remove_quota_inode(ext2_filsys fs, int qtype) +{ + ext2_ino_t qf_ino; + + ext2fs_read_bitmaps(fs); + qf_ino = (qtype == USRQUOTA) ? fs->super->s_usr_quota_inum : + fs->super->s_grp_quota_inum; + set_sb_quota_inum(fs, 0, qtype); + /* Truncate the inode only if its a reserved one. */ + if (qf_ino < EXT2_FIRST_INODE(fs->super)) + truncate_quota_inode(fs, qf_ino); + + ext2fs_mark_super_dirty(fs); + ext2fs_write_bitmaps(fs); + return 0; +} + +static void write_dquots(dict_t *dict, struct quota_handle *qh) +{ + int i = 0; + dnode_t *n; + struct dquot *dq; + __u32 key; + + for (n = dict_first(dict); n; n = dict_next(dict, n)) { + dq = dnode_get(n); + if (dq) { + dq->dq_h = qh; + update_grace_times(dq); + qh->qh_ops->commit_dquot(dq, COMMIT_ALL); + } + } +} + +errcode_t write_quota_inode(quota_ctx_t qctx, int qtype) +{ + int retval, i; + unsigned long qf_inums[MAXQUOTAS]; + struct dquot *dquot; + dict_t *dict; + ext2_filsys fs; + struct quota_handle *h; + int fmt = QFMT_VFS_V1; + + if (!qctx) + return; + + fs = qctx->fs; + h = smalloc(sizeof(struct quota_handle)); + ext2fs_read_bitmaps(fs); + + for (i = 0; i < MAXQUOTAS; i++) { + if ((qtype != -1) && (i != qtype)) + continue; + + dict = qctx->quota_dict[i]; + if (!dict) + continue; + + retval = new_io(h, fs, i, fmt); + if (retval < 0) { + log_err("Cannot initialize io on quotafile", ""); + continue; + } + + write_dquots(dict, h); + retval = end_io(h); + if (retval < 0) { + log_err("Cannot finish IO on new quotafile: %s", + strerror(errno)); + if (h->qh_qf.e2_file) + ext2fs_file_close(h->qh_qf.e2_file); + truncate_quota_inode(fs, h->qh_qf.ino); + continue; + } + + /* Set quota inode numbers in superblock. */ + set_sb_quota_inum(fs, h->qh_qf.ino, i); + ext2fs_mark_super_dirty(fs); + ext2fs_mark_bb_dirty(fs); + fs->flags &= ~EXT2_FLAG_SUPER_ONLY; + } + + ext2fs_write_bitmaps(fs); +out: + free(h); + return retval; +} + +/******************************************************************/ +/* Helper functions for computing quota in memory. */ +/******************************************************************/ + +static int dict_uint_cmp(const void *a, const void *b) +{ + unsigned int c, d; + + c = VOIDPTR_TO_UINT(a); + d = VOIDPTR_TO_UINT(b); + + return c - d; +} + +static qid_t get_qid(struct ext2_inode *inode, int qtype) +{ + switch (qtype) { + case USRQUOTA: + return inode_uid(*inode); + case GRPQUOTA: + return inode_gid(*inode); + default: + log_err("Invalid quota type: %d", qtype); + BUG_ON(1); + } +} + +static void quota_dnode_free(dnode_t *node, + void *context EXT2FS_ATTR((unused))) +{ + void *ptr = node ? dnode_get(node) : 0; + + free(ptr); + free(node); +} + +/* + * Called in Pass #1 to set up the quota tracking data structures. + */ +void init_quota_context(quota_ctx_t *qctx, ext2_filsys fs, int qtype) +{ + int i; + dict_t *dict; + quota_ctx_t ctx; + + ctx = (quota_ctx_t)smalloc(sizeof(struct quota_ctx)); + memset(ctx, 0, sizeof(struct quota_ctx)); + for (i = 0; i < MAXQUOTAS; i++) { + if ((qtype != -1) && (i != qtype)) + continue; + dict = (dict_t *)smalloc(sizeof(dict_t)); + ctx->quota_dict[i] = dict; + dict_init(dict, DICTCOUNT_T_MAX, dict_uint_cmp); + dict_set_allocator(dict, NULL, quota_dnode_free, NULL); + } + + ctx->fs = fs; + *qctx = ctx; +} + +void release_quota_context(quota_ctx_t *qctx) +{ + dict_t *dict; + int i; + quota_ctx_t ctx; + + if (!qctx) + return; + + ctx = *qctx; + for (i = 0; i < MAXQUOTAS; i++) { + dict = ctx->quota_dict[i]; + ctx->quota_dict[i] = 0; + if (dict) { + dict_free_nodes(dict); + free(dict); + } + } + *qctx = NULL; + free(ctx); +} + +static struct dquot *get_dq(dict_t *dict, __u32 key) +{ + struct dquot *dq; + dnode_t *n; + + n = dict_lookup(dict, UINT_TO_VOIDPTR(key)); + if (n) + dq = dnode_get(n); + else { + dq = smalloc(sizeof(struct dquot)); + memset(dq, 0, sizeof(struct dquot)); + dict_alloc_insert(dict, UINT_TO_VOIDPTR(key), dq); + } + return dq; +} + + +/* + * Called to update the blocks used by a particular inode + */ +void quota_data_add(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino, + qsize_t space) +{ + struct dquot *dq; + dict_t *dict; + int i; + + if (!qctx) + return; + + log_debug("ADD_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino, + inode_uid(*inode), + inode_gid(*inode), space); + for (i = 0; i < MAXQUOTAS; i++) { + dict = qctx->quota_dict[i]; + if (dict) { + dq = get_dq(dict, get_qid(inode, i)); + dq->dq_dqb.dqb_curspace += space; + } + } +} + +/* + * Called to remove some blocks used by a particular inode + */ +void quota_data_sub(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino, + qsize_t space) +{ + struct dquot *dq; + dict_t *dict; + int i; + + if (!qctx) + return; + + log_debug("SUB_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino, + inode_uid(*inode), + inode_gid(*inode), space); + for (i = 0; i < MAXQUOTAS; i++) { + dict = qctx->quota_dict[i]; + if (dict) { + dq = get_dq(dict, get_qid(inode, i)); + dq->dq_dqb.dqb_curspace -= space; + } + } +} + +/* + * Called to count the files used by an inode's user/group + */ +void quota_data_inodes(quota_ctx_t qctx, struct ext2_inode *inode, + ext2_ino_t ino, int adjust) +{ + struct dquot *dq; + dict_t *dict; + int i; + + if (!qctx) + return; + + log_debug("ADJ_INODE: Inode: %u, UID/GID: %u/%u, adjust: %d", ino, + inode_uid(*inode), + inode_gid(*inode), adjust); + for (i = 0; i < MAXQUOTAS; i++) { + dict = qctx->quota_dict[i]; + if (dict) { + dq = get_dq(dict, get_qid(inode, i)); + dq->dq_dqb.dqb_curinodes += adjust; + } + } +} + +errcode_t compute_quota(quota_ctx_t qctx, int qtype) +{ + ext2_filsys fs; + const char *name = "lost+found"; + ext2_ino_t ino; + errcode_t ret; + struct ext2_inode inode; + qsize_t space; + ext2_inode_scan scan; + + if (!qctx) + return; + + fs = qctx->fs; + ret = ext2fs_open_inode_scan(fs, 0, &scan); + if (ret) { + log_err("while opening inode scan. ret=%ld", ret); + return ret; + } + + while (1) { + ret = ext2fs_get_next_inode(scan, &ino, &inode); + if (ret) { + log_err("while getting next inode. ret=%ld", ret); + ext2fs_close_inode_scan(scan); + return ret; + } + if (ino == 0) + break; + if (inode.i_links_count) { + /* Convert i_blocks to # of 1k blocks */ + space = (ext2fs_inode_i_blocks(fs, &inode) + 1) >> 1; + quota_data_add(qctx, &inode, ino, space); + quota_data_inodes(qctx, &inode, ino, +1); + } + } + + ext2fs_close_inode_scan(scan); + + return 0; +} diff --git a/lib/quota/mkquota.h b/lib/quota/mkquota.h new file mode 100644 index 00000000..b4eba146 --- /dev/null +++ b/lib/quota/mkquota.h @@ -0,0 +1,66 @@ +/** mkquota.h + * + * Interface to the quota library. + * + * The quota library provides interface for creating and updating the quota + * files and the ext4 superblock fields. It supports the new VFS_V1 quota + * format. The quota library also provides support for keeping track of quotas + * in memory. + * The typical way to use the quota library is as follows: + * { + * quota_ctx_t qctx; + * + * init_quota_context(&qctx, fs, -1); + * { + * compute_quota(qctx, -1); + * AND/OR + * quota_data_add/quota_data_sub/quota_data_inodes(); + * } + * write_quota_inode(qctx, USRQUOTA); + * write_quota_inode(qctx, GRPQUOTA); + * release_quota_context(&qctx); + * } + * + * This initial version does not support reading the quota files. This support + * will be added in near future. + * + * Aditya Kali <adityakali@google.com> + */ + +#ifndef __QUOTA_QUOTAIO_H__ +#define __QUOTA_QUOTAIO_H__ + +#include "ext2fs/ext2_fs.h" +#include "ext2fs/ext2fs.h" +#include "quota.h" +#include "../e2fsck/dict.h" + +typedef struct quota_ctx *quota_ctx_t; + +struct quota_ctx { + ext2_filsys fs; + dict_t *quota_dict[MAXQUOTAS]; +}; + +void init_quota_context(quota_ctx_t *qctx, ext2_filsys fs, int qtype); +void quota_data_inodes(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino, + int adjust); +void quota_data_add(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino, + qsize_t space); +void quota_data_sub(quota_ctx_t qctx, struct ext2_inode *inode, ext2_ino_t ino, + qsize_t space); +errcode_t write_quota_inode(quota_ctx_t qctx, int qtype); +errcode_t compute_quota(quota_ctx_t qctx, int qtype); +void release_quota_context(quota_ctx_t *qctx); + +errcode_t remove_quota_inode(ext2_filsys fs, int qtype); +int is_quota_on(ext2_filsys fs, int type); +int quota_file_exists(ext2_filsys fs, int qtype, int fmt); +void set_sb_quota_inum(ext2_filsys fs, ext2_ino_t ino, int qtype); + +/* in quotaio.c */ +const char *get_qf_name(int type, int fmt, char *buf); +const char *get_qf_path(const char *mntpt, int qtype, int fmt, + char *path_buf, size_t path_buf_size); + +#endif /* __QUOTA_QUOTAIO_H__ */ diff --git a/lib/quota/quota.h b/lib/quota/quota.h new file mode 100644 index 00000000..aab66d96 --- /dev/null +++ b/lib/quota/quota.h @@ -0,0 +1,190 @@ +/* + * Copyright (c) 1982, 1986 Regents of the University of California. + * All rights reserved. + * + * This code is derived from software contributed to Berkeley by + * Robert Elz at The University of Melbourne. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#ifndef _LINUX_QUOTA_ +#define _LINUX_QUOTA_ + +#include <errno.h> +#include <sys/types.h> + +#define __DQUOT_VERSION__ "dquot_6.5.2" + +typedef u_int32_t qid_t; /* Type in which we store ids in memory */ +typedef int64_t qsize_t; /* Type in which we store size limitations */ + +#define MAXQUOTAS 2 +#define USRQUOTA 0 /* element used for user quotas */ +#define GRPQUOTA 1 /* element used for group quotas */ + +/* + * Definitions for the default names of the quotas files. + */ +#define INITQFNAMES { \ + "user", /* USRQUOTA */ \ + "group", /* GRPQUOTA */ \ + "undefined", \ +}; + +/* + * Definitions of magics and versions of current quota files + */ +#define INITQMAGICS {\ + 0xd9c01f11, /* USRQUOTA */\ + 0xd9c01927 /* GRPQUOTA */\ +} + +/* Size of blocks in which are counted size limits in generic utility parts */ +#define QUOTABLOCK_BITS 10 +#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS) + +/* Conversion routines from and to quota blocks */ +#define qb2kb(x) ((x) << (QUOTABLOCK_BITS-10)) +#define kb2qb(x) ((x) >> (QUOTABLOCK_BITS-10)) +#define toqb(x) (((x) + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS) + +/* + * Command definitions for the 'quotactl' system call. + * The commands are broken into a main command defined below + * and a subcommand that is used to convey the type of + * quota that is being manipulated (see above). + */ +#define SUBCMDMASK 0x00ff +#define SUBCMDSHIFT 8 +#define QCMD(cmd, type) (((cmd) << SUBCMDSHIFT) | ((type) & SUBCMDMASK)) + +#define Q_SYNC 0x800001 /* sync disk copy of a filesystems quotas */ +#define Q_QUOTAON 0x800002 /* turn quotas on */ +#define Q_QUOTAOFF 0x800003 /* turn quotas off */ +#define Q_GETFMT 0x800004 /* get quota format used on given filesystem */ +#define Q_GETINFO 0x800005 /* get information about quota files */ +#define Q_SETINFO 0x800006 /* set information about quota files */ +#define Q_GETQUOTA 0x800007 /* get user quota structure */ +#define Q_SETQUOTA 0x800008 /* set user quota structure */ + +/* Quota format type IDs */ +#define QFMT_VFS_OLD 1 +#define QFMT_VFS_V0 2 +#define QFMT_OCFS2 3 +#define QFMT_VFS_V1 4 + +/* Size of block in which space limits are passed through the quota + * interface */ +#define QIF_DQBLKSIZE_BITS 10 +#define QIF_DQBLKSIZE (1 << QIF_DQBLKSIZE_BITS) + +/* + * Quota structure used for communication with userspace via quotactl + * Following flags are used to specify which fields are valid + */ +enum { + QIF_BLIMITS_B = 0, + QIF_SPACE_B, + QIF_ILIMITS_B, + QIF_INODES_B, + QIF_BTIME_B, + QIF_ITIME_B, +}; + +#define QIF_BLIMITS (1 << QIF_BLIMITS_B) +#define QIF_SPACE (1 << QIF_SPACE_B) +#define QIF_ILIMITS (1 << QIF_ILIMITS_B) +#define QIF_INODES (1 << QIF_INODES_B) +#define QIF_BTIME (1 << QIF_BTIME_B) +#define QIF_ITIME (1 << QIF_ITIME_B) +#define QIF_LIMITS (QIF_BLIMITS | QIF_ILIMITS) +#define QIF_USAGE (QIF_SPACE | QIF_INODES) +#define QIF_TIMES (QIF_BTIME | QIF_ITIME) +#define QIF_ALL (QIF_LIMITS | QIF_USAGE | QIF_TIMES) + +struct if_dqblk { + __u64 dqb_bhardlimit; + __u64 dqb_bsoftlimit; + __u64 dqb_curspace; + __u64 dqb_ihardlimit; + __u64 dqb_isoftlimit; + __u64 dqb_curinodes; + __u64 dqb_btime; + __u64 dqb_itime; + __u32 dqb_valid; +}; + +/* + * Structure used for setting quota information about file via quotactl + * Following flags are used to specify which fields are valid + */ +#define IIF_BGRACE 1 +#define IIF_IGRACE 2 +#define IIF_FLAGS 4 +#define IIF_ALL (IIF_BGRACE | IIF_IGRACE | IIF_FLAGS) + +struct if_dqinfo { + __u64 dqi_bgrace; + __u64 dqi_igrace; + __u32 dqi_flags; + __u32 dqi_valid; +}; + +/* + * Definitions for quota netlink interface + */ +#define QUOTA_NL_NOWARN 0 +#define QUOTA_NL_IHARDWARN 1 /* Inode hardlimit reached */ +#define QUOTA_NL_ISOFTLONGWARN 2 /* Inode grace time expired */ +#define QUOTA_NL_ISOFTWARN 3 /* Inode softlimit reached */ +#define QUOTA_NL_BHARDWARN 4 /* Block hardlimit reached */ +#define QUOTA_NL_BSOFTLONGWARN 5 /* Block grace time expired */ +#define QUOTA_NL_BSOFTWARN 6 /* Block softlimit reached */ +#define QUOTA_NL_IHARDBELOW 7 /* Usage got below inode hardlimit */ +#define QUOTA_NL_ISOFTBELOW 8 /* Usage got below inode softlimit */ +#define QUOTA_NL_BHARDBELOW 9 /* Usage got below block hardlimit */ +#define QUOTA_NL_BSOFTBELOW 10 /* Usage got below block softlimit */ + +enum { + QUOTA_NL_C_UNSPEC, + QUOTA_NL_C_WARNING, + __QUOTA_NL_C_MAX, +}; +#define QUOTA_NL_C_MAX (__QUOTA_NL_C_MAX - 1) + +enum { + QUOTA_NL_A_UNSPEC, + QUOTA_NL_A_QTYPE, + QUOTA_NL_A_EXCESS_ID, + QUOTA_NL_A_WARNING, + QUOTA_NL_A_DEV_MAJOR, + QUOTA_NL_A_DEV_MINOR, + QUOTA_NL_A_CAUSED_ID, + __QUOTA_NL_A_MAX, +}; +#define QUOTA_NL_A_MAX (__QUOTA_NL_A_MAX - 1) + +#endif /* _QUOTA_ */ diff --git a/lib/quota/quota.pc.in b/lib/quota/quota.pc.in new file mode 100644 index 00000000..bcc3c441 --- /dev/null +++ b/lib/quota/quota.pc.in @@ -0,0 +1,11 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: quota +Description: Quota management library +Version: @E2FSPROGS_VERSION@ +Requires: +Cflags: -I${includedir}/quota +Libs: -L${libdir} -lquota diff --git a/lib/quota/quotaio.c b/lib/quota/quotaio.c new file mode 100644 index 00000000..803bf12a --- /dev/null +++ b/lib/quota/quotaio.c @@ -0,0 +1,359 @@ +/** quotaio.c + * + * Generic IO operations on quotafiles + * Jan Kara <jack@suse.cz> - sponsored by SuSE CR + * Aditya Kali <adityakali@google.com> - Ported to e2fsprogs + */ + +#include <stdio.h> +#include <errno.h> +#include <string.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/file.h> +#include <asm/byteorder.h> + +#include "common.h" +#include "quotaio.h" + +static const char extensions[MAXQUOTAS + 2][20] = INITQFNAMES; +static const char * const basenames[] = { + "", /* undefined */ + "quota", /* QFMT_VFS_OLD */ + "aquota", /* QFMT_VFS_V0 */ + "", /* QFMT_OCFS2 */ + "aquota" /* QFMT_VFS_V1 */ +}; + +static const char * const fmtnames[] = { + "vfsold", + "vfsv0", + "vfsv1", + "rpc", + "xfs" +}; + +/* Header in all newer quotafiles */ +struct disk_dqheader { + u_int32_t dqh_magic; + u_int32_t dqh_version; +} __attribute__ ((packed)); + +/** + * Convert type of quota to written representation + */ +const char *type2name(int type) +{ + return extensions[type]; +} + +/** + * Creates a quota file name for given type and format. + */ +const char *get_qf_name(int type, int fmt, char *buf) +{ + BUG_ON(!buf); + snprintf(buf, PATH_MAX, "%s.%s", + basenames[fmt], extensions[type]); + + return buf; +} + +const char *get_qf_path(const char *mntpt, int qtype, int fmt, + char *path_buf, size_t path_buf_size) +{ + struct stat qf_stat; + char qf_name[PATH_MAX] = {0}; + + BUG_ON(!mntpt); + BUG_ON(!path_buf); + BUG_ON(!path_buf_size); + + strncpy(path_buf, mntpt, path_buf_size); + strncat(path_buf, "/", 1); + strncat(path_buf, get_qf_name(qtype, fmt, qf_name), + path_buf_size - strlen(path_buf)); + + return path_buf; +} + +/* + * Set grace time if needed + */ +void update_grace_times(struct dquot *q) +{ + time_t now; + + time(&now); + if (q->dq_dqb.dqb_bsoftlimit && toqb(q->dq_dqb.dqb_curspace) > + q->dq_dqb.dqb_bsoftlimit) { + if (!q->dq_dqb.dqb_btime) + q->dq_dqb.dqb_btime = + now + q->dq_h->qh_info.dqi_bgrace; + } else { + q->dq_dqb.dqb_btime = 0; + } + + if (q->dq_dqb.dqb_isoftlimit && q->dq_dqb.dqb_curinodes > + q->dq_dqb.dqb_isoftlimit) { + if (!q->dq_dqb.dqb_itime) + q->dq_dqb.dqb_itime = + now + q->dq_h->qh_info.dqi_igrace; + } else { + q->dq_dqb.dqb_itime = 0; + } +} + +static int release_blocks_proc(ext2_filsys fs, blk64_t *blocknr, + e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *private EXT2FS_ATTR((unused))) +{ + blk64_t block; + + block = *blocknr; + ext2fs_block_alloc_stats2(fs, block, -1); + return 0; +} + +static int compute_num_blocks_proc(ext2_filsys fs, blk64_t *blocknr, + e2_blkcnt_t blockcnt EXT2FS_ATTR((unused)), + blk64_t ref_block EXT2FS_ATTR((unused)), + int ref_offset EXT2FS_ATTR((unused)), + void *private) +{ + blk64_t block; + blk64_t *num_blocks = private; + + *num_blocks += 1; + return 0; +} + +void truncate_quota_inode(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + int i; + + if (ext2fs_read_inode(fs, ino, &inode)) + return; + + inode.i_dtime = fs->now ? fs->now : time(0); + if (!ext2fs_inode_has_valid_blocks(&inode)) + return; + + ext2fs_block_iterate3(fs, ino, BLOCK_FLAG_READ_ONLY, NULL, + release_blocks_proc, NULL); + + memset(&inode, 0, sizeof(struct ext2_inode)); + ext2fs_write_inode(fs, ino, &inode); +} + +static ext2_off64_t compute_inode_size(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + blk64_t num_blocks = 0; + + ext2fs_block_iterate3(fs, ino, + BLOCK_FLAG_READ_ONLY, + NULL, + compute_num_blocks_proc, + &num_blocks); + return num_blocks * fs->blocksize; +} + +/* Functions to read/write quota file. */ +static unsigned int quota_write_nomount(struct quota_file *qf, loff_t offset, + void *buf, unsigned int size) +{ + ext2_file_t e2_file = qf->e2_file; + unsigned int bytes_written = 0; + errcode_t err; + + err = ext2fs_file_llseek(e2_file, offset, EXT2_SEEK_SET, NULL); + if (err) { + log_err("ext2fs_file_llseek failed: %ld", err); + return 0; + } + + err = ext2fs_file_write(e2_file, buf, size, &bytes_written); + if (err) { + log_err("ext2fs_file_write failed: %ld", err); + return 0; + } + + /* Correct inode.i_size is set in end_io. */ + return bytes_written; +} + +static unsigned int quota_read_nomount(struct quota_file *qf, loff_t offset, + void *buf, unsigned int size) +{ + ext2_file_t e2_file = qf->e2_file; + unsigned int bytes_read = 0; + errcode_t err; + + err = ext2fs_file_llseek(e2_file, offset, EXT2_SEEK_SET, NULL); + if (err) { + log_err("ext2fs_file_llseek failed: %ld", err); + return 0; + } + + err = ext2fs_file_read(e2_file, buf, size, &bytes_read); + if (err) { + log_err("ext2fs_file_read failed: %ld", err); + return 0; + } + + return bytes_read; +} + +/* + * Detect quota format and initialize quota IO + */ +struct quota_handle *init_io(ext2_filsys fs, const char *mntpt, int type, + int fmt, int flags) +{ + log_err("Not Implemented.", ""); + BUG_ON(1); + return NULL; +} + +static errcode_t init_new_quota_inode(ext2_filsys fs, ext2_ino_t ino) +{ + struct ext2_inode inode; + errcode_t err = 0; + + err = ext2fs_read_inode(fs, ino, &inode); + if (err) { + log_err("ex2fs_read_inode failed", ""); + return err; + } + + if (EXT2_I_SIZE(&inode)) + truncate_quota_inode(fs, ino); + + memset(&inode, 0, sizeof(struct ext2_inode)); + ext2fs_iblk_set(fs, &inode, 0); + inode.i_atime = inode.i_mtime = + inode.i_ctime = fs->now ? fs->now : time(0); + inode.i_links_count = 1; + inode.i_mode = LINUX_S_IFREG | 0600; + inode.i_flags |= EXT2_IMMUTABLE_FL; + if (fs->super->s_feature_incompat & + EXT3_FEATURE_INCOMPAT_EXTENTS) + inode.i_flags |= EXT4_EXTENTS_FL; + + err = ext2fs_write_new_inode(fs, ino, &inode); + if (err) { + log_err("ext2fs_write_new_inode failed: %ld", err); + return err; + } + return err; +} + +/* + * Create new quotafile of specified format on given filesystem + */ +int new_io(struct quota_handle *h, ext2_filsys fs, int type, int fmt) +{ + int fd = 0; + ext2_file_t e2_file; + const char *mnt_fsname; + char qf_name[PATH_MAX]; + int err; + struct ext2_inode inode; + unsigned long qf_inum; + struct stat st; + + if (fmt == -1) + fmt = QFMT_VFS_V1; + + h->qh_qf.fs = fs; + if (type == USRQUOTA) + qf_inum = EXT4_USR_QUOTA_INO; + else if (type == GRPQUOTA) + qf_inum = EXT4_GRP_QUOTA_INO; + else + BUG_ON(1); + err = ext2fs_read_bitmaps(fs); + if (err) + goto out_err; + + err = init_new_quota_inode(fs, qf_inum); + if (err) { + log_err("init_new_quota_inode failed", ""); + goto out_err; + } + h->qh_qf.ino = qf_inum; + h->e2fs_write = quota_write_nomount; + h->e2fs_read = quota_read_nomount; + + log_debug("Creating quota ino=%lu, type=%d", qf_inum, type); + err = ext2fs_file_open(fs, qf_inum, + EXT2_FILE_WRITE | EXT2_FILE_CREATE, &e2_file); + if (err) { + log_err("ext2fs_file_open failed: %d", err); + goto out_err; + } + h->qh_qf.e2_file = e2_file; + + h->qh_io_flags = 0; + h->qh_type = type; + h->qh_fmt = fmt; + memset(&h->qh_info, 0, sizeof(h->qh_info)); + h->qh_ops = "afile_ops_2; + + if (h->qh_ops->new_io && (h->qh_ops->new_io(h) < 0)) { + log_err("qh_ops->new_io failed", ""); + goto out_err1; + } + + return 0; + +out_err1: + ext2fs_file_close(e2_file); +out_err: + + if (qf_inum) + truncate_quota_inode(fs, qf_inum); + + return -1; +} + +/* + * Close quotafile and release handle + */ +int end_io(struct quota_handle *h) +{ + if (h->qh_io_flags & IOFL_INFODIRTY) { + if (h->qh_ops->write_info && h->qh_ops->write_info(h) < 0) + return -1; + h->qh_io_flags &= ~IOFL_INFODIRTY; + } + + if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0) + return -1; + if (h->qh_qf.e2_file) { + ext2fs_file_flush(h->qh_qf.e2_file); + ext2fs_file_set_size2(h->qh_qf.e2_file, + compute_inode_size(h->qh_qf.fs, h->qh_qf.ino)); + ext2fs_file_close(h->qh_qf.e2_file); + } + + return 0; +} + +/* + * Create empty quota structure + */ +struct dquot *get_empty_dquot(void) +{ + struct dquot *dquot = smalloc(sizeof(struct dquot)); + + memset(dquot, 0, sizeof(*dquot)); + dquot->dq_id = -1; + return dquot; +} diff --git a/lib/quota/quotaio.h b/lib/quota/quotaio.h new file mode 100644 index 00000000..fd39b55a --- /dev/null +++ b/lib/quota/quotaio.h @@ -0,0 +1,163 @@ +/** quotaio.h + * + * Header of IO operations for quota utilities + * Jan Kara <jack@suse.cz> + */ + +#ifndef GUARD_QUOTAIO_H +#define GUARD_QUOTAIO_H + +#include <limits.h> +#include <sys/types.h> +#include <sys/stat.h> + +#include "ext2fs/ext2fs.h" +#include "quota.h" +#include "dqblk_v2.h" + +/* + * Definitions for disk quotas imposed on the average user + * (big brother finally hits Linux). + * + * The following constants define the default amount of time given a user + * before the soft limits are treated as hard limits (usually resulting + * in an allocation failure). The timer is started when the user crosses + * their soft limit, it is reset when they go below their soft limit. + */ +#define MAX_IQ_TIME 604800 /* (7*24*60*60) 1 week */ +#define MAX_DQ_TIME 604800 /* (7*24*60*60) 1 week */ + +#define IOFL_QUOTAON 0x01 /* Is quota enabled in kernel? */ +#define IOFL_INFODIRTY 0x02 /* Did info change? */ +#define IOFL_RO 0x04 /* Just RO access? */ +#define IOFL_NFS_MIXED_PATHS 0x08 /* Should we trim leading slashes + from NFSv4 mountpoints? */ + +struct quotafile_ops; + +/* Generic information about quotafile */ +struct util_dqinfo { + time_t dqi_bgrace; /* Block grace time for given quotafile */ + time_t dqi_igrace; /* Inode grace time for given quotafile */ + union { + struct v2_mem_dqinfo v2_mdqi; + } u; /* Format specific info about quotafile */ +}; + +struct quota_file { + ext2_filsys fs; + ext2_ino_t ino; + ext2_file_t e2_file; +}; + +/* Structure for one opened quota file */ +struct quota_handle { + int qh_type; /* Type of quotafile */ + int qh_fmt; /* Quotafile format */ + int qh_io_flags; /* IO flags for file */ + struct quota_file qh_qf; + unsigned int (*e2fs_read)(struct quota_file *qf, loff_t offset, + void *buf, unsigned int size); + unsigned int (*e2fs_write)(struct quota_file *qf, loff_t offset, + void *buf, unsigned int size); + struct quotafile_ops *qh_ops; /* Operations on quotafile */ + struct util_dqinfo qh_info; /* Generic quotafile info */ +}; + +/* Statistics gathered from kernel */ +struct util_dqstats { + u_int32_t lookups; + u_int32_t drops; + u_int32_t reads; + u_int32_t writes; + u_int32_t cache_hits; + u_int32_t allocated_dquots; + u_int32_t free_dquots; + u_int32_t syncs; + u_int32_t version; +}; + +/* Utility quota block */ +struct util_dqblk { + qsize_t dqb_ihardlimit; + qsize_t dqb_isoftlimit; + qsize_t dqb_curinodes; + qsize_t dqb_bhardlimit; + qsize_t dqb_bsoftlimit; + qsize_t dqb_curspace; + time_t dqb_btime; + time_t dqb_itime; + union { + struct v2_mem_dqblk v2_mdqb; + } u; /* Format specific dquot information */ +}; + +/* Structure for one loaded quota */ +struct dquot { + struct dquot *dq_next; /* Pointer to next dquot in the list */ + qid_t dq_id; /* ID dquot belongs to */ + int dq_flags; /* Some flags for utils */ + struct quota_handle *dq_h; /* Handle of quotafile for this dquot */ + struct util_dqblk dq_dqb; /* Parsed data of dquot */ +}; + +/* Flags for commit function (have effect only when quota in kernel is + * turned on) */ +#define COMMIT_USAGE QIF_USAGE +#define COMMIT_LIMITS QIF_LIMITS +#define COMMIT_TIMES QIF_TIMES +#define COMMIT_ALL (COMMIT_USAGE | COMMIT_LIMITS | COMMIT_TIMES) + +/* Structure of quotafile operations */ +struct quotafile_ops { + /* Check whether quotafile is in our format */ + int (*check_file) (struct quota_handle *h, int type, int fmt); + /* Open quotafile */ + int (*init_io) (struct quota_handle *h); + /* Create new quotafile */ + int (*new_io) (struct quota_handle *h); + /* Write all changes and close quotafile */ + int (*end_io) (struct quota_handle *h); + /* Write info about quotafile */ + int (*write_info) (struct quota_handle *h); + /* Read dquot into memory */ + struct dquot *(*read_dquot) (struct quota_handle *h, qid_t id); + /* Write given dquot to disk */ + int (*commit_dquot) (struct dquot *dquot, int flag); + /* Scan quotafile and call callback on every structure */ + int (*scan_dquots) (struct quota_handle *h, + int (*process_dquot) (struct dquot *dquot, + char *dqname)); + /* Function to print format specific file information */ + int (*report) (struct quota_handle *h, int verbose); +}; + +/* This might go into a special header file but that sounds a bit silly... */ +extern struct quotafile_ops quotafile_ops_meta; + +static inline void mark_quotafile_info_dirty(struct quota_handle *h) +{ + h->qh_io_flags |= IOFL_INFODIRTY; +} + +#define QIO_ENABLED(h) ((h)->qh_io_flags & IOFL_QUOTAON) +#define QIO_RO(h) ((h)->qh_io_flags & IOFL_RO) + +/* Check quota format used on specified medium and initialize it */ +struct quota_handle *init_io(ext2_filsys fs, const char *mntpt, int type, + int fmt, int flags); + +/* Create new quotafile of specified format on given filesystem */ +int new_io(struct quota_handle *h, ext2_filsys fs, int type, int fmt); + +/* Close quotafile */ +int end_io(struct quota_handle *h); + +/* Get empty quota structure */ +struct dquot *get_empty_dquot(void); + +void truncate_quota_inode(ext2_filsys fs, ext2_ino_t ino); + +const char *type2name(int type); + +#endif /* GUARD_QUOTAIO_H */ diff --git a/lib/quota/quotaio_tree.c b/lib/quota/quotaio_tree.c new file mode 100644 index 00000000..48e58f6d --- /dev/null +++ b/lib/quota/quotaio_tree.c @@ -0,0 +1,574 @@ +/* + * Implementation of new quotafile format + * + * Jan Kara <jack@suse.cz> - sponsored by SuSE CR + */ + +#include <sys/types.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <asm/byteorder.h> + +#include "common.h" +#include "quotaio_tree.h" +#include "quotaio.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; + + err = h->e2fs_read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf, + QT_BLKSIZE); + if (err < 0) + log_fatal(2, "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, uint 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_fatal(2, "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 (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, 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, 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 = __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 qt_disk_dqdbheader *dh = (struct qt_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 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 = __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 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 = __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 qt_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 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); + } + + /* Block will be full? */ + if (__le16_to_cpu(dh->dqdh_entries) + 1 >= qtree_dqstr_in_blk(info)) + remove_free_dqentry(h, buf, blk); + + dh->dqdh_entries = __cpu_to_le16(__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_fatal(2, "find_free_dqentry(): Data block full but it " + "shouldn't.", ""); + + 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; + + log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth); + 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 = __le32_to_cpu(ref[get_index(dquot->dq_id, depth)]); + if (!newblk) + newson = 1; + if (depth == QT_TREEDEPTH - 1) { + if (newblk) + log_fatal(2, "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)] = __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) +{ + uint tmp = QT_TREEOFF; + + if (do_insert_tree(h, dquot, &tmp, 0) < 0) + log_fatal(2, "Cannot write quota (id %u): %s", + (uint) dquot->dq_id, strerror(errno)); +} + +/* Write dquot to file */ +void qtree_write_dquot(struct dquot *dquot) +{ + ssize_t ret; + 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); + char *ddquot = (char *)smalloc(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_fatal(2, "Quota write failed (id %u): %s", + (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) + log_fatal(2, "Quota structure has offset to other block (%u) " + "than it should (%u).", 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 = __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 << QT_BLKSIZE_BITS) - 1)), + 0, info->dqi_entry_size); + + /* First free entry? */ + if (__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, + 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[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)] = __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) +{ + 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)) + log_fatal(2, "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 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[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; + 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_fatal(2, + "Cannot read quota structure for id %u: %s", + dquot->dq_id, strerror(errno)); + } + info->dqi_ops->disk2mem_dqblk(dquot, 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 = __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)) { + 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) + log_fatal(2, "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, + type2name(h->qh_type)); +} + +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 = __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); + } + } else { + for (i = 0; i < QT_BLKSIZE >> 2; i++) + blk = __le32_to_cpu(ref[i]); + if (blk) { + 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; +} diff --git a/lib/quota/quotaio_tree.h b/lib/quota/quotaio_tree.h new file mode 100644 index 00000000..a23777d0 --- /dev/null +++ b/lib/quota/quotaio_tree.h @@ -0,0 +1,63 @@ +/* + * Definitions of structures for vfsv0 quota format + */ + +#ifndef _LINUX_QUOTA_TREE_H +#define _LINUX_QUOTA_TREE_H + +#include <sys/types.h> +#include "quota.h" + +#define QT_TREEOFF 1 /* Offset of tree in file in blocks */ +#define QT_TREEDEPTH 4 /* Depth of quota tree */ +#define QT_BLKSIZE_BITS 10 +#define QT_BLKSIZE (1 << QT_BLKSIZE_BITS) /* Size of block with quota + * structures */ + +/* + * Structure of header of block with quota structures. It is padded to 16 bytes + * so there will be space for exactly 21 quota-entries in a block + */ +struct qt_disk_dqdbheader { + u_int32_t dqdh_next_free; /* Number of next block with free + * entry */ + u_int32_t dqdh_prev_free; /* Number of previous block with free + * entry */ + u_int16_t dqdh_entries; /* Number of valid entries in block */ + u_int16_t dqdh_pad1; + u_int32_t dqdh_pad2; +} __attribute__ ((packed)); + +struct dquot; +struct quota_handle; + +/* Operations */ +struct qtree_fmt_operations { + /* Convert given entry from in memory format to disk one */ + void (*mem2disk_dqblk)(void *disk, struct dquot *dquot); + /* Convert given entry from disk format to in memory one */ + void (*disk2mem_dqblk)(struct dquot *dquot, void *disk); + /* Is this structure for given id? */ + int (*is_id)(void *disk, struct dquot *dquot); +}; + +/* Inmemory copy of version specific information */ +struct qtree_mem_dqinfo { + unsigned int dqi_blocks; /* # of blocks in quota file */ + unsigned int dqi_free_blk; /* First block in list of free blocks */ + unsigned int dqi_free_entry; /* First block with free entry */ + unsigned int dqi_entry_size; /* Size of quota entry in quota file */ + struct qtree_fmt_operations *dqi_ops; /* Operations for entry + * manipulation */ +}; + +void qtree_write_dquot(struct dquot *dquot); +struct dquot *qtree_read_dquot(struct quota_handle *h, qid_t id); +void qtree_delete_dquot(struct dquot *dquot); +int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk); +int qtree_scan_dquots(struct quota_handle *h, + int (*process_dquot) (struct dquot *, char *)); + +int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info); + +#endif /* _LINUX_QUOTAIO_TREE_H */ diff --git a/lib/quota/quotaio_v2.c b/lib/quota/quotaio_v2.c new file mode 100644 index 00000000..31197452 --- /dev/null +++ b/lib/quota/quotaio_v2.c @@ -0,0 +1,313 @@ +/* + * Implementation of new quotafile format + * + * Jan Kara <jack@suse.cz> - sponsored by SuSE CR + */ + +#include <sys/types.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> +#include <asm/byteorder.h> + +#include "common.h" +#include "quotaio_v2.h" +#include "dqblk_v2.h" +#include "quotaio.h" +#include "quotaio_tree.h" + +typedef char *dqbuf_t; + +static int v2_check_file(struct quota_handle *h, int type, int fmt); +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, int flags); +static int v2_scan_dquots(struct quota_handle *h, + int (*process_dquot) (struct dquot *dquot, + char *dqname)); +static int v2_report(struct quota_handle *h, int verbose); + +struct quotafile_ops quotafile_ops_2 = { +check_file: v2_check_file, +init_io: v2_init_io, +new_io: v2_new_io, +write_info: v2_write_info, +read_dquot: v2_read_dquot, +commit_dquot: v2_commit_dquot, +scan_dquots: v2_scan_dquots, +report: v2_report +}; + +#define getdqbuf() smalloc(V2_DQBLKSIZE) +#define freedqbuf(buf) free(buf) + +/* + * Copy dquot from disk to memory + */ +static void v2r1_disk2memdqblk(struct dquot *dquot, void *dp) +{ + struct util_dqblk *m = &dquot->dq_dqb; + struct v2r1_disk_dqblk *d = dp, empty; + + dquot->dq_id = __le32_to_cpu(d->dqb_id); + m->dqb_ihardlimit = __le64_to_cpu(d->dqb_ihardlimit); + m->dqb_isoftlimit = __le64_to_cpu(d->dqb_isoftlimit); + m->dqb_bhardlimit = __le64_to_cpu(d->dqb_bhardlimit); + m->dqb_bsoftlimit = __le64_to_cpu(d->dqb_bsoftlimit); + m->dqb_curinodes = __le64_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); + + memset(&empty, 0, sizeof(struct v2r1_disk_dqblk)); + empty.dqb_itime = __cpu_to_le64(1); + if (!memcmp(&empty, dp, sizeof(struct v2r1_disk_dqblk))) + m->dqb_itime = 0; +} + +/* + * Copy dquot from memory to disk + */ +static void v2r1_mem2diskdqblk(void *dp, struct dquot *dquot) +{ + struct util_dqblk *m = &dquot->dq_dqb; + struct v2r1_disk_dqblk *d = dp; + + d->dqb_ihardlimit = __cpu_to_le64(m->dqb_ihardlimit); + d->dqb_isoftlimit = __cpu_to_le64(m->dqb_isoftlimit); + d->dqb_bhardlimit = __cpu_to_le64(m->dqb_bhardlimit); + d->dqb_bsoftlimit = __cpu_to_le64(m->dqb_bsoftlimit); + d->dqb_curinodes = __cpu_to_le64(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); + d->dqb_id = __cpu_to_le32(dquot->dq_id); + if (qtree_entry_unused(&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree, dp)) + d->dqb_itime = __cpu_to_le64(1); +} + +static int v2r1_is_id(void *dp, struct dquot *dquot) +{ + struct v2r1_disk_dqblk *d = dp; + struct qtree_mem_dqinfo *info = + &dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree; + + if (qtree_entry_unused(info, dp)) + return 0; + return __le32_to_cpu(d->dqb_id) == dquot->dq_id; +} + +static struct qtree_fmt_operations v2r1_fmt_ops = { + .mem2disk_dqblk = v2r1_mem2diskdqblk, + .disk2mem_dqblk = v2r1_disk2memdqblk, + .is_id = v2r1_is_id, +}; + +/* + * 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_qtree.dqi_blocks = __le32_to_cpu(d->dqi_blocks); + m->u.v2_mdqi.dqi_qtree.dqi_free_blk = __le32_to_cpu(d->dqi_free_blk); + m->u.v2_mdqi.dqi_qtree.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_qtree.dqi_blocks); + d->dqi_free_blk = __cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_blk); + d->dqi_free_entry = + __cpu_to_le32(m->u.v2_mdqi.dqi_qtree.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; +} + +static int v2_read_header(struct quota_handle *h, struct v2_disk_dqheader *dqh) +{ + if (h->e2fs_read(&h->qh_qf, 0, dqh, sizeof(struct v2_disk_dqheader)) != + sizeof(struct v2_disk_dqheader)) + return 0; + + return 1; +} + +/* + * Check whether given quota file is in our format + */ +static int v2_check_file(struct quota_handle *h, int type, int fmt) +{ + struct v2_disk_dqheader dqh; + int file_magics[] = INITQMAGICS; + int known_versions[] = INIT_V2_VERSIONS; + int version; + + if (!v2_read_header(h, &dqh)) + return 0; + if (fmt == QFMT_VFS_V0) + version = 0; + else if (fmt == QFMT_VFS_V1) + version = 1; + else + return 0; + + if (__le32_to_cpu(dqh.dqh_magic) != file_magics[type]) { + if (__be32_to_cpu(dqh.dqh_magic) == file_magics[type]) + log_fatal(3, "Your quota file is stored in wrong " + "endianity.", ""); + return 0; + } + if (__le32_to_cpu(dqh.dqh_version) > known_versions[type]) + return 0; + if (version != __le32_to_cpu(dqh.dqh_version)) + return 0; + return 1; +} + +/* + * Open quotafile + */ +static int v2_init_io(struct quota_handle *h) +{ + log_err("Not Implemented.", ""); + BUG_ON(1); + return 0; +} + +/* + * Initialize new quotafile + */ +static int v2_new_io(struct quota_handle *h) +{ + int file_magics[] = INITQMAGICS; + struct v2_disk_dqheader ddqheader; + struct v2_disk_dqinfo ddqinfo; + int version = 1; + + BUG_ON(h->qh_fmt != QFMT_VFS_V1); + + /* Write basic quota header */ + ddqheader.dqh_magic = __cpu_to_le32(file_magics[h->qh_type]); + ddqheader.dqh_version = __cpu_to_le32(version); + if (h->e2fs_write(&h->qh_qf, 0, &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_qtree.dqi_blocks = QT_TREEOFF + 1; + h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_blk = 0; + h->qh_info.u.v2_mdqi.dqi_qtree.dqi_free_entry = 0; + h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size = + sizeof(struct v2r1_disk_dqblk); + h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r1_fmt_ops; + v2_mem2diskdqinfo(&ddqinfo, &h->qh_info); + if (h->e2fs_write(&h->qh_qf, V2_DQINFOOFF, &ddqinfo, + sizeof(ddqinfo)) != + sizeof(ddqinfo)) + return -1; + + return 0; +} + +/* + * Write information (grace times to file) + */ +static int v2_write_info(struct quota_handle *h) +{ + struct v2_disk_dqinfo ddqinfo; + + v2_mem2diskdqinfo(&ddqinfo, &h->qh_info); + if (h->e2fs_write(&h->qh_qf, V2_DQINFOOFF, &ddqinfo, sizeof(ddqinfo)) != + sizeof(ddqinfo)) + return -1; + + return 0; +} + +/* + * Read dquot (either from disk or from kernel) + * User can use errno to detect errstr when NULL is returned + */ +static struct dquot *v2_read_dquot(struct quota_handle *h, qid_t id) +{ + return qtree_read_dquot(h, id); +} + +/* + * 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 errstr. + */ +static int v2_commit_dquot(struct dquot *dquot, int flags) +{ + struct util_dqblk *b = &dquot->dq_dqb; + + if (!b->dqb_curspace && !b->dqb_curinodes && !b->dqb_bsoftlimit && + !b->dqb_isoftlimit && !b->dqb_bhardlimit && !b->dqb_ihardlimit) + qtree_delete_dquot(dquot); + else + qtree_write_dquot(dquot); + return 0; +} + +static int v2_scan_dquots(struct quota_handle *h, + int (*process_dquot) (struct dquot *, char *)) +{ + return qtree_scan_dquots(h, process_dquot); +} + +/* Report information about quotafile. + * TODO: Not used right now, but we should be able to use this when we add + * support to debugfs to read quota files. + */ +static int v2_report(struct quota_handle *h, int verbose) +{ + log_err("Not Implemented.", ""); + BUG_ON(1); + return 0; +} diff --git a/lib/quota/quotaio_v2.h b/lib/quota/quotaio_v2.h new file mode 100644 index 00000000..072e36fc --- /dev/null +++ b/lib/quota/quotaio_v2.h @@ -0,0 +1,103 @@ +/* + * + * Header file for disk format of new quotafile format + * + */ + +#ifndef GUARD_QUOTAIO_V2_H +#define GUARD_QUOTAIO_V2_H + +#include <sys/types.h> +#include "quota.h" + +/* Offset of info header in file */ +#define V2_DQINFOOFF sizeof(struct v2_disk_dqheader) +#define INIT_V2_VERSIONS { 1, 1} + +struct v2_disk_dqheader { + u_int32_t dqh_magic; /* Magic number identifying file */ + u_int32_t dqh_version; /* File version */ +} __attribute__ ((packed)); + +/* Flags for version specific files */ +#define V2_DQF_MASK 0x0000 /* Mask for all valid ondisk flags */ + +/* Header with type and version specific information */ +struct v2_disk_dqinfo { + u_int32_t dqi_bgrace; /* Time before block soft limit becomes + * hard limit */ + u_int32_t dqi_igrace; /* Time before inode soft limit becomes + * hard limit */ + u_int32_t dqi_flags; /* Flags for quotafile (DQF_*) */ + u_int32_t dqi_blocks; /* Number of blocks in file */ + u_int32_t dqi_free_blk; /* Number of first free block in the list */ + u_int32_t dqi_free_entry; /* Number of block with at least one + * free entry */ +} __attribute__ ((packed)); + +/* Structure of quota for one user on disk */ +struct v2r0_disk_dqblk { + u_int32_t dqb_id; /* id this quota applies to */ + u_int32_t dqb_ihardlimit; /* absolute limit on allocated inodes */ + u_int32_t dqb_isoftlimit; /* preferred inode limit */ + u_int32_t dqb_curinodes; /* current # allocated inodes */ + u_int32_t dqb_bhardlimit; /* absolute limit on disk space + * (in QUOTABLOCK_SIZE) */ + u_int32_t dqb_bsoftlimit; /* preferred limit on disk space + * (in QUOTABLOCK_SIZE) */ + u_int64_t dqb_curspace; /* current space occupied (in bytes) */ + u_int64_t dqb_btime; /* time limit for excessive disk use */ + u_int64_t dqb_itime; /* time limit for excessive inode use */ +} __attribute__ ((packed)); + +struct v2r1_disk_dqblk { + u_int32_t dqb_id; /* id this quota applies to */ + u_int32_t dqb_pad; + u_int64_t dqb_ihardlimit; /* absolute limit on allocated inodes */ + u_int64_t dqb_isoftlimit; /* preferred inode limit */ + u_int64_t dqb_curinodes; /* current # allocated inodes */ + u_int64_t dqb_bhardlimit; /* absolute limit on disk space + * (in QUOTABLOCK_SIZE) */ + u_int64_t dqb_bsoftlimit; /* preferred limit on disk space + * (in QUOTABLOCK_SIZE) */ + u_int64_t dqb_curspace; /* current space occupied (in bytes) */ + u_int64_t dqb_btime; /* time limit for excessive disk use */ + u_int64_t dqb_itime; /* time limit for excessive inode use */ +} __attribute__ ((packed)); + +/* Structure of quota for communication with kernel */ +struct v2_kern_dqblk { + unsigned int dqb_ihardlimit; + unsigned int dqb_isoftlimit; + unsigned int dqb_curinodes; + unsigned int dqb_bhardlimit; + unsigned int dqb_bsoftlimit; + qsize_t dqb_curspace; + time_t dqb_btime; + time_t dqb_itime; +}; + +/* Structure of quotafile info for communication with kernel (obsolete) */ +struct v2_kern_dqinfo { + unsigned int dqi_bgrace; + unsigned int dqi_igrace; + unsigned int dqi_flags; + unsigned int dqi_blocks; + unsigned int dqi_free_blk; + unsigned int dqi_free_entry; +}; + +/* Structure with gathered statistics from kernel */ +struct v2_dqstats { + u_int32_t lookups; + u_int32_t drops; + u_int32_t reads; + u_int32_t writes; + u_int32_t cache_hits; + u_int32_t allocated_dquots; + u_int32_t free_dquots; + u_int32_t syncs; + u_int32_t version; +}; + +#endif |