summaryrefslogtreecommitdiffstats
path: root/lib/support
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 09:25:10 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 09:25:10 +0000
commit5dced3d1b3deca80e01415a2e35dc7972dcbfae7 (patch)
tree6a403684e0978f0287d7f0ec0e5aab1fd31a59e1 /lib/support
parentInitial commit. (diff)
downloade2fsprogs-5dced3d1b3deca80e01415a2e35dc7972dcbfae7.tar.xz
e2fsprogs-5dced3d1b3deca80e01415a2e35dc7972dcbfae7.zip
Adding upstream version 1.47.0.upstream/1.47.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/support')
-rw-r--r--lib/support/Android.bp78
-rw-r--r--lib/support/Makefile.in184
-rw-r--r--lib/support/argv_parse.c168
-rw-r--r--lib/support/argv_parse.h43
-rw-r--r--lib/support/common.h36
-rw-r--r--lib/support/cstring.c162
-rw-r--r--lib/support/cstring.h6
-rw-r--r--lib/support/devname.c65
-rw-r--r--lib/support/devname.h19
-rw-r--r--lib/support/dict.c1542
-rw-r--r--lib/support/dict.h147
-rw-r--r--lib/support/dqblk_v2.h31
-rw-r--r--lib/support/mkquota.c707
-rw-r--r--lib/support/nls-enable.h21
-rw-r--r--lib/support/parse_qtype.c90
-rw-r--r--lib/support/plausible.c287
-rw-r--r--lib/support/plausible.h29
-rw-r--r--lib/support/print_fs_flags.c75
-rw-r--r--lib/support/print_fs_flags.h5
-rw-r--r--lib/support/prof_err.et66
-rw-r--r--lib/support/profile.c1910
-rw-r--r--lib/support/profile.h107
-rw-r--r--lib/support/profile_helpers.c317
-rw-r--r--lib/support/profile_helpers.h28
-rw-r--r--lib/support/quotaio.c412
-rw-r--r--lib/support/quotaio.h268
-rw-r--r--lib/support/quotaio_tree.c687
-rw-r--r--lib/support/quotaio_tree.h64
-rw-r--r--lib/support/quotaio_v2.c389
-rw-r--r--lib/support/quotaio_v2.h69
-rw-r--r--lib/support/sort_r.h325
31 files changed, 8337 insertions, 0 deletions
diff --git a/lib/support/Android.bp b/lib/support/Android.bp
new file mode 100644
index 0000000..af9b28d
--- /dev/null
+++ b/lib/support/Android.bp
@@ -0,0 +1,78 @@
+// Copyright 2017 The Android Open Source Project
+
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "external_e2fsprogs_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-GPL
+ // SPDX-license-identifier-MIT
+ default_applicable_licenses: ["external_e2fsprogs_license"],
+}
+
+cc_library {
+ name: "libext2_quota",
+ host_supported: true,
+ ramdisk_available: true,
+ vendor_ramdisk_available: true,
+ recovery_available: true,
+ unique_host_soname: true,
+ defaults: ["e2fsprogs-defaults"],
+ srcs: [
+ "devname.c",
+ "dict.c",
+ "mkquota.c",
+ "parse_qtype.c",
+ "plausible.c",
+ "profile.c",
+ "profile_helpers.c",
+ "prof_err.c",
+ "quotaio.c",
+ "quotaio_tree.c",
+ "quotaio_v2.c",
+ ],
+ shared_libs: [
+ "libext2fs",
+ "libext2_blkid",
+ "libext2_com_err",
+ ],
+
+ target: {
+ windows: {
+ enabled: true,
+ },
+ },
+
+ header_libs: ["libext2-headers"],
+ export_include_dirs: ["."],
+ export_header_lib_headers: ["libext2-headers"],
+}
+
+cc_library_shared {
+ name: "libext2_profile",
+ host_supported: true,
+ unique_host_soname: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: [
+ "prof_err.c",
+ "profile.c",
+ ],
+ shared_libs: ["libext2_com_err"],
+
+ header_libs: ["libext2-headers"],
+ export_include_dirs: ["."],
+ export_header_lib_headers: ["libext2-headers"],
+}
+
+cc_library {
+ name: "libext2_support",
+ host_supported: true,
+ defaults: ["e2fsprogs-defaults"],
+
+ srcs: [
+ "cstring.c",
+ ],
+ header_libs: ["libext2-headers"],
+ export_include_dirs: ["."],
+}
diff --git a/lib/support/Makefile.in b/lib/support/Makefile.in
new file mode 100644
index 0000000..b622909
--- /dev/null
+++ b/lib/support/Makefile.in
@@ -0,0 +1,184 @@
+# Makefile for e2fsprog's internal support
+#
+
+srcdir = @srcdir@
+top_srcdir = @top_srcdir@
+VPATH = @srcdir@
+top_builddir = ../..
+my_dir = lib/support
+INSTALL = @INSTALL@
+MKDIR_P = @MKDIR_P@
+
+@MCONFIG@
+
+all::
+
+OBJS= cstring.o \
+ mkquota.o \
+ plausible.o \
+ profile.o \
+ parse_qtype.o \
+ print_fs_flags.o \
+ profile_helpers.o \
+ prof_err.o \
+ quotaio.o \
+ quotaio_v2.o \
+ quotaio_tree.o \
+ dict.o \
+ devname.o
+
+SRCS= $(srcdir)/argv_parse.c \
+ $(srcdir)/cstring.c \
+ $(srcdir)/mkquota.c \
+ $(srcdir)/parse_qtype.c \
+ $(srcdir)/plausible.c \
+ $(srcdir)/print_fs_flags.c \
+ $(srcdir)/profile.c \
+ $(srcdir)/profile_helpers.c \
+ prof_err.c \
+ $(srcdir)/quotaio.c \
+ $(srcdir)/quotaio_tree.c \
+ $(srcdir)/quotaio_v2.c \
+ $(srcdir)/dict.c \
+ $(srcdir)/devname.c
+
+LIBRARY= libsupport
+LIBDIR= support
+
+@MAKEFILE_LIBRARY@
+@MAKEFILE_PROFILE@
+
+COMPILE_ET= _ET_DIR_OVERRIDE=$(srcdir)/../et ../et/compile_et
+
+.c.o:
+ $(E) " CC $<"
+ $(Q) $(CC) $(ALL_CFLAGS) -c $< -o $@
+ $(Q) $(CHECK_CMD) $(ALL_CFLAGS) $<
+ $(Q) $(CPPCHECK_CMD) $(CPPFLAGS) $<
+@PROFILE_CMT@ $(Q) $(CC) $(ALL_CFLAGS) -g -pg -o profiled/$*.o -c $<
+
+installdirs::
+
+install:: all
+
+uninstall::
+
+prof_err.c prof_err.h: prof_err.et
+ $(E) " COMPILE_ET prof_err.et"
+ $(Q) $(COMPILE_ET) $(srcdir)/prof_err.et
+
+test_profile: $(srcdir)/profile.c profile_helpers.o argv_parse.o \
+ prof_err.o profile.h $(DEPSTATIC_LIBCOM_ERR)
+ $(E) " LD $@"
+ $(Q) $(CC) -o test_profile -DDEBUG_PROGRAM $(srcdir)/profile.c prof_err.o \
+ profile_helpers.o argv_parse.o $(STATIC_LIBCOM_ERR) \
+ $(ALL_CFLAGS)
+
+test_cstring: $(srcdir)/cstring.c
+ $(E) " CC $@"
+ $(Q) $(CC) -o test_cstring -DDEBUG_PROGRAM $(srcdir)/cstring.c \
+ $(ALL_CFLAGS)
+
+clean::
+ $(RM) -f \#* *.s *.o *.a *~ *.bak core profiled/* \
+ ../libsupport.a ../libsupport_p.a $(SMANPAGES) \
+ prof_err.c prof_err.h test_profile test_cstring
+
+#fullcheck check:: tst_uuid
+# LD_LIBRARY_PATH=$(LIB) DYLD_LIBRARY_PATH=$(LIB) ./tst_uuid
+
+mostlyclean:: clean
+distclean:: clean
+ $(RM) -f .depend Makefile \
+ $(srcdir)/TAGS $(srcdir)/Makefile.in.old
+
+#
+# Hack to parallel makes recognize dependencies correctly.
+#
+../../lib/libsupport.a: libsupport.a
+../../lib/libsupport.so: image
+../../lib/libsupport.dylib: image
+
+$(OBJS):
+
+# +++ Dependency line eater +++
+#
+# Makefile dependencies follow. This must be the last section in
+# the Makefile.in file
+#
+argv_parse.o: $(srcdir)/argv_parse.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/argv_parse.h
+cstring.o: $(srcdir)/cstring.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/cstring.h
+mkquota.o: $(srcdir)/mkquota.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(top_srcdir)/lib/e2p/e2p.h \
+ $(srcdir)/quotaio.h $(srcdir)/dqblk_v2.h $(srcdir)/quotaio_tree.h \
+ $(srcdir)/quotaio_v2.h $(srcdir)/common.h $(srcdir)/dict.h
+parse_qtype.o: $(srcdir)/parse_qtype.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/quotaio.h \
+ $(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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/dqblk_v2.h \
+ $(srcdir)/quotaio_tree.h
+plausible.o: $(srcdir)/plausible.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/plausible.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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/nls-enable.h
+print_fs_flags.o: $(srcdir)/print_fs_flags.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h
+profile.o: $(srcdir)/profile.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
+ $(srcdir)/profile.h prof_err.h
+profile_helpers.o: $(srcdir)/profile_helpers.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(top_srcdir)/lib/et/com_err.h \
+ $(srcdir)/profile.h $(srcdir)/profile_helpers.h prof_err.h
+prof_err.o: prof_err.c
+quotaio.o: $(srcdir)/quotaio.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/common.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/quotaio.h \
+ $(top_srcdir)/lib/ext2fs/ext2_fs.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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/dqblk_v2.h \
+ $(srcdir)/quotaio_tree.h
+quotaio_tree.o: $(srcdir)/quotaio_tree.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/common.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/quotaio_tree.h \
+ $(srcdir)/quotaio.h $(top_srcdir)/lib/ext2fs/ext2_fs.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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/dqblk_v2.h
+quotaio_v2.o: $(srcdir)/quotaio_v2.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/common.h \
+ $(top_builddir)/lib/ext2fs/ext2_types.h $(srcdir)/quotaio_v2.h \
+ $(srcdir)/quotaio.h $(top_srcdir)/lib/ext2fs/ext2_fs.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/hashmap.h \
+ $(top_srcdir)/lib/ext2fs/bitops.h $(srcdir)/dqblk_v2.h \
+ $(srcdir)/quotaio_tree.h
+dict.o: $(srcdir)/dict.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/dict.h
+devname.o: $(srcdir)/devname.c $(top_builddir)/lib/config.h \
+ $(top_builddir)/lib/dirpaths.h $(srcdir)/devname.h $(srcdir)/nls-enable.h
diff --git a/lib/support/argv_parse.c b/lib/support/argv_parse.c
new file mode 100644
index 0000000..1f50f9e
--- /dev/null
+++ b/lib/support/argv_parse.c
@@ -0,0 +1,168 @@
+/*
+ * argv_parse.c --- utility function for parsing a string into a
+ * argc, argv array.
+ *
+ * This file defines a function argv_parse() which parsing a
+ * passed-in string, handling double quotes and backslashes, and
+ * creates an allocated argv vector which can be freed using the
+ * argv_free() function.
+ *
+ * See argv_parse.h for the formal definition of the functions.
+ *
+ * Copyright 1999 by Theodore Ts'o.
+ *
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose with or without fee is hereby granted, provided that
+ * the above copyright notice and this permission notice appear in all
+ * copies. THE SOFTWARE IS PROVIDED "AS IS" AND THEODORE TS'O (THE
+ * AUTHOR) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. (Isn't
+ * it sick that the U.S. culture of lawsuit-happy lawyers requires
+ * this kind of disclaimer?)
+ *
+ * Version 1.1, modified 2/27/1999
+ */
+
+#include "config.h"
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <ctype.h>
+#include <string.h>
+#include "argv_parse.h"
+
+#define STATE_WHITESPACE 1
+#define STATE_TOKEN 2
+#define STATE_QUOTED 3
+
+/*
+ * Returns 0 on success, -1 on failure.
+ */
+int argv_parse(char *in_buf, int *ret_argc, char ***ret_argv)
+{
+ int argc = 0, max_argc = 0;
+ char **argv, **new_argv, *buf, ch;
+ char *cp = 0, *outcp = 0;
+ int state = STATE_WHITESPACE;
+
+ buf = malloc(strlen(in_buf)+1);
+ if (!buf)
+ return -1;
+
+ max_argc = 0; argc = 0; argv = 0;
+ outcp = buf;
+ for (cp = in_buf; (ch = *cp); cp++) {
+ if (state == STATE_WHITESPACE) {
+ if (isspace((int) ch))
+ continue;
+ /* Not whitespace, so start a new token */
+ state = STATE_TOKEN;
+ if (argc >= max_argc) {
+ max_argc += 3;
+ new_argv = realloc(argv,
+ (max_argc+1)*sizeof(char *));
+ if (!new_argv) {
+ free(argv);
+ free(buf);
+ return -1;
+ }
+ argv = new_argv;
+ }
+ argv[argc++] = outcp;
+ }
+ if (state == STATE_QUOTED) {
+ if (ch == '"')
+ state = STATE_TOKEN;
+ else
+ *outcp++ = ch;
+ continue;
+ }
+ /* Must be processing characters in a word */
+ if (isspace((int) ch)) {
+ /*
+ * Terminate the current word and start
+ * looking for the beginning of the next word.
+ */
+ *outcp++ = 0;
+ state = STATE_WHITESPACE;
+ continue;
+ }
+ if (ch == '"') {
+ state = STATE_QUOTED;
+ continue;
+ }
+ if (ch == '\\') {
+ ch = *++cp;
+ switch (ch) {
+ case '\0':
+ ch = '\\'; cp--; break;
+ case 'n':
+ ch = '\n'; break;
+ case 't':
+ ch = '\t'; break;
+ case 'b':
+ ch = '\b'; break;
+ }
+ }
+ *outcp++ = ch;
+ }
+ if (state != STATE_WHITESPACE)
+ *outcp++ = '\0';
+ if (argv == 0) {
+ argv = malloc(sizeof(char *));
+ free(buf);
+ if (!argv)
+ return -1;
+ }
+ argv[argc] = 0;
+ if (ret_argc)
+ *ret_argc = argc;
+ if (ret_argv)
+ *ret_argv = argv;
+ return 0;
+}
+
+void argv_free(char **argv)
+{
+ free(*argv);
+ free(argv);
+}
+
+#ifdef DEBUG
+/*
+ * For debugging
+ */
+
+#include <stdio.h>
+
+int main(int argc, char **argv)
+{
+ int ac, ret;
+ char **av, **cpp;
+ char buf[256];
+
+ while (!feof(stdin)) {
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ break;
+ ret = argv_parse(buf, &ac, &av);
+ if (ret != 0) {
+ printf("Argv_parse returned %d!\n", ret);
+ continue;
+ }
+ printf("Argv_parse returned %d arguments...\n", ac);
+ for (cpp = av; *cpp; cpp++) {
+ if (cpp != av)
+ printf(", ");
+ printf("'%s'", *cpp);
+ }
+ printf("\n");
+ argv_free(av);
+ }
+ exit(0);
+}
+#endif /* DEBUG */
diff --git a/lib/support/argv_parse.h b/lib/support/argv_parse.h
new file mode 100644
index 0000000..86f4564
--- /dev/null
+++ b/lib/support/argv_parse.h
@@ -0,0 +1,43 @@
+/*
+ * argv_parse.h --- header file for the argv parser.
+ *
+ * This file defines the interface for the functions argv_parse() and
+ * argv_free().
+ *
+ ***********************************************************************
+ * int argv_parse(char *in_buf, int *ret_argc, char ***ret_argv)
+ *
+ * This function takes as its first argument a string which it will
+ * parse into an argv argument vector, with each white-space separated
+ * word placed into its own slot in the argv. This function handles
+ * double quotes and backslashes so that the parsed words can contain
+ * special characters. The count of the number words found in the
+ * parsed string, as well as the argument vector, are returned into
+ * ret_argc and ret_argv, respectively.
+ ***********************************************************************
+ * extern void argv_free(char **argv);
+ *
+ * This function frees the argument vector created by argv_parse().
+ ***********************************************************************
+ *
+ * Copyright 1999 by Theodore Ts'o.
+ *
+ * Permission to use, copy, modify, and distribute this software for
+ * any purpose with or without fee is hereby granted, provided that
+ * the above copyright notice and this permission notice appear in all
+ * copies. THE SOFTWARE IS PROVIDED "AS IS" AND THEODORE TS'O (THE
+ * AUTHOR) DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
+ * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS.
+ * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
+ * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
+ * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR
+ * IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. (Isn't
+ * it sick that the U.S. culture of lawsuit-happy lawyers requires
+ * this kind of disclaimer?)
+ *
+ * Version 1.1, modified 2/27/1999
+ */
+
+extern int argv_parse(char *in_buf, int *ret_argc, char ***ret_argv);
+extern void argv_free(char **argv);
diff --git a/lib/support/common.h b/lib/support/common.h
new file mode 100644
index 0000000..00a9375
--- /dev/null
+++ b/lib/support/common.h
@@ -0,0 +1,36 @@
+/*
+ *
+ * Various things common for all utilities
+ *
+ */
+
+#ifndef __QUOTA_COMMON_H__
+#define __QUOTA_COMMON_H__
+
+#if EXT2_FLAT_INCLUDES
+#include "e2_types.h"
+#else
+#include <ext2fs/ext2_types.h>
+#endif /* EXT2_FLAT_INCLUDES */
+
+/* #define DEBUG_QUOTA 1 */
+
+#ifndef __attribute__
+# if !defined __GNUC__ || __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 8) || __STRICT_ANSI__
+# define __attribute__(x)
+# endif
+#endif
+
+#define log_err(format, arg ...) \
+ fprintf(stderr, "[ERROR] %s:%d:%s: " format "\n", \
+ __FILE__, __LINE__, __func__, ## arg)
+
+#ifdef DEBUG_QUOTA
+# define log_debug(format, arg ...) \
+ fprintf(stderr, "[DEBUG] %s:%d:%s: " format "\n", \
+ __FILE__, __LINE__, __func__, ## arg)
+#else
+# define log_debug(...)
+#endif
+
+#endif /* __QUOTA_COMMON_H__ */
diff --git a/lib/support/cstring.c b/lib/support/cstring.c
new file mode 100644
index 0000000..57f4522
--- /dev/null
+++ b/lib/support/cstring.c
@@ -0,0 +1,162 @@
+/*
+ * cstring.c -- parse and print strings using the C escape sequences
+ */
+
+#include "config.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <ctype.h>
+#ifdef HAVE_GETOPT_H
+#include <getopt.h>
+#endif
+#include <string.h>
+
+#include "cstring.h"
+
+int parse_c_string(char *str)
+{
+ char *to, *from, ch;
+ int v;
+
+ to = from = str;
+
+ for (to = from = (char *) str;
+ *from && *from != '"'; to++, from++) {
+ if (*from == '\\') {
+ ch = *(++from);
+ switch (ch) {
+ case 'a':
+ *to = '\a';
+ break;
+ case 'b':
+ *to = '\b';
+ break;
+ case 'f':
+ *to = '\f';
+ break;
+ case 'n':
+ *to = '\n';
+ break;
+ case 't':
+ *to = '\t';
+ break;
+ case 'v':
+ *to = '\v';
+ break;
+ case 'x':
+ ch = *(from + 1);
+ if (ch >= 'a' && ch <= 'f')
+ ch = ch - 'a' + 'A';
+ if (ch >= '0' && ch <= '9')
+ v = ch - '0';
+ else if (ch >= 'A' && ch <= 'F')
+ v = ch + 10 - 'A';
+ else {
+ *to = 'x';
+ break;
+ }
+ from++;
+ ch = *(from + 1);
+ if (ch >= 'a' && ch <= 'f')
+ ch = ch - 'a' + 'A';
+ if (ch >= '0' && ch <= '9')
+ v = (v * 16) + (ch - '0');
+ else if (ch >= 'A' && ch <= 'F')
+ v = (v * 16) + (ch + 10 - 'A');
+ else {
+ *to = 'x';
+ from--;
+ break;
+ }
+ from++;
+ *to = v;
+ break;
+ default:
+ if (ch >= '0' && ch <= '9') {
+ v = ch - '0';
+ ch = *(from + 1);
+ if (ch >= '0' && ch <= '9') {
+ from++;
+ v = (8 * v) + (ch - '0');
+ ch = *(from + 1);
+ if (ch >= '0' && ch <= '9') {
+ from++;
+ v = (8 * v) + (ch - '0');
+ }
+ }
+ ch = v;
+ }
+ *to = ch;
+ }
+ continue;
+ }
+ *to = *from;
+ }
+ *to = '\0';
+ return to - (char *) str;
+}
+
+void print_c_string(FILE *f, const char *cp, int len)
+{
+ unsigned char ch;
+
+ if (len < 0)
+ len = strlen(cp);
+
+ while (len--) {
+ ch = *cp++;
+ if (ch == '\a')
+ fputs("\\a", f);
+ else if (ch == '\b')
+ fputs("\\b", f);
+ else if (ch == '\f')
+ fputs("\\f", f);
+ else if (ch == '\n')
+ fputs("\\n", f);
+ else if (ch == '\t')
+ fputs("\\t", f);
+ else if (ch == '\v')
+ fputs("\\v", f);
+ else if (ch == '\\')
+ fputs("\\\\", f);
+ else if (ch == '\'')
+ fputs("\\\'", f);
+ else if (ch == '\"')
+ fputs("\\\"", f);
+ else if ((ch < 32) || (ch > 126))
+ fprintf(f, "\\%03o", ch);
+ else
+ fputc(ch, f);
+ }
+}
+
+#ifdef DEBUG_PROGRAM
+int main(int argc, char **argv)
+{
+ char buf[4096];
+ int c, raw = 0;
+
+ while ((c = getopt(argc, argv, "r")) != EOF) {
+ switch (c) {
+ case 'r':
+ raw++;
+ break;
+ default:
+ fprintf(stderr, "Usage: %s [-r]\n", argv[0]);
+ exit(1);
+ }
+ }
+
+ while (!feof(stdin)) {
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ break;
+ c = parse_c_string(buf);
+ if (raw)
+ fputs(buf, stdout);
+ else {
+ print_c_string(stdout, buf, c);
+ printf(" <%d>\n", c);
+ }
+ }
+}
+#endif
diff --git a/lib/support/cstring.h b/lib/support/cstring.h
new file mode 100644
index 0000000..7f80f41
--- /dev/null
+++ b/lib/support/cstring.h
@@ -0,0 +1,6 @@
+/*
+ * cstring.h -- header file for C string parse/print utilities
+ */
+
+extern int parse_c_string(char *str);
+extern void print_c_string(FILE *f, const char *cp, int len);
diff --git a/lib/support/devname.c b/lib/support/devname.c
new file mode 100644
index 0000000..e0306dd
--- /dev/null
+++ b/lib/support/devname.c
@@ -0,0 +1,65 @@
+/*
+ * devname.c --- Support function to translate a user provided string
+ * identifying a device to an actual device path
+ *
+ * Copyright (C) 2022 Red Hat, Inc., Lukas Czerner <lczerner@redhat.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include <unistd.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "config.h"
+#include "devname.h"
+#include "nls-enable.h"
+
+/*
+ * blkid_get_devname() is primarily intended for parsing "NAME=value"
+ * tokens. It will return the device matching the specified token, NULL if
+ * nothing is found, or copy of the string if it's not in "NAME=value"
+ * format.
+ * get_devname() takes the same parameters and works the same way as
+ * blkid_get_devname() except it can handle '=' in the file name.
+ */
+char *get_devname(blkid_cache cache, const char *token, const char *value)
+{
+ int is_file = 0;
+ char *ret = NULL;
+
+ if (!token)
+ goto out;
+
+ if (value) {
+ ret = blkid_get_devname(cache, token, value);
+ goto out;
+ }
+
+ if (access(token, F_OK) == 0)
+ is_file = 1;
+
+ ret = blkid_get_devname(cache, token, NULL);
+ if (ret) {
+ /*
+ * In case of collision prefer the result from
+ * blkid_get_devname() to avoid a file masking file system with
+ * existing tag.
+ */
+ if (is_file && (strcmp(ret, token) != 0)) {
+ fprintf(stderr,
+ _("Collision found: '%s' refers to both '%s' "
+ "and a file '%s'. Using '%s'!\n"),
+ token, ret, token, ret);
+ }
+ goto out;
+ }
+
+ if (is_file)
+ ret = strdup(token);
+out:
+ return ret;
+}
diff --git a/lib/support/devname.h b/lib/support/devname.h
new file mode 100644
index 0000000..cc19561
--- /dev/null
+++ b/lib/support/devname.h
@@ -0,0 +1,19 @@
+/*
+ * devname.c --- Figure out if a pathname is ext* or something else.
+ *
+ * Copyright (C) 2022 Red Hat, Inc., Lukas Czerner <lczerner@redhat.com>
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef DEVNAME_H_
+#define DEVNAME_H_
+
+#include "blkid/blkid.h"
+
+char *get_devname(blkid_cache cache, const char *token, const char *value);
+
+#endif /* DEVNAME_H_ */
diff --git a/lib/support/dict.c b/lib/support/dict.c
new file mode 100644
index 0000000..93fdd0b
--- /dev/null
+++ b/lib/support/dict.c
@@ -0,0 +1,1542 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ * $Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz Exp $
+ * $Name: kazlib_1_20 $
+ * The work has been modified.
+ */
+
+#define DICT_NODEBUG
+
+#ifdef __GNUC__
+#define EXT2FS_ATTR(x) __attribute__(x)
+#else
+#define EXT2FS_ATTR(x)
+#endif
+
+#include "config.h"
+#include <stdlib.h>
+#include <stddef.h>
+#ifdef DICT_NODEBUG
+#define dict_assert(x)
+#else
+#include <assert.h>
+#define dict_assert(x) assert(x)
+#endif
+#define DICT_IMPLEMENTATION
+#include "dict.h"
+
+#ifdef KAZLIB_RCSID
+static const char rcsid[] = "$Id: dict.c,v 1.40.2.7 2000/11/13 01:36:44 kaz Exp $";
+#endif
+
+/*
+ * These macros provide short convenient names for structure members,
+ * which are embellished with dict_ prefixes so that they are
+ * properly confined to the documented namespace. It's legal for a
+ * program which uses dict to define, for instance, a macro called ``parent''.
+ * Such a macro would interfere with the dnode_t struct definition.
+ * In general, highly portable and reusable C modules which expose their
+ * structures need to confine structure member names to well-defined spaces.
+ * The resulting identifiers aren't necessarily convenient to use, nor
+ * readable, in the implementation, however!
+ */
+
+#define left dict_left
+#define right dict_right
+#define parent dict_parent
+#define color dict_color
+#define key dict_key
+#define data dict_data
+
+#define nilnode dict_nilnode
+#define nodecount dict_nodecount
+#define maxcount dict_maxcount
+#define compare dict_compare
+#define allocnode dict_allocnode
+#define freenode dict_freenode
+#define context dict_context
+#define dupes dict_dupes
+
+#define dictptr dict_dictptr
+
+#define dict_root(D) ((D)->nilnode.left)
+#define dict_nil(D) (&(D)->nilnode)
+#define DICT_DEPTH_MAX 64
+
+static dnode_t *dnode_alloc(void *context);
+static void dnode_free(dnode_t *node, void *context);
+
+/*
+ * Perform a ``left rotation'' adjustment on the tree. The given node P and
+ * its right child C are rearranged so that the P instead becomes the left
+ * child of C. The left subtree of C is inherited as the new right subtree
+ * for P. The ordering of the keys within the tree is thus preserved.
+ */
+
+static void rotate_left(dnode_t *upper)
+{
+ dnode_t *lower, *lowleft, *upparent;
+
+ lower = upper->right;
+ upper->right = lowleft = lower->left;
+ lowleft->parent = upper;
+
+ lower->parent = upparent = upper->parent;
+
+ /* don't need to check for root node here because root->parent is
+ the sentinel nil node, and root->parent->left points back to root */
+
+ if (upper == upparent->left) {
+ upparent->left = lower;
+ } else {
+ dict_assert (upper == upparent->right);
+ upparent->right = lower;
+ }
+
+ lower->left = upper;
+ upper->parent = lower;
+}
+
+/*
+ * This operation is the ``mirror'' image of rotate_left. It is
+ * the same procedure, but with left and right interchanged.
+ */
+
+static void rotate_right(dnode_t *upper)
+{
+ dnode_t *lower, *lowright, *upparent;
+
+ lower = upper->left;
+ upper->left = lowright = lower->right;
+ lowright->parent = upper;
+
+ lower->parent = upparent = upper->parent;
+
+ if (upper == upparent->right) {
+ upparent->right = lower;
+ } else {
+ dict_assert (upper == upparent->left);
+ upparent->left = lower;
+ }
+
+ lower->right = upper;
+ upper->parent = lower;
+}
+
+/*
+ * Do a postorder traversal of the tree rooted at the specified
+ * node and free everything under it. Used by dict_free().
+ */
+
+static void free_nodes(dict_t *dict, dnode_t *node, dnode_t *nil)
+{
+ if (node == nil)
+ return;
+ free_nodes(dict, node->left, nil);
+ free_nodes(dict, node->right, nil);
+ dict->freenode(node, dict->context);
+}
+
+/*
+ * This procedure performs a verification that the given subtree is a binary
+ * search tree. It performs an inorder traversal of the tree using the
+ * dict_next() successor function, verifying that the key of each node is
+ * strictly lower than that of its successor, if duplicates are not allowed,
+ * or lower or equal if duplicates are allowed. This function is used for
+ * debugging purposes.
+ */
+#ifndef DICT_NODEBUG
+static int verify_bintree(dict_t *dict)
+{
+ dnode_t *first, *next;
+
+ first = dict_first(dict);
+
+ if (dict->dupes) {
+ while (first && (next = dict_next(dict, first))) {
+ if (dict->compare(first->key, next->key) > 0)
+ return 0;
+ first = next;
+ }
+ } else {
+ while (first && (next = dict_next(dict, first))) {
+ if (dict->compare(first->key, next->key) >= 0)
+ return 0;
+ first = next;
+ }
+ }
+ return 1;
+}
+
+/*
+ * This function recursively verifies that the given binary subtree satisfies
+ * three of the red black properties. It checks that every red node has only
+ * black children. It makes sure that each node is either red or black. And it
+ * checks that every path has the same count of black nodes from root to leaf.
+ * It returns the blackheight of the given subtree; this allows blackheights to
+ * be computed recursively and compared for left and right siblings for
+ * mismatches. It does not check for every nil node being black, because there
+ * is only one sentinel nil node. The return value of this function is the
+ * black height of the subtree rooted at the node ``root'', or zero if the
+ * subtree is not red-black.
+ */
+
+static unsigned int verify_redblack(dnode_t *nil, dnode_t *root)
+{
+ unsigned height_left, height_right;
+
+ if (root != nil) {
+ height_left = verify_redblack(nil, root->left);
+ height_right = verify_redblack(nil, root->right);
+ if (height_left == 0 || height_right == 0)
+ return 0;
+ if (height_left != height_right)
+ return 0;
+ if (root->color == dnode_red) {
+ if (root->left->color != dnode_black)
+ return 0;
+ if (root->right->color != dnode_black)
+ return 0;
+ return height_left;
+ }
+ if (root->color != dnode_black)
+ return 0;
+ return height_left + 1;
+ }
+ return 1;
+}
+
+/*
+ * Compute the actual count of nodes by traversing the tree and
+ * return it. This could be compared against the stored count to
+ * detect a mismatch.
+ */
+
+static dictcount_t verify_node_count(dnode_t *nil, dnode_t *root)
+{
+ if (root == nil)
+ return 0;
+ else
+ return 1 + verify_node_count(nil, root->left)
+ + verify_node_count(nil, root->right);
+}
+#endif
+
+/*
+ * Verify that the tree contains the given node. This is done by
+ * traversing all of the nodes and comparing their pointers to the
+ * given pointer. Returns 1 if the node is found, otherwise
+ * returns zero. It is intended for debugging purposes.
+ */
+
+static int verify_dict_has_node(dnode_t *nil, dnode_t *root, dnode_t *node)
+{
+ if (root != nil) {
+ return root == node
+ || verify_dict_has_node(nil, root->left, node)
+ || verify_dict_has_node(nil, root->right, node);
+ }
+ return 0;
+}
+
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Dynamically allocate and initialize a dictionary object.
+ */
+
+dict_t *dict_create(dictcount_t maxcount, dict_comp_t comp)
+{
+ dict_t *new = malloc(sizeof *new);
+
+ if (new) {
+ new->compare = comp;
+ new->allocnode = dnode_alloc;
+ new->freenode = dnode_free;
+ new->context = NULL;
+ new->cmp_ctx = NULL;
+ new->nodecount = 0;
+ new->maxcount = maxcount;
+ new->nilnode.left = &new->nilnode;
+ new->nilnode.right = &new->nilnode;
+ new->nilnode.parent = &new->nilnode;
+ new->nilnode.color = dnode_black;
+ new->dupes = 0;
+ }
+ return new;
+}
+#endif /* E2FSCK_NOTUSED */
+
+/*
+ * Select a different set of node allocator routines.
+ */
+
+void dict_set_allocator(dict_t *dict, dnode_alloc_t al,
+ dnode_free_t fr, void *context)
+{
+ dict_assert (dict_count(dict) == 0);
+ dict_assert ((al == NULL && fr == NULL) || (al != NULL && fr != NULL));
+
+ dict->allocnode = al ? al : dnode_alloc;
+ dict->freenode = fr ? fr : dnode_free;
+ dict->context = context;
+}
+
+void dict_set_cmp_context(dict_t *dict, const void *cmp_ctx)
+{
+ dict_assert (!dict->cmp_ctx);
+ dict_assert (dict_count(dict) == 0);
+
+ dict->cmp_ctx = cmp_ctx;
+}
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Free a dynamically allocated dictionary object. Removing the nodes
+ * from the tree before deleting it is required.
+ */
+
+void dict_destroy(dict_t *dict)
+{
+ dict_assert (dict_isempty(dict));
+ free(dict);
+}
+#endif
+
+/*
+ * Free all the nodes in the dictionary by using the dictionary's
+ * installed free routine. The dictionary is emptied.
+ */
+
+void dict_free_nodes(dict_t *dict)
+{
+ dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+ free_nodes(dict, root, nil);
+ dict->nodecount = 0;
+ dict->nilnode.left = &dict->nilnode;
+ dict->nilnode.right = &dict->nilnode;
+}
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Obsolescent function, equivalent to dict_free_nodes
+ */
+void dict_free(dict_t *dict)
+{
+#ifdef KAZLIB_OBSOLESCENT_DEBUG
+ dict_assert ("call to obsolescent function dict_free()" && 0);
+#endif
+ dict_free_nodes(dict);
+}
+#endif
+
+/*
+ * Initialize a user-supplied dictionary object.
+ */
+
+dict_t *dict_init(dict_t *dict, dictcount_t maxcount, dict_comp_t comp)
+{
+ dict->compare = comp;
+ dict->allocnode = dnode_alloc;
+ dict->freenode = dnode_free;
+ dict->context = NULL;
+ dict->nodecount = 0;
+ dict->maxcount = maxcount;
+ dict->nilnode.left = &dict->nilnode;
+ dict->nilnode.right = &dict->nilnode;
+ dict->nilnode.parent = &dict->nilnode;
+ dict->nilnode.color = dnode_black;
+ dict->dupes = 0;
+ return dict;
+}
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Initialize a dictionary in the likeness of another dictionary
+ */
+
+void dict_init_like(dict_t *dict, const dict_t *template)
+{
+ dict->compare = template->compare;
+ dict->allocnode = template->allocnode;
+ dict->freenode = template->freenode;
+ dict->context = template->context;
+ dict->nodecount = 0;
+ dict->maxcount = template->maxcount;
+ dict->nilnode.left = &dict->nilnode;
+ dict->nilnode.right = &dict->nilnode;
+ dict->nilnode.parent = &dict->nilnode;
+ dict->nilnode.color = dnode_black;
+ dict->dupes = template->dupes;
+
+ dict_assert (dict_similar(dict, template));
+}
+
+/*
+ * Remove all nodes from the dictionary (without freeing them in any way).
+ */
+
+static void dict_clear(dict_t *dict)
+{
+ dict->nodecount = 0;
+ dict->nilnode.left = &dict->nilnode;
+ dict->nilnode.right = &dict->nilnode;
+ dict->nilnode.parent = &dict->nilnode;
+ dict_assert (dict->nilnode.color == dnode_black);
+}
+#endif /* E2FSCK_NOTUSED */
+
+
+/*
+ * Verify the integrity of the dictionary structure. This is provided for
+ * debugging purposes, and should be placed in assert statements. Just because
+ * this function succeeds doesn't mean that the tree is not corrupt. Certain
+ * corruptions in the tree may simply cause undefined behavior.
+ */
+#ifndef DICT_NODEBUG
+int dict_verify(dict_t *dict)
+{
+ dnode_t *nil = dict_nil(dict), *root = dict_root(dict);
+
+ /* check that the sentinel node and root node are black */
+ if (root->color != dnode_black)
+ return 0;
+ if (nil->color != dnode_black)
+ return 0;
+ if (nil->right != nil)
+ return 0;
+ /* nil->left is the root node; check that its parent pointer is nil */
+ if (nil->left->parent != nil)
+ return 0;
+ /* perform a weak test that the tree is a binary search tree */
+ if (!verify_bintree(dict))
+ return 0;
+ /* verify that the tree is a red-black tree */
+ if (!verify_redblack(nil, root))
+ return 0;
+ if (verify_node_count(nil, root) != dict_count(dict))
+ return 0;
+ return 1;
+}
+#endif /* DICT_NODEBUG */
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Determine whether two dictionaries are similar: have the same comparison and
+ * allocator functions, and same status as to whether duplicates are allowed.
+ */
+int dict_similar(const dict_t *left, const dict_t *right)
+{
+ if (left->compare != right->compare)
+ return 0;
+
+ if (left->allocnode != right->allocnode)
+ return 0;
+
+ if (left->freenode != right->freenode)
+ return 0;
+
+ if (left->context != right->context)
+ return 0;
+
+ if (left->dupes != right->dupes)
+ return 0;
+
+ return 1;
+}
+#endif /* E2FSCK_NOTUSED */
+
+/*
+ * Locate a node in the dictionary having the given key.
+ * If the node is not found, a null a pointer is returned (rather than
+ * a pointer that dictionary's nil sentinel node), otherwise a pointer to the
+ * located node is returned.
+ */
+
+dnode_t *dict_lookup(dict_t *dict, const void *key)
+{
+ dnode_t *root = dict_root(dict);
+ dnode_t *nil = dict_nil(dict);
+ dnode_t *saved;
+ int result;
+
+ /* simple binary search adapted for trees that contain duplicate keys */
+
+ while (root != nil) {
+ result = dict->compare(dict->cmp_ctx, key, root->key);
+ if (result < 0)
+ root = root->left;
+ else if (result > 0)
+ root = root->right;
+ else {
+ if (!dict->dupes) { /* no duplicates, return match */
+ return root;
+ } else { /* could be dupes, find leftmost one */
+ do {
+ saved = root;
+ root = root->left;
+ while (root != nil
+ && dict->compare(dict->cmp_ctx, key, root->key))
+ root = root->right;
+ } while (root != nil);
+ return saved;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Look for the node corresponding to the lowest key that is equal to or
+ * greater than the given key. If there is no such node, return null.
+ */
+
+dnode_t *dict_lower_bound(dict_t *dict, const void *key)
+{
+ dnode_t *root = dict_root(dict);
+ dnode_t *nil = dict_nil(dict);
+ dnode_t *tentative = 0;
+
+ while (root != nil) {
+ int result = dict->compare(dict->cmp_ctx, key, root->key);
+
+ if (result > 0) {
+ root = root->right;
+ } else if (result < 0) {
+ tentative = root;
+ root = root->left;
+ } else {
+ if (!dict->dupes) {
+ return root;
+ } else {
+ tentative = root;
+ root = root->left;
+ }
+ }
+ }
+
+ return tentative;
+}
+
+/*
+ * Look for the node corresponding to the greatest key that is equal to or
+ * lower than the given key. If there is no such node, return null.
+ */
+
+dnode_t *dict_upper_bound(dict_t *dict, const void *key)
+{
+ dnode_t *root = dict_root(dict);
+ dnode_t *nil = dict_nil(dict);
+ dnode_t *tentative = 0;
+
+ while (root != nil) {
+ int result = dict->compare(dict->cmp_ctx, key, root->key);
+
+ if (result < 0) {
+ root = root->left;
+ } else if (result > 0) {
+ tentative = root;
+ root = root->right;
+ } else {
+ if (!dict->dupes) {
+ return root;
+ } else {
+ tentative = root;
+ root = root->right;
+ }
+ }
+ }
+
+ return tentative;
+}
+#endif
+
+/*
+ * Insert a node into the dictionary. The node should have been
+ * initialized with a data field. All other fields are ignored.
+ * The behavior is undefined if the user attempts to insert into
+ * a dictionary that is already full (for which the dict_isfull()
+ * function returns true).
+ */
+
+void dict_insert(dict_t *dict, dnode_t *node, const void *key)
+{
+ dnode_t *where = dict_root(dict), *nil = dict_nil(dict);
+ dnode_t *parent = nil, *uncle, *grandpa;
+ int result = -1;
+
+ node->key = key;
+
+ dict_assert (!dict_isfull(dict));
+ dict_assert (!dict_contains(dict, node));
+ dict_assert (!dnode_is_in_a_dict(node));
+
+ /* basic binary tree insert */
+
+ while (where != nil) {
+ parent = where;
+ result = dict->compare(dict->cmp_ctx, key, where->key);
+ /* trap attempts at duplicate key insertion unless it's explicitly allowed */
+ dict_assert (dict->dupes || result != 0);
+ if (result < 0)
+ where = where->left;
+ else
+ where = where->right;
+ }
+
+ dict_assert (where == nil);
+
+ if (result < 0)
+ parent->left = node;
+ else
+ parent->right = node;
+
+ node->parent = parent;
+ node->left = nil;
+ node->right = nil;
+
+ dict->nodecount++;
+
+ /* red black adjustments */
+
+ node->color = dnode_red;
+
+ while (parent->color == dnode_red) {
+ grandpa = parent->parent;
+ if (parent == grandpa->left) {
+ uncle = grandpa->right;
+ if (uncle->color == dnode_red) { /* red parent, red uncle */
+ parent->color = dnode_black;
+ uncle->color = dnode_black;
+ grandpa->color = dnode_red;
+ node = grandpa;
+ parent = grandpa->parent;
+ } else { /* red parent, black uncle */
+ if (node == parent->right) {
+ rotate_left(parent);
+ parent = node;
+ dict_assert (grandpa == parent->parent);
+ /* rotation between parent and child preserves grandpa */
+ }
+ parent->color = dnode_black;
+ grandpa->color = dnode_red;
+ rotate_right(grandpa);
+ break;
+ }
+ } else { /* symmetric cases: parent == parent->parent->right */
+ uncle = grandpa->left;
+ if (uncle->color == dnode_red) {
+ parent->color = dnode_black;
+ uncle->color = dnode_black;
+ grandpa->color = dnode_red;
+ node = grandpa;
+ parent = grandpa->parent;
+ } else {
+ if (node == parent->left) {
+ rotate_right(parent);
+ parent = node;
+ dict_assert (grandpa == parent->parent);
+ }
+ parent->color = dnode_black;
+ grandpa->color = dnode_red;
+ rotate_left(grandpa);
+ break;
+ }
+ }
+ }
+
+ dict_root(dict)->color = dnode_black;
+
+ dict_assert (dict_verify(dict));
+}
+
+#ifdef E2FSCK_NOTUSED
+/*
+ * Delete the given node from the dictionary. If the given node does not belong
+ * to the given dictionary, undefined behavior results. A pointer to the
+ * deleted node is returned.
+ */
+
+dnode_t *dict_delete(dict_t *dict, dnode_t *delete)
+{
+ dnode_t *nil = dict_nil(dict), *child, *delparent = delete->parent;
+
+ /* basic deletion */
+
+ dict_assert (!dict_isempty(dict));
+ dict_assert (dict_contains(dict, delete));
+
+ /*
+ * If the node being deleted has two children, then we replace it with its
+ * successor (i.e. the leftmost node in the right subtree.) By doing this,
+ * we avoid the traditional algorithm under which the successor's key and
+ * value *only* move to the deleted node and the successor is spliced out
+ * from the tree. We cannot use this approach because the user may hold
+ * pointers to the successor, or nodes may be inextricably tied to some
+ * other structures by way of embedding, etc. So we must splice out the
+ * node we are given, not some other node, and must not move contents from
+ * one node to another behind the user's back.
+ */
+
+ if (delete->left != nil && delete->right != nil) {
+ dnode_t *next = dict_next(dict, delete);
+ dnode_t *nextparent = next->parent;
+ dnode_color_t nextcolor = next->color;
+
+ dict_assert (next != nil);
+ dict_assert (next->parent != nil);
+ dict_assert (next->left == nil);
+
+ /*
+ * First, splice out the successor from the tree completely, by
+ * moving up its right child into its place.
+ */
+
+ child = next->right;
+ child->parent = nextparent;
+
+ if (nextparent->left == next) {
+ nextparent->left = child;
+ } else {
+ dict_assert (nextparent->right == next);
+ nextparent->right = child;
+ }
+
+ /*
+ * Now that the successor has been extricated from the tree, install it
+ * in place of the node that we want deleted.
+ */
+
+ next->parent = delparent;
+ next->left = delete->left;
+ next->right = delete->right;
+ next->left->parent = next;
+ next->right->parent = next;
+ next->color = delete->color;
+ delete->color = nextcolor;
+
+ if (delparent->left == delete) {
+ delparent->left = next;
+ } else {
+ dict_assert (delparent->right == delete);
+ delparent->right = next;
+ }
+
+ } else {
+ dict_assert (delete != nil);
+ dict_assert (delete->left == nil || delete->right == nil);
+
+ child = (delete->left != nil) ? delete->left : delete->right;
+
+ child->parent = delparent = delete->parent;
+
+ if (delete == delparent->left) {
+ delparent->left = child;
+ } else {
+ dict_assert (delete == delparent->right);
+ delparent->right = child;
+ }
+ }
+
+ delete->parent = NULL;
+ delete->right = NULL;
+ delete->left = NULL;
+
+ dict->nodecount--;
+
+ dict_assert (verify_bintree(dict));
+
+ /* red-black adjustments */
+
+ if (delete->color == dnode_black) {
+ dnode_t *parent, *sister;
+
+ dict_root(dict)->color = dnode_red;
+
+ while (child->color == dnode_black) {
+ parent = child->parent;
+ if (child == parent->left) {
+ sister = parent->right;
+ dict_assert (sister != nil);
+ if (sister->color == dnode_red) {
+ sister->color = dnode_black;
+ parent->color = dnode_red;
+ rotate_left(parent);
+ sister = parent->right;
+ dict_assert (sister != nil);
+ }
+ if (sister->left->color == dnode_black
+ && sister->right->color == dnode_black) {
+ sister->color = dnode_red;
+ child = parent;
+ } else {
+ if (sister->right->color == dnode_black) {
+ dict_assert (sister->left->color == dnode_red);
+ sister->left->color = dnode_black;
+ sister->color = dnode_red;
+ rotate_right(sister);
+ sister = parent->right;
+ dict_assert (sister != nil);
+ }
+ sister->color = parent->color;
+ sister->right->color = dnode_black;
+ parent->color = dnode_black;
+ rotate_left(parent);
+ break;
+ }
+ } else { /* symmetric case: child == child->parent->right */
+ dict_assert (child == parent->right);
+ sister = parent->left;
+ dict_assert (sister != nil);
+ if (sister->color == dnode_red) {
+ sister->color = dnode_black;
+ parent->color = dnode_red;
+ rotate_right(parent);
+ sister = parent->left;
+ dict_assert (sister != nil);
+ }
+ if (sister->right->color == dnode_black
+ && sister->left->color == dnode_black) {
+ sister->color = dnode_red;
+ child = parent;
+ } else {
+ if (sister->left->color == dnode_black) {
+ dict_assert (sister->right->color == dnode_red);
+ sister->right->color = dnode_black;
+ sister->color = dnode_red;
+ rotate_left(sister);
+ sister = parent->left;
+ dict_assert (sister != nil);
+ }
+ sister->color = parent->color;
+ sister->left->color = dnode_black;
+ parent->color = dnode_black;
+ rotate_right(parent);
+ break;
+ }
+ }
+ }
+
+ child->color = dnode_black;
+ dict_root(dict)->color = dnode_black;
+ }
+
+ dict_assert (dict_verify(dict));
+
+ return delete;
+}
+#endif /* E2FSCK_NOTUSED */
+
+/*
+ * Allocate a node using the dictionary's allocator routine, give it
+ * the data item.
+ */
+
+int dict_alloc_insert(dict_t *dict, const void *key, void *data)
+{
+ dnode_t *node = dict->allocnode(dict->context);
+
+ if (node) {
+ dnode_init(node, data);
+ dict_insert(dict, node, key);
+ return 1;
+ }
+ return 0;
+}
+
+#ifdef E2FSCK_NOTUSED
+void dict_delete_free(dict_t *dict, dnode_t *node)
+{
+ dict_delete(dict, node);
+ dict->freenode(node, dict->context);
+}
+#endif
+
+/*
+ * Return the node with the lowest (leftmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+
+dnode_t *dict_first(dict_t *dict)
+{
+ dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *left;
+
+ if (root != nil)
+ while ((left = root->left) != nil)
+ root = left;
+
+ return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the node with the highest (rightmost) key. If the dictionary is empty
+ * (that is, dict_isempty(dict) returns 1) a null pointer is returned.
+ */
+
+dnode_t *dict_last(dict_t *dict)
+{
+ dnode_t *nil = dict_nil(dict), *root = dict_root(dict), *right;
+
+ if (root != nil)
+ while ((right = root->right) != nil)
+ root = right;
+
+ return (root == nil) ? NULL : root;
+}
+
+/*
+ * Return the given node's successor node---the node which has the
+ * next key in the the left to right ordering. If the node has
+ * no successor, a null pointer is returned rather than a pointer to
+ * the nil node.
+ */
+
+dnode_t *dict_next(dict_t *dict, dnode_t *curr)
+{
+ dnode_t *nil = dict_nil(dict), *parent, *left;
+
+ if (curr->right != nil) {
+ curr = curr->right;
+ while ((left = curr->left) != nil)
+ curr = left;
+ return curr;
+ }
+
+ parent = curr->parent;
+
+ while (parent != nil && curr == parent->right) {
+ curr = parent;
+ parent = curr->parent;
+ }
+
+ return (parent == nil) ? NULL : parent;
+}
+
+/*
+ * Return the given node's predecessor, in the key order.
+ * The nil sentinel node is returned if there is no predecessor.
+ */
+
+dnode_t *dict_prev(dict_t *dict, dnode_t *curr)
+{
+ dnode_t *nil = dict_nil(dict), *parent, *right;
+
+ if (curr->left != nil) {
+ curr = curr->left;
+ while ((right = curr->right) != nil)
+ curr = right;
+ return curr;
+ }
+
+ parent = curr->parent;
+
+ while (parent != nil && curr == parent->left) {
+ curr = parent;
+ parent = curr->parent;
+ }
+
+ return (parent == nil) ? NULL : parent;
+}
+
+void dict_allow_dupes(dict_t *dict)
+{
+ dict->dupes = 1;
+}
+
+#undef dict_count
+#undef dict_isempty
+#undef dict_isfull
+#undef dnode_get
+#undef dnode_put
+#undef dnode_getkey
+
+dictcount_t dict_count(dict_t *dict)
+{
+ return dict->nodecount;
+}
+
+int dict_isempty(dict_t *dict)
+{
+ return dict->nodecount == 0;
+}
+
+int dict_isfull(dict_t *dict)
+{
+ return dict->nodecount == dict->maxcount;
+}
+
+int dict_contains(dict_t *dict, dnode_t *node)
+{
+ return verify_dict_has_node(dict_nil(dict), dict_root(dict), node);
+}
+
+static dnode_t *dnode_alloc(void *context EXT2FS_ATTR((unused)))
+{
+ return malloc(sizeof *dnode_alloc(NULL));
+}
+
+static void dnode_free(dnode_t *node, void *context EXT2FS_ATTR((unused)))
+{
+ free(node);
+}
+
+dnode_t *dnode_create(void *data)
+{
+ dnode_t *new = malloc(sizeof *new);
+ if (new) {
+ new->data = data;
+ new->parent = NULL;
+ new->left = NULL;
+ new->right = NULL;
+ }
+ return new;
+}
+
+dnode_t *dnode_init(dnode_t *dnode, void *data)
+{
+ dnode->data = data;
+ dnode->parent = NULL;
+ dnode->left = NULL;
+ dnode->right = NULL;
+ return dnode;
+}
+
+void dnode_destroy(dnode_t *dnode)
+{
+ dict_assert (!dnode_is_in_a_dict(dnode));
+ free(dnode);
+}
+
+void *dnode_get(dnode_t *dnode)
+{
+ return dnode->data;
+}
+
+const void *dnode_getkey(dnode_t *dnode)
+{
+ return dnode->key;
+}
+
+#ifdef E2FSCK_NOTUSED
+void dnode_put(dnode_t *dnode, void *data)
+{
+ dnode->data = data;
+}
+#endif
+
+#ifndef DICT_NODEBUG
+int dnode_is_in_a_dict(dnode_t *dnode)
+{
+ return (dnode->parent && dnode->left && dnode->right);
+}
+#endif
+
+#ifdef E2FSCK_NOTUSED
+void dict_process(dict_t *dict, void *context, dnode_process_t function)
+{
+ dnode_t *node = dict_first(dict), *next;
+
+ while (node != NULL) {
+ /* check for callback function deleting */
+ /* the next node from under us */
+ dict_assert (dict_contains(dict, node));
+ next = dict_next(dict, node);
+ function(dict, node, context);
+ node = next;
+ }
+}
+
+static void load_begin_internal(dict_load_t *load, dict_t *dict)
+{
+ load->dictptr = dict;
+ load->nilnode.left = &load->nilnode;
+ load->nilnode.right = &load->nilnode;
+}
+
+void dict_load_begin(dict_load_t *load, dict_t *dict)
+{
+ dict_assert (dict_isempty(dict));
+ load_begin_internal(load, dict);
+}
+
+void dict_load_next(dict_load_t *load, dnode_t *newnode, const void *key)
+{
+ dict_t *dict = load->dictptr;
+ dnode_t *nil = &load->nilnode;
+
+ dict_assert (!dnode_is_in_a_dict(newnode));
+ dict_assert (dict->nodecount < dict->maxcount);
+
+#ifndef DICT_NODEBUG
+ if (dict->nodecount > 0) {
+ if (dict->dupes)
+ dict_assert (dict->compare(nil->left->key, key) <= 0);
+ else
+ dict_assert (dict->compare(nil->left->key, key) < 0);
+ }
+#endif
+
+ newnode->key = key;
+ nil->right->left = newnode;
+ nil->right = newnode;
+ newnode->left = nil;
+ dict->nodecount++;
+}
+
+void dict_load_end(dict_load_t *load)
+{
+ dict_t *dict = load->dictptr;
+ dnode_t *tree[DICT_DEPTH_MAX] = { 0 };
+ dnode_t *curr, *dictnil = dict_nil(dict), *loadnil = &load->nilnode, *next;
+ dnode_t *complete = 0;
+ dictcount_t fullcount = DICTCOUNT_T_MAX, nodecount = dict->nodecount;
+ dictcount_t botrowcount;
+ unsigned baselevel = 0, level = 0, i;
+
+ dict_assert (dnode_red == 0 && dnode_black == 1);
+
+ while (fullcount >= nodecount && fullcount)
+ fullcount >>= 1;
+
+ botrowcount = nodecount - fullcount;
+
+ for (curr = loadnil->left; curr != loadnil; curr = next) {
+ next = curr->left;
+
+ if (complete == NULL && botrowcount-- == 0) {
+ dict_assert (baselevel == 0);
+ dict_assert (level == 0);
+ baselevel = level = 1;
+ complete = tree[0];
+
+ if (complete != 0) {
+ tree[0] = 0;
+ complete->right = dictnil;
+ while (tree[level] != 0) {
+ tree[level]->right = complete;
+ complete->parent = tree[level];
+ complete = tree[level];
+ tree[level++] = 0;
+ }
+ }
+ }
+
+ if (complete == NULL) {
+ curr->left = dictnil;
+ curr->right = dictnil;
+ curr->color = level % 2;
+ complete = curr;
+
+ dict_assert (level == baselevel);
+ while (tree[level] != 0) {
+ tree[level]->right = complete;
+ complete->parent = tree[level];
+ complete = tree[level];
+ tree[level++] = 0;
+ }
+ } else {
+ curr->left = complete;
+ curr->color = (level + 1) % 2;
+ complete->parent = curr;
+ tree[level] = curr;
+ complete = 0;
+ level = baselevel;
+ }
+ }
+
+ if (complete == NULL)
+ complete = dictnil;
+
+ for (i = 0; i < DICT_DEPTH_MAX; i++) {
+ if (tree[i] != 0) {
+ tree[i]->right = complete;
+ complete->parent = tree[i];
+ complete = tree[i];
+ }
+ }
+
+ dictnil->color = dnode_black;
+ dictnil->right = dictnil;
+ complete->parent = dictnil;
+ complete->color = dnode_black;
+ dict_root(dict) = complete;
+
+ dict_assert (dict_verify(dict));
+}
+
+void dict_merge(dict_t *dest, dict_t *source)
+{
+ dict_load_t load;
+ dnode_t *leftnode = dict_first(dest), *rightnode = dict_first(source);
+
+ dict_assert (dict_similar(dest, source));
+
+ if (source == dest)
+ return;
+
+ dest->nodecount = 0;
+ load_begin_internal(&load, dest);
+
+ for (;;) {
+ if (leftnode != NULL && rightnode != NULL) {
+ if (dest->compare(leftnode->key, rightnode->key) < 0)
+ goto copyleft;
+ else
+ goto copyright;
+ } else if (leftnode != NULL) {
+ goto copyleft;
+ } else if (rightnode != NULL) {
+ goto copyright;
+ } else {
+ dict_assert (leftnode == NULL && rightnode == NULL);
+ break;
+ }
+
+ copyleft:
+ {
+ dnode_t *next = dict_next(dest, leftnode);
+#ifndef DICT_NODEBUG
+ leftnode->left = NULL; /* suppress assertion in dict_load_next */
+#endif
+ dict_load_next(&load, leftnode, leftnode->key);
+ leftnode = next;
+ continue;
+ }
+
+ copyright:
+ {
+ dnode_t *next = dict_next(source, rightnode);
+#ifndef DICT_NODEBUG
+ rightnode->left = NULL;
+#endif
+ dict_load_next(&load, rightnode, rightnode->key);
+ rightnode = next;
+ continue;
+ }
+ }
+
+ dict_clear(source);
+ dict_load_end(&load);
+}
+#endif /* E2FSCK_NOTUSED */
+
+#ifdef KAZLIB_TEST_MAIN
+
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+typedef char input_t[256];
+
+static int tokenize(char *string, ...)
+{
+ char **tokptr;
+ va_list arglist;
+ int tokcount = 0;
+
+ va_start(arglist, string);
+ tokptr = va_arg(arglist, char **);
+ while (tokptr) {
+ while (*string && isspace((unsigned char) *string))
+ string++;
+ if (!*string)
+ break;
+ *tokptr = string;
+ while (*string && !isspace((unsigned char) *string))
+ string++;
+ tokptr = va_arg(arglist, char **);
+ tokcount++;
+ if (!*string)
+ break;
+ *string++ = 0;
+ }
+ va_end(arglist);
+
+ return tokcount;
+}
+
+static int comparef(const void *cmp_ctx, const void *key1, const void *key2)
+{
+ return strcmp(key1, key2);
+}
+
+static char *dupstring(char *str)
+{
+ int sz = strlen(str) + 1;
+ char *new = malloc(sz);
+ if (new)
+ memcpy(new, str, sz);
+ return new;
+}
+
+static dnode_t *new_node(void *c)
+{
+ static dnode_t few[5];
+ static int count;
+
+ if (count < 5)
+ return few + count++;
+
+ return NULL;
+}
+
+static void del_node(dnode_t *n, void *c)
+{
+}
+
+static int prompt = 0;
+
+static void construct(dict_t *d)
+{
+ input_t in;
+ int done = 0;
+ dict_load_t dl;
+ dnode_t *dn;
+ char *tok1, *tok2, *val;
+ const char *key;
+ char *help =
+ "p turn prompt on\n"
+ "q finish construction\n"
+ "a <key> <val> add new entry\n";
+
+ if (!dict_isempty(d))
+ puts("warning: dictionary not empty!");
+
+ dict_load_begin(&dl, d);
+
+ while (!done) {
+ if (prompt)
+ putchar('>');
+ fflush(stdout);
+
+ if (!fgets(in, sizeof(input_t), stdin))
+ break;
+
+ switch (in[0]) {
+ case '?':
+ puts(help);
+ break;
+ case 'p':
+ prompt = 1;
+ break;
+ case 'q':
+ done = 1;
+ break;
+ case 'a':
+ if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+ puts("what?");
+ break;
+ }
+ key = dupstring(tok1);
+ val = dupstring(tok2);
+ dn = dnode_create(val);
+
+ if (!key || !val || !dn) {
+ puts("out of memory");
+ free((void *) key);
+ free(val);
+ if (dn)
+ dnode_destroy(dn);
+ }
+
+ dict_load_next(&dl, dn, key);
+ break;
+ default:
+ putchar('?');
+ putchar('\n');
+ break;
+ }
+ }
+
+ dict_load_end(&dl);
+}
+
+int main(void)
+{
+ input_t in;
+ dict_t darray[10];
+ dict_t *d = &darray[0];
+ dnode_t *dn;
+ int i;
+ char *tok1, *tok2, *val;
+ const char *key;
+
+ char *help =
+ "a <key> <val> add value to dictionary\n"
+ "d <key> delete value from dictionary\n"
+ "l <key> lookup value in dictionary\n"
+ "( <key> lookup lower bound\n"
+ ") <key> lookup upper bound\n"
+ "# <num> switch to alternate dictionary (0-9)\n"
+ "j <num> <num> merge two dictionaries\n"
+ "f free the whole dictionary\n"
+ "k allow duplicate keys\n"
+ "c show number of entries\n"
+ "t dump whole dictionary in sort order\n"
+ "m make dictionary out of sorted items\n"
+ "p turn prompt on\n"
+ "s switch to non-functioning allocator\n"
+ "q quit";
+
+ for (i = 0; i < sizeof darray / sizeof *darray; i++)
+ dict_init(&darray[i], DICTCOUNT_T_MAX, comparef);
+
+ for (;;) {
+ if (prompt)
+ putchar('>');
+ fflush(stdout);
+
+ if (!fgets(in, sizeof(input_t), stdin))
+ break;
+
+ switch(in[0]) {
+ case '?':
+ puts(help);
+ break;
+ case 'a':
+ if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+ puts("what?");
+ break;
+ }
+ key = dupstring(tok1);
+ val = dupstring(tok2);
+
+ if (!key || !val) {
+ puts("out of memory");
+ free((void *) key);
+ free(val);
+ }
+
+ if (!dict_alloc_insert(d, key, val)) {
+ puts("dict_alloc_insert failed");
+ free((void *) key);
+ free(val);
+ break;
+ }
+ break;
+ case 'd':
+ if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+ puts("what?");
+ break;
+ }
+ dn = dict_lookup(d, tok1);
+ if (!dn) {
+ puts("dict_lookup failed");
+ break;
+ }
+ val = dnode_get(dn);
+ key = dnode_getkey(dn);
+ dict_delete_free(d, dn);
+
+ free(val);
+ free((void *) key);
+ break;
+ case 'f':
+ dict_free(d);
+ break;
+ case 'l':
+ case '(':
+ case ')':
+ if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+ puts("what?");
+ break;
+ }
+ dn = 0;
+ switch (in[0]) {
+ case 'l':
+ dn = dict_lookup(d, tok1);
+ break;
+ case '(':
+ dn = dict_lower_bound(d, tok1);
+ break;
+ case ')':
+ dn = dict_upper_bound(d, tok1);
+ break;
+ }
+ if (!dn) {
+ puts("lookup failed");
+ break;
+ }
+ val = dnode_get(dn);
+ puts(val);
+ break;
+ case 'm':
+ construct(d);
+ break;
+ case 'k':
+ dict_allow_dupes(d);
+ break;
+ case 'c':
+ printf("%lu\n", (unsigned long) dict_count(d));
+ break;
+ case 't':
+ for (dn = dict_first(d); dn; dn = dict_next(d, dn)) {
+ printf("%s\t%s\n", (char *) dnode_getkey(dn),
+ (char *) dnode_get(dn));
+ }
+ break;
+ case 'q':
+ exit(0);
+ break;
+ case '\0':
+ break;
+ case 'p':
+ prompt = 1;
+ break;
+ case 's':
+ dict_set_allocator(d, new_node, del_node, NULL);
+ break;
+ case '#':
+ if (tokenize(in+1, &tok1, (char **) 0) != 1) {
+ puts("what?");
+ break;
+ } else {
+ int dictnum = atoi(tok1);
+ if (dictnum < 0 || dictnum > 9) {
+ puts("invalid number");
+ break;
+ }
+ d = &darray[dictnum];
+ }
+ break;
+ case 'j':
+ if (tokenize(in+1, &tok1, &tok2, (char **) 0) != 2) {
+ puts("what?");
+ break;
+ } else {
+ int dict1 = atoi(tok1), dict2 = atoi(tok2);
+ if (dict1 < 0 || dict1 > 9 || dict2 < 0 || dict2 > 9) {
+ puts("invalid number");
+ break;
+ }
+ dict_merge(&darray[dict1], &darray[dict2]);
+ }
+ break;
+ default:
+ putchar('?');
+ putchar('\n');
+ break;
+ }
+ }
+
+ return 0;
+}
+
+#endif
diff --git a/lib/support/dict.h b/lib/support/dict.h
new file mode 100644
index 0000000..2d87cc0
--- /dev/null
+++ b/lib/support/dict.h
@@ -0,0 +1,147 @@
+/*
+ * Dictionary Abstract Data Type
+ * Copyright (C) 1997 Kaz Kylheku <kaz@ashi.footprints.net>
+ *
+ * Free Software License:
+ *
+ * All rights are reserved by the author, with the following exceptions:
+ * Permission is granted to freely reproduce and distribute this software,
+ * possibly in exchange for a fee, provided that this copyright notice appears
+ * intact. Permission is also granted to adapt this software to produce
+ * derivative works, as long as the modified versions carry this copyright
+ * notice and additional notices stating that the work has been modified.
+ * This source code may be translated into executable form and incorporated
+ * into proprietary software; there is no requirement for such software to
+ * contain a copyright notice related to this source.
+ *
+ * $Id: dict.h,v 1.22.2.6 2000/11/13 01:36:44 kaz Exp $
+ * $Name: kazlib_1_20 $
+ * The work has been modified.
+ */
+
+#ifndef DICT_H
+#define DICT_H
+
+#include <limits.h>
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#include "sfx.h"
+#endif
+
+/*
+ * Blurb for inclusion into C++ translation units
+ */
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef unsigned long dictcount_t;
+#define DICTCOUNT_T_MAX ULONG_MAX
+
+/*
+ * The dictionary is implemented as a red-black tree
+ */
+
+typedef enum { dnode_red, dnode_black } dnode_color_t;
+
+typedef struct dnode_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+ struct dnode_t *dict_left;
+ struct dnode_t *dict_right;
+ struct dnode_t *dict_parent;
+ dnode_color_t dict_color;
+ const void *dict_key;
+ void *dict_data;
+#else
+ int dict_dummy;
+#endif
+} dnode_t;
+
+typedef int (*dict_comp_t)(const void *, const void *, const void *);
+typedef dnode_t *(*dnode_alloc_t)(void *);
+typedef void (*dnode_free_t)(dnode_t *, void *);
+
+typedef struct dict_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+ dnode_t dict_nilnode;
+ dictcount_t dict_nodecount;
+ dictcount_t dict_maxcount;
+ dict_comp_t dict_compare;
+ dnode_alloc_t dict_allocnode;
+ dnode_free_t dict_freenode;
+ void *dict_context;
+ const void *cmp_ctx;
+ int dict_dupes;
+#else
+ int dict_dummmy;
+#endif
+} dict_t;
+
+typedef void (*dnode_process_t)(dict_t *, dnode_t *, void *);
+
+typedef struct dict_load_t {
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+ dict_t *dict_dictptr;
+ dnode_t dict_nilnode;
+#else
+ int dict_dummmy;
+#endif
+} dict_load_t;
+
+extern dict_t *dict_create(dictcount_t, dict_comp_t);
+extern void dict_set_allocator(dict_t *, dnode_alloc_t, dnode_free_t, void *);
+extern void dict_set_cmp_context(dict_t *, const void *);
+extern void dict_destroy(dict_t *);
+extern void dict_free_nodes(dict_t *);
+extern void dict_free(dict_t *);
+extern dict_t *dict_init(dict_t *, dictcount_t, dict_comp_t);
+extern void dict_init_like(dict_t *, const dict_t *);
+extern int dict_verify(dict_t *);
+extern int dict_similar(const dict_t *, const dict_t *);
+extern dnode_t *dict_lookup(dict_t *, const void *);
+extern dnode_t *dict_lower_bound(dict_t *, const void *);
+extern dnode_t *dict_upper_bound(dict_t *, const void *);
+extern void dict_insert(dict_t *, dnode_t *, const void *);
+extern dnode_t *dict_delete(dict_t *, dnode_t *);
+extern int dict_alloc_insert(dict_t *, const void *, void *);
+extern void dict_delete_free(dict_t *, dnode_t *);
+extern dnode_t *dict_first(dict_t *);
+extern dnode_t *dict_last(dict_t *);
+extern dnode_t *dict_next(dict_t *, dnode_t *);
+extern dnode_t *dict_prev(dict_t *, dnode_t *);
+extern dictcount_t dict_count(dict_t *);
+extern int dict_isempty(dict_t *);
+extern int dict_isfull(dict_t *);
+extern int dict_contains(dict_t *, dnode_t *);
+extern void dict_allow_dupes(dict_t *);
+extern int dnode_is_in_a_dict(dnode_t *);
+extern dnode_t *dnode_create(void *);
+extern dnode_t *dnode_init(dnode_t *, void *);
+extern void dnode_destroy(dnode_t *);
+extern void *dnode_get(dnode_t *);
+extern const void *dnode_getkey(dnode_t *);
+extern void dnode_put(dnode_t *, void *);
+extern void dict_process(dict_t *, void *, dnode_process_t);
+extern void dict_load_begin(dict_load_t *, dict_t *);
+extern void dict_load_next(dict_load_t *, dnode_t *, const void *);
+extern void dict_load_end(dict_load_t *);
+extern void dict_merge(dict_t *, dict_t *);
+
+#if defined(DICT_IMPLEMENTATION) || !defined(KAZLIB_OPAQUE_DEBUG)
+#ifdef KAZLIB_SIDEEFFECT_DEBUG
+#define dict_isfull(D) (SFX_CHECK(D)->dict_nodecount == (D)->dict_maxcount)
+#else
+#define dict_isfull(D) ((D)->dict_nodecount == (D)->dict_maxcount)
+#endif
+#define dict_count(D) ((D)->dict_nodecount)
+#define dict_isempty(D) ((D)->dict_nodecount == 0)
+#define dnode_get(N) ((N)->dict_data)
+#define dnode_getkey(N) ((N)->dict_key)
+#define dnode_put(N, X) ((N)->dict_data = (X))
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/lib/support/dqblk_v2.h b/lib/support/dqblk_v2.h
new file mode 100644
index 0000000..d12512a
--- /dev/null
+++ b/lib/support/dqblk_v2.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "quotaio_tree.h"
+
+/* Structure for format specific information */
+struct v2_mem_dqinfo {
+ struct qtree_mem_dqinfo dqi_qtree;
+ unsigned int dqi_flags; /* Flags set in quotafile */
+ unsigned int dqi_used_entries; /* Number of entries in file -
+ updated by scan_dquots */
+ unsigned int dqi_data_blocks; /* Number of data blocks in file -
+ updated by scan_dquots */
+};
+
+struct v2_mem_dqblk {
+ long long 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/support/mkquota.c b/lib/support/mkquota.c
new file mode 100644
index 0000000..9339c99
--- /dev/null
+++ b/lib/support/mkquota.c
@@ -0,0 +1,707 @@
+/*
+ * mkquota.c --- create quota files for a filesystem
+ *
+ * Aditya Kali <adityakali@google.com>
+ */
+#include "config.h"
+#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 "quotaio.h"
+#include "quotaio_v2.h"
+#include "quotaio_tree.h"
+#include "common.h"
+#include "dict.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))
+
+#if DEBUG_QUOTA
+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;
+}
+
+static void print_dquot(const char *desc, struct dquot *dq)
+{
+ if (desc)
+ fprintf(stderr, "%s: ", desc);
+ fprintf(stderr, "%u %lld:%lld:%lld %lld:%lld:%lld\n",
+ dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,
+ (long long) dq->dq_dqb.dqb_bsoftlimit,
+ (long long) dq->dq_dqb.dqb_bhardlimit,
+ (long long) dq->dq_dqb.dqb_curinodes,
+ (long long) dq->dq_dqb.dqb_isoftlimit,
+ (long long) dq->dq_dqb.dqb_ihardlimit);
+}
+#else
+static void print_dquot(const char *desc EXT2FS_ATTR((unused)),
+ struct dquot *dq EXT2FS_ATTR((unused)))
+{
+}
+#endif
+
+/*
+ * Returns 0 if not able to find the quota file, otherwise returns its
+ * inode number.
+ */
+int quota_file_exists(ext2_filsys fs, enum quota_type qtype)
+{
+ char qf_name[256];
+ errcode_t ret;
+ ext2_ino_t ino;
+
+ if (qtype >= MAXQUOTAS)
+ return -EINVAL;
+
+ quota_get_qf_name(qtype, QFMT_VFS_V1, 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 quota_set_sb_inum(ext2_filsys fs, ext2_ino_t ino, enum quota_type qtype)
+{
+ ext2_ino_t *inump;
+
+ inump = quota_sb_inump(fs->super, qtype);
+
+ log_debug("setting quota ino in superblock: ino=%u, type=%d", ino,
+ qtype);
+ if (inump == NULL)
+ return;
+ *inump = ino;
+ ext2fs_mark_super_dirty(fs);
+}
+
+errcode_t quota_remove_inode(ext2_filsys fs, enum quota_type qtype)
+{
+ ext2_ino_t qf_ino;
+ errcode_t retval;
+
+ retval = ext2fs_read_bitmaps(fs);
+ if (retval) {
+ log_debug("Couldn't read bitmaps: %s", error_message(retval));
+ return retval;
+ }
+
+ qf_ino = *quota_sb_inump(fs->super, qtype);
+ if (qf_ino == 0)
+ return 0;
+ retval = quota_inode_truncate(fs, qf_ino);
+ if (retval)
+ return retval;
+ if (qf_ino >= EXT2_FIRST_INODE(fs->super)) {
+ struct ext2_inode inode;
+
+ retval = ext2fs_read_inode(fs, qf_ino, &inode);
+ if (!retval) {
+ memset(&inode, 0, sizeof(struct ext2_inode));
+ ext2fs_write_inode(fs, qf_ino, &inode);
+ }
+ ext2fs_inode_alloc_stats2(fs, qf_ino, -1, 0);
+ ext2fs_mark_ib_dirty(fs);
+
+ }
+ quota_set_sb_inum(fs, 0, qtype);
+
+ ext2fs_mark_super_dirty(fs);
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ retval = ext2fs_write_bitmaps(fs);
+ if (retval) {
+ log_debug("Couldn't write bitmaps: %s", error_message(retval));
+ return retval;
+ }
+ return 0;
+}
+
+static void write_dquots(dict_t *dict, struct quota_handle *qh)
+{
+ dnode_t *n;
+ struct dquot *dq;
+
+ for (n = dict_first(dict); n; n = dict_next(dict, n)) {
+ dq = dnode_get(n);
+ if (dq) {
+ print_dquot("write", dq);
+ dq->dq_h = qh;
+ update_grace_times(dq);
+ qh->qh_ops->commit_dquot(dq);
+ }
+ }
+}
+
+errcode_t quota_write_inode(quota_ctx_t qctx, unsigned int qtype_bits)
+{
+ int retval = 0;
+ enum quota_type qtype;
+ dict_t *dict;
+ ext2_filsys fs;
+ struct quota_handle *h = NULL;
+ int fmt = QFMT_VFS_V1;
+
+ if (!qctx)
+ return 0;
+
+ fs = qctx->fs;
+ retval = ext2fs_get_mem(sizeof(struct quota_handle), &h);
+ if (retval) {
+ log_debug("Unable to allocate quota handle: %s",
+ error_message(retval));
+ goto out;
+ }
+
+ retval = ext2fs_read_bitmaps(fs);
+ if (retval) {
+ log_debug("Couldn't read bitmaps: %s", error_message(retval));
+ goto out;
+ }
+
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ if (((1 << qtype) & qtype_bits) == 0)
+ continue;
+
+ dict = qctx->quota_dict[qtype];
+ if (!dict)
+ continue;
+
+ retval = quota_file_create(h, fs, qtype, fmt);
+ if (retval) {
+ log_debug("Cannot initialize io on quotafile: %s",
+ error_message(retval));
+ goto out;
+ }
+
+ write_dquots(dict, h);
+ retval = quota_file_close(qctx, h);
+ if (retval) {
+ log_debug("Cannot finish IO on new quotafile: %s",
+ strerror(errno));
+ if (h->qh_qf.e2_file)
+ ext2fs_file_close(h->qh_qf.e2_file);
+ (void) quota_inode_truncate(fs, h->qh_qf.ino);
+ goto out;
+ }
+
+ /* Set quota inode numbers in superblock. */
+ quota_set_sb_inum(fs, h->qh_qf.ino, qtype);
+ ext2fs_mark_super_dirty(fs);
+ ext2fs_mark_bb_dirty(fs);
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ }
+
+ retval = ext2fs_write_bitmaps(fs);
+ if (retval) {
+ log_debug("Couldn't write bitmaps: %s", error_message(retval));
+ goto out;
+ }
+out:
+ if (h)
+ ext2fs_free_mem(&h);
+ return retval;
+}
+
+/******************************************************************/
+/* Helper functions for computing quota in memory. */
+/******************************************************************/
+
+static int dict_uint_cmp(const void *cmp_ctx EXT2FS_ATTR((unused)),
+ const void *a, const void *b)
+{
+ unsigned int c, d;
+
+ c = VOIDPTR_TO_UINT(a);
+ d = VOIDPTR_TO_UINT(b);
+
+ if (c == d)
+ return 0;
+ else if (c > d)
+ return 1;
+ else
+ return -1;
+}
+
+static inline int project_quota_valid(quota_ctx_t qctx)
+{
+ return (EXT2_INODE_SIZE(qctx->fs->super) > EXT2_GOOD_OLD_INODE_SIZE);
+}
+
+static inline qid_t get_qid(struct ext2_inode_large *inode, enum quota_type qtype)
+{
+ unsigned int inode_size;
+
+ switch (qtype) {
+ case USRQUOTA:
+ return inode_uid(*inode);
+ case GRPQUOTA:
+ return inode_gid(*inode);
+ case PRJQUOTA:
+ inode_size = EXT2_GOOD_OLD_INODE_SIZE +
+ inode->i_extra_isize;
+ if (inode_includes(inode_size, i_projid))
+ return inode_projid(*inode);
+ return 0;
+ default:
+ return 0;
+ }
+
+ return 0;
+}
+
+static void quota_dnode_free(dnode_t *node,
+ void *context EXT2FS_ATTR((unused)))
+{
+ void *ptr = node ? dnode_get(node) : 0;
+
+ ext2fs_free_mem(&ptr);
+ free(node);
+}
+
+/*
+ * Set up the quota tracking data structures.
+ */
+errcode_t quota_init_context(quota_ctx_t *qctx, ext2_filsys fs,
+ unsigned int qtype_bits)
+{
+ errcode_t err;
+ dict_t *dict;
+ quota_ctx_t ctx;
+ enum quota_type qtype;
+
+ err = ext2fs_get_mem(sizeof(struct quota_ctx), &ctx);
+ if (err) {
+ log_debug("Failed to allocate quota context");
+ return err;
+ }
+
+ memset(ctx, 0, sizeof(struct quota_ctx));
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ ctx->quota_file[qtype] = NULL;
+ if (qtype_bits) {
+ if (((1 << qtype) & qtype_bits) == 0)
+ continue;
+ } else {
+ if (*quota_sb_inump(fs->super, qtype) == 0)
+ continue;
+ }
+ err = ext2fs_get_mem(sizeof(dict_t), &dict);
+ if (err) {
+ log_debug("Failed to allocate dictionary");
+ quota_release_context(&ctx);
+ return err;
+ }
+ ctx->quota_dict[qtype] = dict;
+ dict_init(dict, DICTCOUNT_T_MAX, dict_uint_cmp);
+ dict_set_allocator(dict, NULL, quota_dnode_free, NULL);
+ }
+
+ ctx->fs = fs;
+ *qctx = ctx;
+ return 0;
+}
+
+void quota_release_context(quota_ctx_t *qctx)
+{
+ errcode_t err;
+ dict_t *dict;
+ enum quota_type qtype;
+ quota_ctx_t ctx;
+
+ if (!qctx)
+ return;
+
+ ctx = *qctx;
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ dict = ctx->quota_dict[qtype];
+ ctx->quota_dict[qtype] = 0;
+ if (dict) {
+ dict_free_nodes(dict);
+ free(dict);
+ }
+ if (ctx->quota_file[qtype]) {
+ err = quota_file_close(ctx, ctx->quota_file[qtype]);
+ if (err) {
+ log_err("Cannot close quotafile: %s",
+ strerror(errno));
+ ext2fs_free_mem(&ctx->quota_file[qtype]);
+ }
+ }
+ }
+ *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 {
+ if (ext2fs_get_mem(sizeof(struct dquot), &dq)) {
+ log_err("Unable to allocate dquot");
+ return NULL;
+ }
+ memset(dq, 0, sizeof(struct dquot));
+ dict_alloc_insert(dict, UINT_TO_VOIDPTR(key), dq);
+ dq->dq_id = key;
+ }
+ return dq;
+}
+
+
+/*
+ * Called to update the blocks used by a particular inode
+ */
+void quota_data_add(quota_ctx_t qctx, struct ext2_inode_large *inode,
+ ext2_ino_t ino EXT2FS_ATTR((unused)),
+ qsize_t space)
+{
+ struct dquot *dq;
+ dict_t *dict;
+ enum quota_type qtype;
+
+ if (!qctx)
+ return;
+
+ log_debug("ADD_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino,
+ inode_uid(*inode),
+ inode_gid(*inode), space);
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ if (qtype == PRJQUOTA && !project_quota_valid(qctx))
+ continue;
+ dict = qctx->quota_dict[qtype];
+ if (dict) {
+ dq = get_dq(dict, get_qid(inode, qtype));
+ if (dq)
+ 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_large *inode,
+ ext2_ino_t ino EXT2FS_ATTR((unused)),
+ qsize_t space)
+{
+ struct dquot *dq;
+ dict_t *dict;
+ enum quota_type qtype;
+
+ if (!qctx)
+ return;
+
+ log_debug("SUB_DATA: Inode: %u, UID/GID: %u/%u, space: %ld", ino,
+ inode_uid(*inode),
+ inode_gid(*inode), space);
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ if (qtype == PRJQUOTA && !project_quota_valid(qctx))
+ continue;
+ dict = qctx->quota_dict[qtype];
+ if (dict) {
+ dq = get_dq(dict, get_qid(inode, qtype));
+ if (dq)
+ 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_large *inode,
+ ext2_ino_t ino EXT2FS_ATTR((unused)), int adjust)
+{
+ struct dquot *dq;
+ dict_t *dict;
+ enum quota_type qtype;
+
+ if (!qctx)
+ return;
+
+ log_debug("ADJ_INODE: Inode: %u, UID/GID: %u/%u, adjust: %d", ino,
+ inode_uid(*inode),
+ inode_gid(*inode), adjust);
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++) {
+ if (qtype == PRJQUOTA && !project_quota_valid(qctx))
+ continue;
+ dict = qctx->quota_dict[qtype];
+ if (dict) {
+ dq = get_dq(dict, get_qid(inode, qtype));
+ if (dq)
+ dq->dq_dqb.dqb_curinodes += adjust;
+ }
+ }
+}
+
+errcode_t quota_compute_usage(quota_ctx_t qctx)
+{
+ ext2_filsys fs;
+ ext2_ino_t ino;
+ errcode_t ret;
+ struct ext2_inode_large *inode;
+ int inode_size;
+ qsize_t space;
+ ext2_inode_scan scan;
+
+ if (!qctx)
+ return 0;
+
+ fs = qctx->fs;
+ ret = ext2fs_open_inode_scan(fs, 0, &scan);
+ if (ret) {
+ log_err("while opening inode scan. ret=%ld", ret);
+ return ret;
+ }
+ inode_size = fs->super->s_inode_size;
+ inode = malloc(inode_size);
+ if (!inode) {
+ ext2fs_close_inode_scan(scan);
+ return ENOMEM;
+ }
+ while (1) {
+ ret = ext2fs_get_next_inode_full(scan, &ino,
+ EXT2_INODE(inode), inode_size);
+ if (ret) {
+ log_err("while getting next inode. ret=%ld", ret);
+ ext2fs_close_inode_scan(scan);
+ free(inode);
+ return ret;
+ }
+ if (ino == 0)
+ break;
+ if (!inode->i_links_count)
+ continue;
+ if (ino == EXT2_ROOT_INO ||
+ (ino >= EXT2_FIRST_INODE(fs->super) &&
+ ino != quota_type2inum(PRJQUOTA, fs->super) &&
+ ino != fs->super->s_orphan_file_inum)) {
+ space = ext2fs_get_stat_i_blocks(fs,
+ EXT2_INODE(inode)) << 9;
+ quota_data_add(qctx, inode, ino, space);
+ quota_data_inodes(qctx, inode, ino, +1);
+ }
+ }
+
+ ext2fs_close_inode_scan(scan);
+ free(inode);
+ return 0;
+}
+
+struct scan_dquots_data {
+ dict_t *quota_dict;
+ int update_limits; /* update limits from disk */
+ int update_usage;
+ int check_consistency;
+ int usage_is_inconsistent;
+};
+
+static int scan_dquots_callback(struct dquot *dquot, void *cb_data)
+{
+ struct scan_dquots_data *scan_data = cb_data;
+ dict_t *quota_dict = scan_data->quota_dict;
+ struct dquot *dq;
+
+ dq = get_dq(quota_dict, dquot->dq_id);
+ if (!dq)
+ return -1;
+ dq->dq_id = dquot->dq_id;
+ dq->dq_flags |= DQF_SEEN;
+
+ print_dquot("mem", dq);
+ print_dquot("dsk", dquot);
+
+ /* Check if there is inconsistency */
+ if (scan_data->check_consistency &&
+ (dq->dq_dqb.dqb_curspace != dquot->dq_dqb.dqb_curspace ||
+ dq->dq_dqb.dqb_curinodes != dquot->dq_dqb.dqb_curinodes)) {
+ scan_data->usage_is_inconsistent = 1;
+ fprintf(stderr, "[QUOTA WARNING] Usage inconsistent for ID %u:"
+ "actual (%lld, %lld) != expected (%lld, %lld)\n",
+ dq->dq_id, (long long) dq->dq_dqb.dqb_curspace,
+ (long long) dq->dq_dqb.dqb_curinodes,
+ (long long) dquot->dq_dqb.dqb_curspace,
+ (long long) dquot->dq_dqb.dqb_curinodes);
+ }
+
+ if (scan_data->update_limits) {
+ dq->dq_dqb.dqb_ihardlimit = dquot->dq_dqb.dqb_ihardlimit;
+ dq->dq_dqb.dqb_isoftlimit = dquot->dq_dqb.dqb_isoftlimit;
+ dq->dq_dqb.dqb_bhardlimit = dquot->dq_dqb.dqb_bhardlimit;
+ dq->dq_dqb.dqb_bsoftlimit = dquot->dq_dqb.dqb_bsoftlimit;
+ }
+
+ if (scan_data->update_usage) {
+ dq->dq_dqb.dqb_curspace = dquot->dq_dqb.dqb_curspace;
+ dq->dq_dqb.dqb_curinodes = dquot->dq_dqb.dqb_curinodes;
+ }
+
+ return 0;
+}
+
+/*
+ * Read quotas from disk and updates the in-memory information determined by
+ * 'flags' from the on-disk data.
+ */
+errcode_t quota_read_all_dquots(quota_ctx_t qctx, ext2_ino_t qf_ino,
+ enum quota_type qtype, unsigned int flags)
+{
+ struct scan_dquots_data scan_data;
+ struct quota_handle *qh;
+ errcode_t err;
+
+ if (!qctx)
+ return 0;
+
+ err = ext2fs_get_mem(sizeof(struct quota_handle), &qh);
+ if (err) {
+ log_debug("Unable to allocate quota handle");
+ return err;
+ }
+
+ err = quota_file_open(qctx, qh, qf_ino, qtype, -1, 0);
+ if (err) {
+ log_debug("Open quota file failed");
+ goto out;
+ }
+
+ scan_data.quota_dict = qctx->quota_dict[qh->qh_type];
+ scan_data.check_consistency = 0;
+ scan_data.update_limits = !!(flags & QREAD_LIMITS);
+ scan_data.update_usage = !!(flags & QREAD_USAGE);
+ qh->qh_ops->scan_dquots(qh, scan_dquots_callback, &scan_data);
+
+ err = quota_file_close(qctx, qh);
+ if (err) {
+ log_debug("Cannot finish IO on new quotafile: %s",
+ strerror(errno));
+ if (qh->qh_qf.e2_file)
+ ext2fs_file_close(qh->qh_qf.e2_file);
+ }
+out:
+ ext2fs_free_mem(&qh);
+ return err;
+}
+
+/*
+ * Compares the measured quota in qctx->quota_dict with that in the quota inode
+ * on disk and updates the limits in qctx->quota_dict. 'usage_inconsistent' is
+ * set to 1 if the supplied and on-disk quota usage values are not identical.
+ */
+errcode_t quota_compare_and_update(quota_ctx_t qctx, enum quota_type qtype,
+ int *usage_inconsistent)
+{
+ struct quota_handle qh;
+ struct scan_dquots_data scan_data;
+ struct dquot *dq;
+ dnode_t *n;
+ dict_t *dict = qctx->quota_dict[qtype];
+ errcode_t err = 0;
+
+ if (!dict)
+ goto out;
+
+ err = quota_file_open(qctx, &qh, 0, qtype, -1, 0);
+ if (err) {
+ log_debug("Open quota file failed");
+ goto out;
+ }
+
+ scan_data.quota_dict = qctx->quota_dict[qtype];
+ scan_data.update_limits = 1;
+ scan_data.update_usage = 0;
+ scan_data.check_consistency = 1;
+ scan_data.usage_is_inconsistent = 0;
+ err = qh.qh_ops->scan_dquots(&qh, scan_dquots_callback, &scan_data);
+ if (err) {
+ log_debug("Error scanning dquots");
+ *usage_inconsistent = 1;
+ goto out_close_qh;
+ }
+
+ for (n = dict_first(dict); n; n = dict_next(dict, n)) {
+ dq = dnode_get(n);
+ if (!dq)
+ continue;
+ if ((dq->dq_flags & DQF_SEEN) == 0) {
+ fprintf(stderr, "[QUOTA WARNING] "
+ "Missing quota entry ID %d\n", dq->dq_id);
+ scan_data.usage_is_inconsistent = 1;
+ }
+ }
+ *usage_inconsistent = scan_data.usage_is_inconsistent;
+
+out_close_qh:
+ err = quota_file_close(qctx, &qh);
+ if (err) {
+ log_debug("Cannot close quotafile: %s", error_message(errno));
+ if (qh.qh_qf.e2_file)
+ ext2fs_file_close(qh.qh_qf.e2_file);
+ }
+out:
+ return err;
+}
+
+int parse_quota_opts(const char *opts, int (*func)(char *))
+{
+ char *buf, *token, *next, *p;
+ int len;
+ int ret = 0;
+
+ len = strlen(opts);
+ buf = malloc(len + 1);
+ if (!buf) {
+ fprintf(stderr,
+ "Couldn't allocate memory to parse quota options!\n");
+ return -ENOMEM;
+ }
+ strcpy(buf, opts);
+ for (token = buf; token && *token; token = next) {
+ p = strchr(token, ',');
+ next = 0;
+ if (p) {
+ *p = 0;
+ next = p + 1;
+ }
+ ret = func(token);
+ if (ret)
+ break;
+ }
+ free(buf);
+ return ret;
+}
diff --git a/lib/support/nls-enable.h b/lib/support/nls-enable.h
new file mode 100644
index 0000000..2f62c01
--- /dev/null
+++ b/lib/support/nls-enable.h
@@ -0,0 +1,21 @@
+#if defined(ENABLE_NLS) && !defined(DEBUGFS)
+#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
diff --git a/lib/support/parse_qtype.c b/lib/support/parse_qtype.c
new file mode 100644
index 0000000..d8df07d
--- /dev/null
+++ b/lib/support/parse_qtype.c
@@ -0,0 +1,90 @@
+/*
+ * parse_qtype.c
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <errno.h>
+#include <string.h>
+#include <fcntl.h>
+
+#include "quotaio.h"
+
+#define PARSE_DELIM ":,"
+
+int parse_quota_types(const char *in_str, unsigned int *qtype_bits,
+ char **err_token)
+{
+ char *buf, *token, *next, *tmp;
+ unsigned int qtype = *qtype_bits;
+ int len, ret = 0;
+
+ if (!in_str)
+ return 0;
+
+ len = strlen(in_str);
+ buf = malloc(len + 1);
+ if (!buf)
+ return ENOMEM;
+ strcpy(buf, in_str);
+
+ for (token = buf, next = strtok_r(buf, PARSE_DELIM, &tmp);
+ token && *token; token = next) {
+ int not = 0;
+ char *p = token;
+
+ if (*p == '^') {
+ not = 1;
+ p++;
+ }
+ if (!strcmp(p, "usr") || !strcmp(p, "usrquota")) {
+ if (not)
+ qtype &= ~QUOTA_USR_BIT;
+ else
+ qtype |= QUOTA_USR_BIT;
+ } else if (!strcmp(p, "grp") || !strcmp(p, "grpquota")) {
+ if (not)
+ qtype &= ~QUOTA_GRP_BIT;
+ else
+ qtype |= QUOTA_GRP_BIT;
+ } else if (!strcmp(p, "prj") || !strcmp(p, "prjquota")) {
+ if (not)
+ qtype &= ~QUOTA_PRJ_BIT;
+ else
+ qtype |= QUOTA_PRJ_BIT;
+ } else {
+ if (err_token) {
+ *err_token = malloc(strlen(token) + 1);
+ if (*err_token)
+ strcpy(*err_token, token);
+ }
+ ret = EINVAL;
+ goto errout;
+ }
+#ifdef DEBUG_PROGRAM
+ printf("word: %s\n", token);
+#endif
+ next = strtok_r(NULL, PARSE_DELIM, &tmp);
+ }
+ *qtype_bits = qtype;
+errout:
+ free(buf);
+ return ret;
+}
+
+#ifdef DEBUG_PROGRAM
+int main(int argc, char **argv)
+{
+ unsigned int qtype_bits = 0;
+ int ret;
+ char *err_token = 0;
+
+ ret = parse_quota_types(argv[1], &qtype_bits, &err_token);
+ printf("parse_quota_types returns %d, %d\n", ret, qtype_bits);
+ if (err_token)
+ printf("err_token is %s\n", err_token);
+ return 0;
+}
+#endif
diff --git a/lib/support/plausible.c b/lib/support/plausible.c
new file mode 100644
index 0000000..65a6b2e
--- /dev/null
+++ b/lib/support/plausible.c
@@ -0,0 +1,287 @@
+/*
+ * plausible.c --- Figure out if a pathname is ext* or something else.
+ *
+ * Copyright 2014, Oracle, Inc.
+ *
+ * Some parts are:
+ * Copyright 1995, 1996, 1997, 1998, 1999, 2000 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef _LARGEFILE_SOURCE
+#define _LARGEFILE_SOURCE
+#endif
+#ifndef _LARGEFILE64_SOURCE
+#define _LARGEFILE64_SOURCE
+#endif
+
+#include "config.h"
+#include <fcntl.h>
+#include <time.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#ifdef HAVE_MAGIC_H
+#include <magic.h>
+#endif
+#include "plausible.h"
+#include "ext2fs/ext2fs.h"
+#include "nls-enable.h"
+#include "blkid/blkid.h"
+
+#ifdef HAVE_MAGIC_H
+static magic_t (*dl_magic_open)(int);
+static const char *(*dl_magic_file)(magic_t, const char *);
+static int (*dl_magic_load)(magic_t, const char *);
+static void (*dl_magic_close)(magic_t);
+
+/*
+ * NO_CHECK functionality was only added in file 4.20.
+ * Older systems like RHEL 5.x still have file 4.17
+ */
+#ifndef MAGIC_NO_CHECK_COMPRESS
+#define MAGIC_NO_CHECK_COMPRESS 0x0001000
+#endif
+#ifndef MAGIC_NO_CHECK_ELF
+#define MAGIC_NO_CHECK_ELF 0x0010000
+#endif
+
+#ifdef HAVE_DLOPEN
+#include <dlfcn.h>
+
+static void *magic_handle;
+
+static int magic_library_available(void)
+{
+ if (!magic_handle) {
+ magic_handle = dlopen("libmagic.so.1", RTLD_NOW);
+ if (!magic_handle)
+ return 0;
+
+ dl_magic_open = (magic_t (*)(int))
+ dlsym(magic_handle, "magic_open");
+ dl_magic_file = (const char *(*)(magic_t, const char *))
+ dlsym(magic_handle, "magic_file");
+ dl_magic_load = (int (*)(magic_t, const char *))
+ dlsym(magic_handle, "magic_load");
+ dl_magic_close = (void (*)(magic_t))
+ dlsym(magic_handle, "magic_close");
+ }
+
+ if (!dl_magic_open || !dl_magic_file ||
+ !dl_magic_load || !dl_magic_close)
+ return 0;
+ return 1;
+}
+#else
+static int magic_library_available(void)
+{
+ dl_magic_open = magic_open;
+ dl_magic_file = magic_file;
+ dl_magic_load = magic_load;
+ dl_magic_close = magic_close;
+
+ return 1;
+}
+#endif
+#endif
+
+static void print_ext2_info(const char *device)
+
+{
+ struct ext2_super_block *sb;
+ ext2_filsys fs;
+ errcode_t retval;
+ time_t tm;
+
+ retval = ext2fs_open2(device, 0, EXT2_FLAG_64BITS, 0, 0,
+ default_io_manager, &fs);
+ if (retval)
+ return;
+ sb = fs->super;
+
+ if (sb->s_mtime) {
+ tm = sb->s_mtime;
+ if (sb->s_last_mounted[0])
+ printf(_("\tlast mounted on %.*s on %s"),
+ EXT2_LEN_STR(sb->s_last_mounted), ctime(&tm));
+ else
+ printf(_("\tlast mounted on %s"), ctime(&tm));
+ } else if (sb->s_mkfs_time) {
+ tm = sb->s_mkfs_time;
+ printf(_("\tcreated on %s"), ctime(&tm));
+ } else if (sb->s_wtime) {
+ tm = sb->s_wtime;
+ printf(_("\tlast modified on %s"), ctime(&tm));
+ }
+ ext2fs_close_free(&fs);
+}
+
+/*
+ * return 1 if there is no partition table, 0 if a partition table is
+ * detected, and -1 on an error.
+ */
+#ifdef HAVE_BLKID_PROBE_ENABLE_PARTITIONS
+static int check_partition_table(const char *device)
+{
+ blkid_probe pr;
+ const char *value;
+ int ret;
+
+ pr = blkid_new_probe_from_filename(device);
+ if (!pr)
+ return -1;
+
+ ret = blkid_probe_enable_partitions(pr, 1);
+ if (ret < 0)
+ goto errout;
+
+ ret = blkid_probe_enable_superblocks(pr, 0);
+ if (ret < 0)
+ goto errout;
+
+ ret = blkid_do_fullprobe(pr);
+ if (ret < 0)
+ goto errout;
+
+ ret = blkid_probe_lookup_value(pr, "PTTYPE", &value, NULL);
+ if (ret == 0)
+ fprintf(stderr, _("Found a %s partition table in %s\n"),
+ value, device);
+ else
+ ret = 1;
+
+errout:
+ blkid_free_probe(pr);
+ return ret;
+}
+#else
+static int check_partition_table(const char *device EXT2FS_ATTR((unused)))
+{
+ return -1;
+}
+#endif
+
+/*
+ * return 1 if the device looks plausible, creating the file if necessary
+ */
+int check_plausibility(const char *device, int flags, int *ret_is_dev)
+{
+ int fd, ret, is_dev = 0;
+ ext2fs_struct_stat s;
+ int fl = O_RDONLY;
+ blkid_cache cache = NULL;
+ char *fs_type = NULL;
+ char *fs_label = NULL;
+
+ fd = ext2fs_open_file(device, fl, 0666);
+ if ((fd < 0) && (errno == ENOENT) && (flags & NO_SIZE)) {
+ fprintf(stderr, _("The file %s does not exist and no "
+ "size was specified.\n"), device);
+ exit(1);
+ }
+ if ((fd < 0) && (errno == ENOENT) && (flags & CREATE_FILE)) {
+ fl |= O_CREAT;
+ fd = ext2fs_open_file(device, fl, 0666);
+ if (fd >= 0 && (flags & VERBOSE_CREATE))
+ printf(_("Creating regular file %s\n"), device);
+ }
+ if (fd < 0) {
+ fprintf(stderr, _("Could not open %s: %s\n"),
+ device, error_message(errno));
+ if (errno == ENOENT)
+ fputs(_("\nThe device apparently does not exist; "
+ "did you specify it correctly?\n"), stderr);
+ exit(1);
+ }
+
+ if (ext2fs_fstat(fd, &s) < 0) {
+ perror("stat");
+ exit(1);
+ }
+ close(fd);
+
+ if (S_ISBLK(s.st_mode))
+ is_dev = 1;
+#if defined(__FreeBSD__) || defined(__FreeBSD_kernel__)
+ /* On FreeBSD, all disk devices are character specials */
+ if (S_ISCHR(s.st_mode))
+ is_dev = 1;
+#endif
+ if (ret_is_dev)
+ *ret_is_dev = is_dev;
+
+ if ((flags & CHECK_BLOCK_DEV) && !is_dev) {
+ printf(_("%s is not a block special device.\n"), device);
+ return 0;
+ }
+
+ /*
+ * Note: we use the older-style blkid API's here because we
+ * want as much functionality to be available when using the
+ * internal blkid library, when e2fsprogs is compiled for
+ * non-Linux systems that will probably not have the libraries
+ * from util-linux available. We only use the newer
+ * blkid-probe interfaces to access functionality not
+ * available in the original blkid library.
+ */
+ if ((flags & CHECK_FS_EXIST) && blkid_get_cache(&cache, NULL) >= 0) {
+ fs_type = blkid_get_tag_value(cache, "TYPE", device);
+ if (fs_type)
+ fs_label = blkid_get_tag_value(cache, "LABEL", device);
+ blkid_put_cache(cache);
+ }
+
+ if (fs_type) {
+ if (fs_label)
+ printf(_("%s contains a %s file system labelled '%s'\n"),
+ device, fs_type, fs_label);
+ else
+ printf(_("%s contains a %s file system\n"), device,
+ fs_type);
+ if (strncmp(fs_type, "ext", 3) == 0)
+ print_ext2_info(device);
+ free(fs_type);
+ free(fs_label);
+ return 0;
+ }
+
+#ifdef HAVE_MAGIC_H
+ if ((flags & CHECK_FS_EXIST) &&
+ !getenv("E2FSPROGS_LIBMAGIC_SUPPRESS") &&
+ magic_library_available()) {
+ const char *msg;
+ magic_t mag;
+ int has_magic = 0;
+
+ mag = dl_magic_open(MAGIC_RAW | MAGIC_SYMLINK | MAGIC_DEVICES |
+ MAGIC_ERROR | MAGIC_NO_CHECK_ELF |
+ MAGIC_NO_CHECK_COMPRESS);
+ dl_magic_load(mag, NULL);
+
+ msg = dl_magic_file(mag, device);
+ if (msg && strcmp(msg, "data") && strcmp(msg, "empty")) {
+ printf(_("%s contains `%s' data\n"), device, msg);
+ has_magic = 1;
+ }
+
+ dl_magic_close(mag);
+ return !has_magic;
+ }
+#endif
+ if (flags & CHECK_FS_EXIST) {
+ ret = check_partition_table(device);
+ if (ret >= 0)
+ return ret;
+ }
+ return 1;
+}
+
diff --git a/lib/support/plausible.h b/lib/support/plausible.h
new file mode 100644
index 0000000..b85150c
--- /dev/null
+++ b/lib/support/plausible.h
@@ -0,0 +1,29 @@
+/*
+ * plausible.h --- header file defining prototypes for helper functions
+ * used by tune2fs and mke2fs
+ *
+ * Copyright 2014 by Oracle, Inc.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#ifndef PLAUSIBLE_H_
+#define PLAUSIBLE_H_
+
+/*
+ * Flags for check_plausibility()
+ */
+#define CHECK_BLOCK_DEV 0x0001
+#define CREATE_FILE 0x0002
+#define CHECK_FS_EXIST 0x0004
+#define VERBOSE_CREATE 0x0008
+#define NO_SIZE 0x0010
+#define QUIET_CHECK 0x0020
+
+extern int check_plausibility(const char *device, int flags,
+ int *ret_is_dev);
+
+#endif /* PLAUSIBLE_H_ */
diff --git a/lib/support/print_fs_flags.c b/lib/support/print_fs_flags.c
new file mode 100644
index 0000000..f47cd66
--- /dev/null
+++ b/lib/support/print_fs_flags.c
@@ -0,0 +1,75 @@
+/*
+ * print_flags.c - Print ext2_filsys flags
+ *
+ * Copyright (C) 1993, 1994 Remy Card <card@masi.ibp.fr>
+ * Laboratoire MASI, Institut Blaise Pascal
+ * Universite Pierre et Marie Curie (Paris VI)
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Library
+ * General Public License, version 2.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdio.h>
+
+#include "ext2fs/ext2fs.h"
+
+struct flags_name {
+ unsigned long flag;
+ const char *name;
+};
+
+static const struct flags_name flags_array[] = {
+ { EXT2_FLAG_RW, "EXT2_FLAG_RW" },
+ { EXT2_FLAG_CHANGED, "EXT2_FLAG_CHANGED" },
+ { EXT2_FLAG_DIRTY, "EXT2_FLAG_DIRTY" },
+ { EXT2_FLAG_VALID, "EXT2_FLAG_VALID" },
+ { EXT2_FLAG_IB_DIRTY, "EXT2_FLAG_IB_DIRTY" },
+ { EXT2_FLAG_BB_DIRTY, "EXT2_FLAG_BB_DIRTY" },
+ { EXT2_FLAG_SWAP_BYTES, "EXT2_FLAG_SWAP_BYTES" },
+ { EXT2_FLAG_SWAP_BYTES_READ, "EXT2_FLAG_SWAP_BYTES_READ" },
+ { EXT2_FLAG_SWAP_BYTES_WRITE, "EXT2_FLAG_SWAP_BYTES_WRITE" },
+ { EXT2_FLAG_MASTER_SB_ONLY, "EXT2_FLAG_MASTER_SB_ONLY" },
+ { EXT2_FLAG_FORCE, "EXT2_FLAG_FORCE" },
+ { EXT2_FLAG_SUPER_ONLY, "EXT2_FLAG_SUPER_ONLY" },
+ { EXT2_FLAG_JOURNAL_DEV_OK, "EXT2_FLAG_JOURNAL_DEV_OK" },
+ { EXT2_FLAG_IMAGE_FILE, "EXT2_FLAG_IMAGE_FILE" },
+ { EXT2_FLAG_EXCLUSIVE, "EXT2_FLAG_EXCLUSIVE" },
+ { EXT2_FLAG_SOFTSUPP_FEATURES, "EXT2_FLAG_SOFTSUPP_FEATURES" },
+ { EXT2_FLAG_NOFREE_ON_ERROR, "EXT2_FLAG_NOFREE_ON_ERROR" },
+ { EXT2_FLAG_64BITS, "EXT2_FLAG_64BITS" },
+ { EXT2_FLAG_PRINT_PROGRESS, "EXT2_FLAG_PRINT_PROGRESS" },
+ { EXT2_FLAG_DIRECT_IO, "EXT2_FLAG_DIRECT_IO" },
+ { EXT2_FLAG_SKIP_MMP, "EXT2_FLAG_SKIP_MMP" },
+ { EXT2_FLAG_IGNORE_CSUM_ERRORS, "EXT2_FLAG_IGNORE_CSUM_ERRORS" },
+ { EXT2_FLAG_SHARE_DUP, "EXT2_FLAG_SHARE_DUP" },
+ { EXT2_FLAG_IGNORE_SB_ERRORS, "EXT2_FLAG_IGNORE_SB_ERRORS" },
+ { EXT2_FLAG_BBITMAP_TAIL_PROBLEM, "EXT2_FLAG_BBITMAP_TAIL_PROBLEM" },
+ { EXT2_FLAG_IBITMAP_TAIL_PROBLEM, "EXT2_FLAG_IBITMAP_TAIL_PROBLEM" },
+ { EXT2_FLAG_THREADS, "EXT2_FLAG_THREADS" },
+ { 0, NULL },
+};
+
+void print_fs_flags(FILE * f, unsigned long flags)
+{
+ const struct flags_name *fp;
+ int first = 1, pos = 16;
+
+ for (fp = flags_array; fp->flag != 0; fp++) {
+ if ((flags & fp->flag) == 0)
+ continue;
+ pos += strlen(fp->name) + 1;
+ if (pos > 72) {
+ fputs("\n\t", f);
+ pos = 9 + strlen(fp->name);
+ }
+ if (first)
+ first = 0;
+ else
+ fputc(' ', f);
+ fputs(fp->name, f);
+ }
+ fputc('\n', f);
+}
diff --git a/lib/support/print_fs_flags.h b/lib/support/print_fs_flags.h
new file mode 100644
index 0000000..bdd58a0
--- /dev/null
+++ b/lib/support/print_fs_flags.h
@@ -0,0 +1,5 @@
+/*
+ * print_flags.h -- header file for printing the fs flags
+ */
+
+void print_fs_flags(FILE * f, unsigned long flags);
diff --git a/lib/support/prof_err.et b/lib/support/prof_err.et
new file mode 100644
index 0000000..c9316c7
--- /dev/null
+++ b/lib/support/prof_err.et
@@ -0,0 +1,66 @@
+error_table prof
+
+error_code PROF_VERSION, "Profile version 0.0"
+
+#
+# generated by prof_tree.c
+#
+error_code PROF_MAGIC_NODE, "Bad magic value in profile_node"
+error_code PROF_NO_SECTION, "Profile section not found"
+error_code PROF_NO_RELATION, "Profile relation not found"
+error_code PROF_ADD_NOT_SECTION,
+ "Attempt to add a relation to node which is not a section"
+error_code PROF_SECTION_WITH_VALUE,
+ "A profile section header has a non-zero value"
+error_code PROF_BAD_LINK_LIST, "Bad linked list in profile structures"
+error_code PROF_BAD_GROUP_LVL, "Bad group level in profile structures"
+error_code PROF_BAD_PARENT_PTR,
+ "Bad parent pointer in profile structures"
+error_code PROF_MAGIC_ITERATOR, "Bad magic value in profile iterator"
+error_code PROF_SET_SECTION_VALUE, "Can't set value on section node"
+error_code PROF_EINVAL, "Invalid argument passed to profile library"
+error_code PROF_READ_ONLY, "Attempt to modify read-only profile"
+
+#
+# generated by prof_parse.c
+#
+
+error_code PROF_SECTION_NOTOP, "Profile section header not at top level"
+error_code PROF_SECTION_SYNTAX, "Syntax error in profile section header"
+error_code PROF_RELATION_SYNTAX, "Syntax error in profile relation"
+error_code PROF_EXTRA_CBRACE, "Extra closing brace in profile"
+error_code PROF_MISSING_OBRACE, "Missing open brace in profile"
+
+#
+# generated by prof_init.c
+#
+error_code PROF_MAGIC_PROFILE, "Bad magic value in profile_t"
+error_code PROF_MAGIC_SECTION, "Bad magic value in profile_section_t"
+error_code PROF_TOPSECTION_ITER_NOSUPP,
+ "Iteration through all top level section not supported"
+error_code PROF_INVALID_SECTION, "Invalid profile_section object"
+error_code PROF_END_OF_SECTIONS, "No more sections"
+error_code PROF_BAD_NAMESET, "Bad nameset passed to query routine"
+error_code PROF_NO_PROFILE, "No profile file open"
+
+#
+# generated by prof_file.c
+#
+error_code PROF_MAGIC_FILE, "Bad magic value in profile_file_t"
+error_code PROF_FAIL_OPEN, "Couldn't open profile file"
+
+#
+# generated by prof_set.c
+#
+error_code PROF_EXISTS, "Section already exists"
+
+#
+# generated by prof_get.c
+#
+error_code PROF_BAD_BOOLEAN, "Invalid boolean value"
+error_code PROF_BAD_INTEGER, "Invalid integer value"
+
+error_code PROF_MAGIC_FILE_DATA, "Bad magic value in profile_file_data_t"
+
+
+end
diff --git a/lib/support/profile.c b/lib/support/profile.c
new file mode 100644
index 0000000..bdb14b1
--- /dev/null
+++ b/lib/support/profile.c
@@ -0,0 +1,1910 @@
+/*
+ * profile.c -- A simple configuration file parsing "library in a file"
+ *
+ * The profile library was originally written by Theodore Ts'o in 1995
+ * for use in the MIT Kerberos v5 library. It has been
+ * modified/enhanced/bug-fixed over time by other members of the MIT
+ * Kerberos team. This version was originally taken from the Kerberos
+ * v5 distribution, version 1.4.2, and radically simplified for use in
+ * e2fsprogs. (Support for locking for multi-threaded operations,
+ * being able to modify and update the configuration file
+ * programmatically, and Mac/Windows portability have been removed.
+ * It has been folded into a single C source file to make it easier to
+ * fold into an application program.)
+ *
+ * Copyright (C) 2005, 2006 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ *
+ * Copyright (C) 1985-2005 by the Massachusetts Institute of Technology.
+ *
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original MIT software.
+ * M.I.T. makes no representations about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ *
+ */
+
+#include "config.h"
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+#include <stdio.h>
+#ifdef HAVE_STDLIB_H
+#include <stdlib.h>
+#endif
+#include <time.h>
+#include <string.h>
+#include <strings.h>
+#include <errno.h>
+#include <ctype.h>
+#include <limits.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <dirent.h>
+#ifdef HAVE_PWD_H
+#include <pwd.h>
+#endif
+
+#include <et/com_err.h>
+#include "profile.h"
+#include "prof_err.h"
+
+#undef STAT_ONCE_PER_SECOND
+#undef HAVE_STAT
+
+/*
+ * prof_int.h
+ */
+
+typedef long prf_magic_t;
+
+/*
+ * This is the structure which stores the profile information for a
+ * particular configuration file.
+ */
+struct _prf_file_t {
+ prf_magic_t magic;
+ char *filespec;
+#ifdef STAT_ONCE_PER_SECOND
+ time_t last_stat;
+#endif
+ time_t timestamp; /* time tree was last updated from file */
+ int flags; /* r/w, dirty */
+ int upd_serial; /* incremented when data changes */
+ struct profile_node *root;
+ struct _prf_file_t *next;
+};
+
+typedef struct _prf_file_t *prf_file_t;
+
+/*
+ * The profile flags
+ */
+#define PROFILE_FILE_RW 0x0001
+#define PROFILE_FILE_DIRTY 0x0002
+#define PROFILE_FILE_NO_RELOAD 0x0004
+
+/*
+ * This structure defines the high-level, user visible profile_t
+ * object, which is used as a handle by users who need to query some
+ * configuration file(s)
+ */
+struct _profile_t {
+ prf_magic_t magic;
+ prf_file_t first_file;
+};
+
+/*
+ * Used by the profile iterator in prof_get.c
+ */
+#define PROFILE_ITER_LIST_SECTION 0x0001
+#define PROFILE_ITER_SECTIONS_ONLY 0x0002
+#define PROFILE_ITER_RELATIONS_ONLY 0x0004
+
+#define PROFILE_ITER_FINAL_SEEN 0x0100
+
+/*
+ * Check if a filespec is last in a list (NULL on UNIX, invalid FSSpec on MacOS
+ */
+
+#define PROFILE_LAST_FILESPEC(x) (((x) == NULL) || ((x)[0] == '\0'))
+
+struct profile_node {
+ errcode_t magic;
+ char *name;
+ char *value;
+ int group_level;
+ unsigned int final:1; /* Indicate don't search next file */
+ unsigned int deleted:1;
+ struct profile_node *first_child;
+ struct profile_node *parent;
+ struct profile_node *next, *prev;
+};
+
+#define CHECK_MAGIC(node) \
+ if ((node)->magic != PROF_MAGIC_NODE) \
+ return PROF_MAGIC_NODE;
+
+/* profile parser declarations */
+struct parse_state {
+ int state;
+ int group_level;
+ int line_num;
+ struct profile_node *root_section;
+ struct profile_node *current_section;
+};
+
+static const char *default_filename = "<default>";
+
+static profile_syntax_err_cb_t syntax_err_cb;
+
+static errcode_t parse_line(char *line, struct parse_state *state);
+
+#ifdef DEBUG_PROGRAM
+static errcode_t profile_write_tree_file
+ (struct profile_node *root, FILE *dstfile);
+
+static errcode_t profile_write_tree_to_buffer
+ (struct profile_node *root, char **buf);
+#endif
+
+
+static void profile_free_node
+ (struct profile_node *relation);
+
+static errcode_t profile_create_node
+ (const char *name, const char *value,
+ struct profile_node **ret_node);
+
+#ifdef DEBUG_PROGRAM
+static errcode_t profile_verify_node
+ (struct profile_node *node);
+#endif
+
+static errcode_t profile_add_node
+ (struct profile_node *section,
+ const char *name, const char *value,
+ struct profile_node **ret_node);
+
+static errcode_t profile_find_node
+ (struct profile_node *section,
+ const char *name, const char *value,
+ int section_flag, void **state,
+ struct profile_node **node);
+
+static errcode_t profile_node_iterator
+ (void **iter_p, struct profile_node **ret_node,
+ char **ret_name, char **ret_value);
+
+static errcode_t profile_open_file
+ (const char * file, prf_file_t *ret_prof);
+
+static errcode_t profile_update_file
+ (prf_file_t prf);
+
+static void profile_free_file
+ (prf_file_t profile);
+
+static errcode_t profile_get_value(profile_t profile, const char *name,
+ const char *subname, const char *subsubname,
+ const char **ret_value);
+
+
+/*
+ * prof_init.c --- routines that manipulate the user-visible profile_t
+ * object.
+ */
+
+static int compstr(const void *m1, const void *m2)
+{
+ const char *s1 = *((const char * const *) m1);
+ const char *s2 = *((const char * const *) m2);
+
+ return strcmp(s1, s2);
+}
+
+static void free_list(char **list)
+{
+ char **cp;
+
+ if (list == 0)
+ return;
+
+ for (cp = list; *cp; cp++)
+ free(*cp);
+ free(list);
+}
+
+static errcode_t get_dirlist(const char *dirname, char***ret_array)
+{
+ DIR *dir;
+ struct dirent *de;
+ struct stat st;
+ errcode_t retval;
+ char *fn, *cp;
+ char **array = 0, **new_array;
+ int max = 0, num = 0;
+
+ dir = opendir(dirname);
+ if (!dir)
+ return errno;
+
+ while ((de = readdir(dir)) != NULL) {
+ for (cp = de->d_name; *cp; cp++) {
+ if (!isalnum(*cp) &&
+ (*cp != '-') &&
+ (*cp != '_'))
+ break;
+ }
+ if (*cp)
+ continue;
+ fn = malloc(strlen(dirname) + strlen(de->d_name) + 2);
+ if (!fn) {
+ retval = ENOMEM;
+ goto errout;
+ }
+ sprintf(fn, "%s/%s", dirname, de->d_name);
+ if ((stat(fn, &st) < 0) || !S_ISREG(st.st_mode)) {
+ free(fn);
+ continue;
+ }
+ if (num >= max) {
+ max += 10;
+ new_array = realloc(array, sizeof(char *) * (max+1));
+ if (!new_array) {
+ retval = ENOMEM;
+ free(fn);
+ goto errout;
+ }
+ array = new_array;
+ }
+ array[num++] = fn;
+ }
+ if (array) {
+ qsort(array, num, sizeof(char *), compstr);
+ array[num++] = 0;
+ }
+ *ret_array = array;
+ closedir(dir);
+ return 0;
+errout:
+ if (array)
+ array[num] = 0;
+ closedir(dir);
+ free_list(array);
+ return retval;
+}
+
+errcode_t
+profile_init(const char * const *files, profile_t *ret_profile)
+{
+ const char * const *fs;
+ profile_t profile;
+ prf_file_t new_file, *last;
+ errcode_t retval = 0;
+ char **cpp, *cp, **array = 0;
+
+ profile = malloc(sizeof(struct _profile_t));
+ if (!profile)
+ return ENOMEM;
+ memset(profile, 0, sizeof(struct _profile_t));
+ profile->magic = PROF_MAGIC_PROFILE;
+ last = &profile->first_file;
+
+ /* if the filenames list is not specified return an empty profile */
+ if ( files ) {
+ for (fs = files; !PROFILE_LAST_FILESPEC(*fs); fs++) {
+ if (array)
+ free_list(array);
+ array = NULL;
+ retval = get_dirlist(*fs, &array);
+ if (retval == 0) {
+ if (!array)
+ continue;
+ for (cpp = array; (cp = *cpp); cpp++) {
+ retval = profile_open_file(cp, &new_file);
+ if (retval == EACCES)
+ continue;
+ if (retval)
+ goto errout;
+ *last = new_file;
+ last = &new_file->next;
+ }
+ } else if ((retval != ENOTDIR) &&
+ strcmp(*fs, default_filename))
+ goto errout;
+
+ retval = profile_open_file(*fs, &new_file);
+ /* if this file is missing, skip to the next */
+ if (retval == ENOENT || retval == EACCES) {
+ continue;
+ }
+ if (retval)
+ goto errout;
+ *last = new_file;
+ last = &new_file->next;
+ }
+ /*
+ * If all the files were not found, return the appropriate error.
+ */
+ if (!profile->first_file) {
+ retval = ENOENT;
+ goto errout;
+ }
+ }
+
+ free_list(array);
+ *ret_profile = profile;
+ return 0;
+errout:
+ free_list(array);
+ profile_release(profile);
+ return retval;
+}
+
+void
+profile_release(profile_t profile)
+{
+ prf_file_t p, next;
+
+ if (!profile || profile->magic != PROF_MAGIC_PROFILE)
+ return;
+
+ for (p = profile->first_file; p; p = next) {
+ next = p->next;
+ profile_free_file(p);
+ }
+ profile->magic = 0;
+ free(profile);
+}
+
+/*
+ * This function sets the value of the pseudo file "<default>". If
+ * the file "<default>" had previously been passed to profile_init(),
+ * then def_string parameter will be parsed and used as the profile
+ * information for the "<default>" file.
+ */
+errcode_t profile_set_default(profile_t profile, const char *def_string)
+{
+ struct parse_state state;
+ prf_file_t prf;
+ errcode_t retval;
+ const char *in;
+ char *line, *p, *end;
+ int line_size, len;
+
+ if (!def_string || !profile || profile->magic != PROF_MAGIC_PROFILE)
+ return PROF_MAGIC_PROFILE;
+
+ for (prf = profile->first_file; prf; prf = prf->next) {
+ if (strcmp(prf->filespec, default_filename) == 0)
+ break;
+ }
+ if (!prf)
+ return 0;
+
+ if (prf->root) {
+ profile_free_node(prf->root);
+ prf->root = 0;
+ }
+
+ memset(&state, 0, sizeof(struct parse_state));
+ retval = profile_create_node("(root)", 0, &state.root_section);
+ if (retval)
+ return retval;
+
+ line = 0;
+ line_size = 0;
+ in = def_string;
+ while (*in) {
+ end = strchr(in, '\n');
+ len = end ? (end - in) : (int) strlen(in);
+ if (len >= line_size) {
+ line_size = len+1;
+ p = realloc(line, line_size);
+ if (!p) {
+ retval = ENOMEM;
+ goto errout;
+ }
+ line = p;
+ }
+ memcpy(line, in, len);
+ line[len] = 0;
+ retval = parse_line(line, &state);
+ if (retval) {
+ errout:
+ if (syntax_err_cb)
+ (syntax_err_cb)(prf->filespec, retval,
+ state.line_num);
+ free(line);
+ if (prf->root)
+ profile_free_node(prf->root);
+ return retval;
+ }
+ if (!end)
+ break;
+ in = end+1;
+ }
+ prf->root = state.root_section;
+ free(line);
+
+ return 0;
+}
+
+/*
+ * prof_file.c ---- routines that manipulate an individual profile file.
+ */
+
+errcode_t profile_open_file(const char * filespec,
+ prf_file_t *ret_prof)
+{
+ prf_file_t prf;
+ errcode_t retval;
+ char *home_env = 0;
+ unsigned int len;
+ char *expanded_filename;
+
+ prf = malloc(sizeof(struct _prf_file_t));
+ if (!prf)
+ return ENOMEM;
+ memset(prf, 0, sizeof(struct _prf_file_t));
+ prf->magic = PROF_MAGIC_FILE;
+
+ len = strlen(filespec)+1;
+ if (filespec[0] == '~' && filespec[1] == '/') {
+ home_env = getenv("HOME");
+#ifdef HAVE_PWD_H
+ if (home_env == NULL) {
+#ifdef HAVE_GETWUID_R
+ struct passwd *pw, pwx;
+ uid_t uid;
+ char pwbuf[BUFSIZ];
+
+ uid = getuid();
+ if (!getpwuid_r(uid, &pwx, pwbuf, sizeof(pwbuf), &pw)
+ && pw != NULL && pw->pw_dir[0] != 0)
+ home_env = pw->pw_dir;
+#else
+ struct passwd *pw;
+
+ pw = getpwuid(getuid());
+ home_env = pw->pw_dir;
+#endif
+ }
+#endif
+ if (home_env)
+ len += strlen(home_env);
+ }
+ expanded_filename = malloc(len);
+ if (expanded_filename == 0) {
+ profile_free_file(prf);
+ return errno;
+ }
+ if (home_env) {
+ strcpy(expanded_filename, home_env);
+ strcat(expanded_filename, filespec+1);
+ } else
+ memcpy(expanded_filename, filespec, len);
+
+ prf->filespec = expanded_filename;
+
+ if (strcmp(prf->filespec, default_filename) != 0) {
+ retval = profile_update_file(prf);
+ if (retval) {
+ profile_free_file(prf);
+ return retval;
+ }
+ }
+
+ *ret_prof = prf;
+ return 0;
+}
+
+errcode_t profile_update_file(prf_file_t prf)
+{
+ errcode_t retval;
+#ifdef HAVE_STAT
+ struct stat st;
+#ifdef STAT_ONCE_PER_SECOND
+ time_t now;
+#endif
+#endif
+ FILE *f;
+ char buf[2048];
+ struct parse_state state;
+
+ if (prf->flags & PROFILE_FILE_NO_RELOAD)
+ return 0;
+
+#ifdef HAVE_STAT
+#ifdef STAT_ONCE_PER_SECOND
+ now = time(0);
+ if (now == prf->last_stat && prf->root != NULL) {
+ return 0;
+ }
+#endif
+ if (stat(prf->filespec, &st)) {
+ retval = errno;
+ return retval;
+ }
+#ifdef STAT_ONCE_PER_SECOND
+ prf->last_stat = now;
+#endif
+ if (st.st_mtime == prf->timestamp && prf->root != NULL) {
+ return 0;
+ }
+ if (prf->root) {
+ profile_free_node(prf->root);
+ prf->root = 0;
+ }
+#else
+ /*
+ * If we don't have the stat() call, assume that our in-core
+ * memory image is correct. That is, we won't reread the
+ * profile file if it changes.
+ */
+ if (prf->root) {
+ return 0;
+ }
+#endif
+ memset(&state, 0, sizeof(struct parse_state));
+ retval = profile_create_node("(root)", 0, &state.root_section);
+ if (retval)
+ return retval;
+ errno = 0;
+ f = fopen(prf->filespec, "r");
+ if (f == NULL) {
+ retval = errno;
+ if (retval == 0)
+ retval = ENOENT;
+ return retval;
+ }
+ prf->upd_serial++;
+ while (!feof(f)) {
+ if (fgets(buf, sizeof(buf), f) == NULL)
+ break;
+ retval = parse_line(buf, &state);
+ if (retval) {
+ if (syntax_err_cb)
+ (syntax_err_cb)(prf->filespec, retval,
+ state.line_num);
+ fclose(f);
+ return retval;
+ }
+ }
+ prf->root = state.root_section;
+
+ fclose(f);
+
+#ifdef HAVE_STAT
+ prf->timestamp = st.st_mtime;
+#endif
+ return 0;
+}
+
+void profile_free_file(prf_file_t prf)
+{
+ if (prf->root)
+ profile_free_node(prf->root);
+ free(prf->filespec);
+ free(prf);
+}
+
+/* Begin the profile parser */
+
+profile_syntax_err_cb_t profile_set_syntax_err_cb(profile_syntax_err_cb_t hook)
+{
+ profile_syntax_err_cb_t old;
+
+ old = syntax_err_cb;
+ syntax_err_cb = hook;
+ return(old);
+}
+
+#define STATE_INIT_COMMENT 0
+#define STATE_STD_LINE 1
+#define STATE_GET_OBRACE 2
+
+static char *skip_over_blanks(char *cp)
+{
+ while (*cp && isspace((int) (*cp)))
+ cp++;
+ return cp;
+}
+
+static int end_or_comment(char ch)
+{
+ return (ch == 0 || ch == '#' || ch == ';');
+}
+
+static char *skip_over_nonblanks(char *cp)
+{
+ while (!end_or_comment(*cp) && !isspace(*cp))
+ cp++;
+ return cp;
+}
+
+static void strip_line(char *line)
+{
+ char *p = line + strlen(line);
+ while (p > line && (p[-1] == '\n' || p[-1] == '\r'))
+ *p-- = 0;
+}
+
+static void parse_quoted_string(char *str)
+{
+ char *to, *from;
+
+ to = from = str;
+
+ for (to = from = str; *from && *from != '"'; to++, from++) {
+ if (*from == '\\') {
+ from++;
+ switch (*from) {
+ case 'n':
+ *to = '\n';
+ break;
+ case 't':
+ *to = '\t';
+ break;
+ case 'b':
+ *to = '\b';
+ break;
+ default:
+ *to = *from;
+ }
+ continue;
+ }
+ *to = *from;
+ }
+ *to = '\0';
+}
+
+static errcode_t parse_line(char *line, struct parse_state *state)
+{
+ char *cp, ch, *tag, *value;
+ char *p;
+ errcode_t retval;
+ struct profile_node *node;
+ int do_subsection = 0;
+ void *iter = 0;
+
+ state->line_num++;
+ if (state->state == STATE_GET_OBRACE) {
+ cp = skip_over_blanks(line);
+ if (*cp != '{')
+ return PROF_MISSING_OBRACE;
+ state->state = STATE_STD_LINE;
+ return 0;
+ }
+ if (state->state == STATE_INIT_COMMENT) {
+ if (line[0] != '[')
+ return 0;
+ state->state = STATE_STD_LINE;
+ }
+
+ if (*line == 0)
+ return 0;
+ strip_line(line);
+ cp = skip_over_blanks(line);
+ ch = *cp;
+ if (end_or_comment(ch))
+ return 0;
+ if (ch == '[') {
+ if (state->group_level > 0)
+ return PROF_SECTION_NOTOP;
+ cp++;
+ cp = skip_over_blanks(cp);
+ p = strchr(cp, ']');
+ if (p == NULL)
+ return PROF_SECTION_SYNTAX;
+ if (*cp == '"') {
+ cp++;
+ parse_quoted_string(cp);
+ } else {
+ *p-- = '\0';
+ while (isspace(*p) && (p > cp))
+ *p-- = '\0';
+ if (*cp == 0)
+ return PROF_SECTION_SYNTAX;
+ }
+ retval = profile_find_node(state->root_section, cp, 0, 1,
+ &iter, &state->current_section);
+ if (retval == PROF_NO_SECTION) {
+ retval = profile_add_node(state->root_section,
+ cp, 0,
+ &state->current_section);
+ if (retval)
+ return retval;
+ } else if (retval)
+ return retval;
+
+ /*
+ * Finish off the rest of the line.
+ */
+ cp = p+1;
+ if (*cp == '*') {
+ state->current_section->final = 1;
+ cp++;
+ }
+ /*
+ * Spaces or comments after ']' should not be fatal
+ */
+ cp = skip_over_blanks(cp);
+ if (!end_or_comment(*cp))
+ return PROF_SECTION_SYNTAX;
+ return 0;
+ }
+ if (ch == '}') {
+ if (state->group_level == 0)
+ return PROF_EXTRA_CBRACE;
+ if (*(cp+1) == '*')
+ state->current_section->final = 1;
+ state->current_section = state->current_section->parent;
+ state->group_level--;
+ return 0;
+ }
+ /*
+ * Parse the relations
+ */
+ tag = cp;
+ cp = strchr(cp, '=');
+ if (!cp)
+ return PROF_RELATION_SYNTAX;
+ if (cp == tag)
+ return PROF_RELATION_SYNTAX;
+ *cp = '\0';
+ if (*tag == '"') {
+ tag++;
+ parse_quoted_string(tag);
+ } else {
+ /* Look for whitespace on left-hand side. */
+ p = skip_over_nonblanks(tag);
+ if (*p)
+ *p++ = 0;
+ p = skip_over_blanks(p);
+ /* If we have more non-whitespace, it's an error. */
+ if (*p)
+ return PROF_RELATION_SYNTAX;
+ }
+
+ cp = skip_over_blanks(cp+1);
+ value = cp;
+ ch = value[0];
+ if (ch == '"') {
+ value++;
+ parse_quoted_string(value);
+ } else if (end_or_comment(ch)) {
+ do_subsection++;
+ state->state = STATE_GET_OBRACE;
+ } else if (value[0] == '{') {
+ cp = skip_over_blanks(value+1);
+ ch = *cp;
+ if (end_or_comment(ch))
+ do_subsection++;
+ else
+ return PROF_RELATION_SYNTAX;
+ } else {
+ cp = skip_over_nonblanks(value);
+ p = skip_over_blanks(cp);
+ ch = *p;
+ *cp = 0;
+ if (!end_or_comment(ch))
+ return PROF_RELATION_SYNTAX;
+ }
+ if (do_subsection) {
+ p = strchr(tag, '*');
+ if (p)
+ *p = '\0';
+ retval = profile_add_node(state->current_section,
+ tag, 0, &state->current_section);
+ if (retval)
+ return retval;
+ if (p)
+ state->current_section->final = 1;
+ state->group_level++;
+ return 0;
+ }
+ p = strchr(tag, '*');
+ if (p)
+ *p = '\0';
+ profile_add_node(state->current_section, tag, value, &node);
+ if (p)
+ node->final = 1;
+ return 0;
+}
+
+#ifdef DEBUG_PROGRAM
+/*
+ * Return TRUE if the string begins or ends with whitespace
+ */
+static int need_double_quotes(char *str)
+{
+ if (!str || !*str)
+ return 0;
+ if (isspace((int) (*str)) ||isspace((int) (*(str + strlen(str) - 1))))
+ return 1;
+ if (strchr(str, '\n') || strchr(str, '\t') || strchr(str, '\b') ||
+ strchr(str, ' ') || strchr(str, '#') || strchr(str, ';'))
+ return 1;
+ return 0;
+}
+
+/*
+ * Output a string with double quotes, doing appropriate backquoting
+ * of characters as necessary.
+ */
+static void output_quoted_string(char *str, void (*cb)(const char *,void *),
+ void *data)
+{
+ char ch;
+ char buf[2];
+
+ cb("\"", data);
+ if (!str) {
+ cb("\"", data);
+ return;
+ }
+ buf[1] = 0;
+ while ((ch = *str++)) {
+ switch (ch) {
+ case '\\':
+ cb("\\\\", data);
+ break;
+ case '\n':
+ cb("\\n", data);
+ break;
+ case '\t':
+ cb("\\t", data);
+ break;
+ case '\b':
+ cb("\\b", data);
+ break;
+ default:
+ /* This would be a lot faster if we scanned
+ forward for the next "interesting"
+ character. */
+ buf[0] = ch;
+ cb(buf, data);
+ break;
+ }
+ }
+ cb("\"", data);
+}
+
+#ifndef EOL
+#define EOL "\n"
+#endif
+
+/* Errors should be returned, not ignored! */
+static void dump_profile(struct profile_node *root, int level,
+ void (*cb)(const char *, void *), void *data)
+{
+ int i;
+ struct profile_node *p;
+ void *iter;
+ long retval;
+
+ iter = 0;
+ do {
+ retval = profile_find_node(root, 0, 0, 0, &iter, &p);
+ if (retval)
+ break;
+ for (i=0; i < level; i++)
+ cb("\t", data);
+ if (need_double_quotes(p->name))
+ output_quoted_string(p->name, cb, data);
+ else
+ cb(p->name, data);
+ cb(" = ", data);
+ if (need_double_quotes(p->value))
+ output_quoted_string(p->value, cb, data);
+ else
+ cb(p->value, data);
+ cb(EOL, data);
+ } while (iter != 0);
+
+ iter = 0;
+ do {
+ retval = profile_find_node(root, 0, 0, 1, &iter, &p);
+ if (retval)
+ break;
+ if (level == 0) { /* [xxx] */
+ cb("[", data);
+ if (need_double_quotes(p->name))
+ output_quoted_string(p->name, cb, data);
+ else
+ cb(p->name, data);
+ cb("]", data);
+ cb(p->final ? "*" : "", data);
+ cb(EOL, data);
+ dump_profile(p, level+1, cb, data);
+ cb(EOL, data);
+ } else { /* xxx = { ... } */
+ for (i=0; i < level; i++)
+ cb("\t", data);
+ if (need_double_quotes(p->name))
+ output_quoted_string(p->name, cb, data);
+ else
+ cb(p->name, data);
+ cb(" = {", data);
+ cb(EOL, data);
+ dump_profile(p, level+1, cb, data);
+ for (i=0; i < level; i++)
+ cb("\t", data);
+ cb("}", data);
+ cb(p->final ? "*" : "", data);
+ cb(EOL, data);
+ }
+ } while (iter != 0);
+}
+
+static void dump_profile_to_file_cb(const char *str, void *data)
+{
+ fputs(str, data);
+}
+
+errcode_t profile_write_tree_file(struct profile_node *root, FILE *dstfile)
+{
+ dump_profile(root, 0, dump_profile_to_file_cb, dstfile);
+ return 0;
+}
+
+struct prof_buf {
+ char *base;
+ size_t cur, max;
+ int err;
+};
+
+static void add_data_to_buffer(struct prof_buf *b, const void *d, size_t len)
+{
+ if (b->err)
+ return;
+ if (b->max - b->cur < len) {
+ size_t newsize;
+ char *newptr;
+
+ newsize = b->max + (b->max >> 1) + len + 1024;
+ newptr = realloc(b->base, newsize);
+ if (newptr == NULL) {
+ b->err = 1;
+ return;
+ }
+ b->base = newptr;
+ b->max = newsize;
+ }
+ memcpy(b->base + b->cur, d, len);
+ b->cur += len; /* ignore overflow */
+}
+
+static void dump_profile_to_buffer_cb(const char *str, void *data)
+{
+ add_data_to_buffer((struct prof_buf *)data, str, strlen(str));
+}
+
+errcode_t profile_write_tree_to_buffer(struct profile_node *root,
+ char **buf)
+{
+ struct prof_buf prof_buf = { 0, 0, 0, 0 };
+
+ dump_profile(root, 0, dump_profile_to_buffer_cb, &prof_buf);
+ if (prof_buf.err) {
+ *buf = NULL;
+ return ENOMEM;
+ }
+ add_data_to_buffer(&prof_buf, "", 1); /* append nul */
+ if (prof_buf.max - prof_buf.cur > (prof_buf.max >> 3)) {
+ char *newptr = realloc(prof_buf.base, prof_buf.cur);
+ if (newptr)
+ prof_buf.base = newptr;
+ }
+ *buf = prof_buf.base;
+ return 0;
+}
+#endif
+
+/*
+ * prof_tree.c --- these routines maintain the parse tree of the
+ * config file.
+ *
+ * All of the details of how the tree is stored is abstracted away in
+ * this file; all of the other profile routines build, access, and
+ * modify the tree via the accessor functions found in this file.
+ *
+ * Each node may represent either a relation or a section header.
+ *
+ * A section header must have its value field set to 0, and may a one
+ * or more child nodes, pointed to by first_child.
+ *
+ * A relation has as its value a pointer to allocated memory
+ * containing a string. Its first_child pointer must be null.
+ *
+ */
+
+/*
+ * Free a node, and any children
+ */
+void profile_free_node(struct profile_node *node)
+{
+ struct profile_node *child, *next;
+
+ if (node->magic != PROF_MAGIC_NODE)
+ return;
+
+ free(node->name);
+ free(node->value);
+
+ for (child=node->first_child; child; child = next) {
+ next = child->next;
+ profile_free_node(child);
+ }
+ node->magic = 0;
+
+ free(node);
+}
+
+#ifndef HAVE_STRDUP
+#undef strdup
+#define strdup MYstrdup
+static char *MYstrdup (const char *s)
+{
+ size_t sz = strlen(s) + 1;
+ char *p = malloc(sz);
+ if (p != 0)
+ memcpy(p, s, sz);
+ return p;
+}
+#endif
+
+/*
+ * Create a node
+ */
+errcode_t profile_create_node(const char *name, const char *value,
+ struct profile_node **ret_node)
+{
+ struct profile_node *new;
+
+ new = malloc(sizeof(struct profile_node));
+ if (!new)
+ return ENOMEM;
+ memset(new, 0, sizeof(struct profile_node));
+ new->magic = PROF_MAGIC_NODE;
+ new->name = strdup(name);
+ if (new->name == 0) {
+ profile_free_node(new);
+ return ENOMEM;
+ }
+ if (value) {
+ new->value = strdup(value);
+ if (new->value == 0) {
+ profile_free_node(new);
+ return ENOMEM;
+ }
+ }
+
+ *ret_node = new;
+ return 0;
+}
+
+/*
+ * This function verifies that all of the representation invariants of
+ * the profile are true. If not, we have a programming bug somewhere,
+ * probably in this file.
+ */
+#ifdef DEBUG_PROGRAM
+errcode_t profile_verify_node(struct profile_node *node)
+{
+ struct profile_node *p, *last;
+ errcode_t retval;
+
+ CHECK_MAGIC(node);
+
+ if (node->value && node->first_child)
+ return PROF_SECTION_WITH_VALUE;
+
+ last = 0;
+ for (p = node->first_child; p; last = p, p = p->next) {
+ if (p->prev != last)
+ return PROF_BAD_LINK_LIST;
+ if (last && (last->next != p))
+ return PROF_BAD_LINK_LIST;
+ if (node->group_level+1 != p->group_level)
+ return PROF_BAD_GROUP_LVL;
+ if (p->parent != node)
+ return PROF_BAD_PARENT_PTR;
+ retval = profile_verify_node(p);
+ if (retval)
+ return retval;
+ }
+ return 0;
+}
+#endif
+
+/*
+ * Add a node to a particular section
+ */
+errcode_t profile_add_node(struct profile_node *section, const char *name,
+ const char *value, struct profile_node **ret_node)
+{
+ errcode_t retval;
+ struct profile_node *p, *last, *new;
+
+ CHECK_MAGIC(section);
+
+ if (section->value)
+ return PROF_ADD_NOT_SECTION;
+
+ /*
+ * Find the place to insert the new node. We look for the
+ * place *after* the last match of the node name, since
+ * order matters.
+ */
+ for (p=section->first_child, last = 0; p; last = p, p = p->next) {
+ int cmp;
+ cmp = strcmp(p->name, name);
+ if (cmp > 0)
+ break;
+ }
+ retval = profile_create_node(name, value, &new);
+ if (retval)
+ return retval;
+ new->group_level = section->group_level+1;
+ new->deleted = 0;
+ new->parent = section;
+ new->prev = last;
+ new->next = p;
+ if (p)
+ p->prev = new;
+ if (last)
+ last->next = new;
+ else
+ section->first_child = new;
+ if (ret_node)
+ *ret_node = new;
+ return 0;
+}
+
+/*
+ * Iterate through the section, returning the nodes which match
+ * the given name. If name is NULL, then iterate through all the
+ * nodes in the section. If section_flag is non-zero, only return the
+ * section which matches the name; don't return relations. If value
+ * is non-NULL, then only return relations which match the requested
+ * value. (The value argument is ignored if section_flag is non-zero.)
+ *
+ * The first time this routine is called, the state pointer must be
+ * null. When this profile_find_node_relation() returns, if the state
+ * pointer is non-NULL, then this routine should be called again.
+ * (This won't happen if section_flag is non-zero, obviously.)
+ *
+ */
+errcode_t profile_find_node(struct profile_node *section, const char *name,
+ const char *value, int section_flag, void **state,
+ struct profile_node **node)
+{
+ struct profile_node *p;
+
+ CHECK_MAGIC(section);
+ p = *state;
+ if (p) {
+ CHECK_MAGIC(p);
+ } else
+ p = section->first_child;
+
+ for (; p; p = p->next) {
+ if (name && (strcmp(p->name, name)))
+ continue;
+ if (section_flag) {
+ if (p->value)
+ continue;
+ } else {
+ if (!p->value)
+ continue;
+ if (value && (strcmp(p->value, value)))
+ continue;
+ }
+ if (p->deleted)
+ continue;
+ /* A match! */
+ if (node)
+ *node = p;
+ break;
+ }
+ if (p == 0) {
+ *state = 0;
+ return section_flag ? PROF_NO_SECTION : PROF_NO_RELATION;
+ }
+ /*
+ * OK, we've found one match; now let's try to find another
+ * one. This way, if we return a non-zero state pointer,
+ * there's guaranteed to be another match that's returned.
+ */
+ for (p = p->next; p; p = p->next) {
+ if (name && (strcmp(p->name, name)))
+ continue;
+ if (section_flag) {
+ if (p->value)
+ continue;
+ } else {
+ if (!p->value)
+ continue;
+ if (value && (strcmp(p->value, value)))
+ continue;
+ }
+ /* A match! */
+ break;
+ }
+ *state = p;
+ return 0;
+}
+
+/*
+ * This is a general-purpose iterator for returning all nodes that
+ * match the specified name array.
+ */
+struct profile_iterator {
+ prf_magic_t magic;
+ profile_t profile;
+ int flags;
+ const char *const *names;
+ const char *name;
+ prf_file_t file;
+ int file_serial;
+ int done_idx;
+ struct profile_node *node;
+ int num;
+};
+
+errcode_t
+profile_iterator_create(profile_t profile, const char *const *names, int flags,
+ void **ret_iter)
+{
+ struct profile_iterator *iter;
+ int done_idx = 0;
+
+ if (profile == 0)
+ return PROF_NO_PROFILE;
+ if (profile->magic != PROF_MAGIC_PROFILE)
+ return PROF_MAGIC_PROFILE;
+ if (!names)
+ return PROF_BAD_NAMESET;
+ if (!(flags & PROFILE_ITER_LIST_SECTION)) {
+ if (!names[0])
+ return PROF_BAD_NAMESET;
+ done_idx = 1;
+ }
+
+ if ((iter = malloc(sizeof(struct profile_iterator))) == NULL)
+ return ENOMEM;
+
+ iter->magic = PROF_MAGIC_ITERATOR;
+ iter->profile = profile;
+ iter->names = names;
+ iter->flags = flags;
+ iter->file = profile->first_file;
+ iter->done_idx = done_idx;
+ iter->node = 0;
+ iter->num = 0;
+ *ret_iter = iter;
+ return 0;
+}
+
+void profile_iterator_free(void **iter_p)
+{
+ struct profile_iterator *iter;
+
+ if (!iter_p)
+ return;
+ iter = *iter_p;
+ if (!iter || iter->magic != PROF_MAGIC_ITERATOR)
+ return;
+ free(iter);
+ *iter_p = 0;
+}
+
+/*
+ * Note: the returned character strings in ret_name and ret_value
+ * points to the stored character string in the parse string. Before
+ * this string value is returned to a calling application
+ * (profile_node_iterator is not an exported interface), it should be
+ * strdup()'ed.
+ */
+errcode_t profile_node_iterator(void **iter_p, struct profile_node **ret_node,
+ char **ret_name, char **ret_value)
+{
+ struct profile_iterator *iter = *iter_p;
+ struct profile_node *section, *p;
+ const char *const *cpp;
+ errcode_t retval;
+ int skip_num = 0;
+
+ if (!iter || iter->magic != PROF_MAGIC_ITERATOR)
+ return PROF_MAGIC_ITERATOR;
+ if (iter->file && iter->file->magic != PROF_MAGIC_FILE)
+ return PROF_MAGIC_FILE;
+ /*
+ * If the file has changed, then the node pointer is invalid,
+ * so we'll have search the file again looking for it.
+ */
+ if (iter->node && (iter->file &&
+ iter->file->upd_serial != iter->file_serial)) {
+ iter->flags &= ~PROFILE_ITER_FINAL_SEEN;
+ skip_num = iter->num;
+ iter->node = 0;
+ }
+ if (iter->node && iter->node->magic != PROF_MAGIC_NODE) {
+ return PROF_MAGIC_NODE;
+ }
+get_new_file:
+ if (iter->node == 0) {
+ if (iter->file == NULL ||
+ (iter->flags & PROFILE_ITER_FINAL_SEEN)) {
+ profile_iterator_free(iter_p);
+ if (ret_node)
+ *ret_node = 0;
+ if (ret_name)
+ *ret_name = 0;
+ if (ret_value)
+ *ret_value =0;
+ return 0;
+ }
+ if ((retval = profile_update_file(iter->file))) {
+ if (retval == ENOENT || retval == EACCES) {
+ /* XXX memory leak? */
+ if (iter->file)
+ iter->file = iter->file->next;
+ skip_num = 0;
+ retval = 0;
+ goto get_new_file;
+ } else {
+ profile_iterator_free(iter_p);
+ return retval;
+ }
+ }
+ iter->file_serial = iter->file->upd_serial;
+ /*
+ * Find the section to list if we are a LIST_SECTION,
+ * or find the containing section if not.
+ */
+ section = iter->file->root;
+ for (cpp = iter->names; cpp[iter->done_idx]; cpp++) {
+ for (p=section->first_child; p; p = p->next) {
+ if (!strcmp(p->name, *cpp) && !p->value)
+ break;
+ }
+ if (!p) {
+ section = 0;
+ break;
+ }
+ section = p;
+ if (p->final)
+ iter->flags |= PROFILE_ITER_FINAL_SEEN;
+ }
+ if (!section) {
+ if (iter->file)
+ iter->file = iter->file->next;
+ skip_num = 0;
+ goto get_new_file;
+ }
+ iter->name = *cpp;
+ iter->node = section->first_child;
+ }
+ /*
+ * OK, now we know iter->node is set up correctly. Let's do
+ * the search.
+ */
+ for (p = iter->node; p; p = p->next) {
+ if (iter->name && strcmp(p->name, iter->name))
+ continue;
+ if ((iter->flags & PROFILE_ITER_SECTIONS_ONLY) &&
+ p->value)
+ continue;
+ if ((iter->flags & PROFILE_ITER_RELATIONS_ONLY) &&
+ !p->value)
+ continue;
+ if (skip_num > 0) {
+ skip_num--;
+ continue;
+ }
+ if (p->deleted)
+ continue;
+ break;
+ }
+ iter->num++;
+ if (!p) {
+ if (iter->file)
+ iter->file = iter->file->next;
+ iter->node = 0;
+ skip_num = 0;
+ goto get_new_file;
+ }
+ if ((iter->node = p->next) == NULL)
+ if (iter->file)
+ iter->file = iter->file->next;
+ if (ret_node)
+ *ret_node = p;
+ if (ret_name)
+ *ret_name = p->name;
+ if (ret_value)
+ *ret_value = p->value;
+ return 0;
+}
+
+
+/*
+ * prof_get.c --- routines that expose the public interfaces for
+ * querying items from the profile.
+ *
+ */
+
+/*
+ * This function only gets the first value from the file; it is a
+ * helper function for profile_get_string, profile_get_integer, etc.
+ */
+errcode_t profile_get_value(profile_t profile, const char *name,
+ const char *subname, const char *subsubname,
+ const char **ret_value)
+{
+ errcode_t retval;
+ void *state;
+ char *value;
+ const char *names[4];
+
+ names[0] = name;
+ names[1] = subname;
+ names[2] = subsubname;
+ names[3] = 0;
+
+ if ((retval = profile_iterator_create(profile, names,
+ PROFILE_ITER_RELATIONS_ONLY,
+ &state)))
+ return retval;
+
+ if ((retval = profile_node_iterator(&state, 0, 0, &value)))
+ goto cleanup;
+
+ if (value)
+ *ret_value = value;
+ else
+ retval = PROF_NO_RELATION;
+
+cleanup:
+ profile_iterator_free(&state);
+ return retval;
+}
+
+errcode_t
+profile_get_string(profile_t profile, const char *name, const char *subname,
+ const char *subsubname, const char *def_val,
+ char **ret_string)
+{
+ const char *value;
+ errcode_t retval;
+
+ if (profile) {
+ retval = profile_get_value(profile, name, subname,
+ subsubname, &value);
+ if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION)
+ value = def_val;
+ else if (retval)
+ return retval;
+ } else
+ value = def_val;
+
+ if (value) {
+ *ret_string = malloc(strlen(value)+1);
+ if (*ret_string == 0)
+ return ENOMEM;
+ strcpy(*ret_string, value);
+ } else
+ *ret_string = 0;
+ return 0;
+}
+
+errcode_t
+profile_get_integer(profile_t profile, const char *name, const char *subname,
+ const char *subsubname, int def_val, int *ret_int)
+{
+ const char *value;
+ errcode_t retval;
+ char *end_value;
+ long ret_long;
+
+ *ret_int = def_val;
+ if (profile == 0)
+ return 0;
+
+ retval = profile_get_value(profile, name, subname, subsubname, &value);
+ if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) {
+ *ret_int = def_val;
+ return 0;
+ } else if (retval)
+ return retval;
+
+ if (value[0] == 0)
+ /* Empty string is no good. */
+ return PROF_BAD_INTEGER;
+ errno = 0;
+ ret_long = strtol(value, &end_value, 0);
+
+ /* Overflow or underflow. */
+ if ((ret_long == LONG_MIN || ret_long == LONG_MAX) && errno != 0)
+ return PROF_BAD_INTEGER;
+ /* Value outside "int" range. */
+ if ((long) (int) ret_long != ret_long)
+ return PROF_BAD_INTEGER;
+ /* Garbage in string. */
+ if (end_value != value + strlen (value))
+ return PROF_BAD_INTEGER;
+
+
+ *ret_int = ret_long;
+ return 0;
+}
+
+errcode_t
+profile_get_uint(profile_t profile, const char *name, const char *subname,
+ const char *subsubname, unsigned int def_val,
+ unsigned int *ret_int)
+{
+ const char *value;
+ errcode_t retval;
+ char *end_value;
+ unsigned long ret_long;
+
+ *ret_int = def_val;
+ if (profile == 0)
+ return 0;
+
+ retval = profile_get_value(profile, name, subname, subsubname, &value);
+ if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) {
+ *ret_int = def_val;
+ return 0;
+ } else if (retval)
+ return retval;
+
+ if (value[0] == 0)
+ /* Empty string is no good. */
+ return PROF_BAD_INTEGER;
+ errno = 0;
+ ret_long = strtoul(value, &end_value, 0);
+
+ /* Overflow or underflow. */
+ if ((ret_long == ULONG_MAX) && errno != 0)
+ return PROF_BAD_INTEGER;
+ /* Value outside "int" range. */
+ if ((unsigned long) (unsigned int) ret_long != ret_long)
+ return PROF_BAD_INTEGER;
+ /* Garbage in string. */
+ if (end_value != value + strlen (value))
+ return PROF_BAD_INTEGER;
+
+ *ret_int = ret_long;
+ return 0;
+}
+
+errcode_t
+profile_get_double(profile_t profile, const char *name, const char *subname,
+ const char *subsubname, double def_val, double *ret_double)
+{
+ const char *value;
+ errcode_t retval;
+ char *end_value;
+ double double_val;
+
+ *ret_double = def_val;
+ if (profile == 0)
+ return 0;
+
+ retval = profile_get_value(profile, name, subname, subsubname, &value);
+ if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) {
+ *ret_double = def_val;
+ return 0;
+ } else if (retval)
+ return retval;
+
+ if (value[0] == 0)
+ /* Empty string is no good. */
+ return PROF_BAD_INTEGER;
+ errno = 0;
+ double_val = strtod(value, &end_value);
+
+ /* Overflow or underflow. */
+ if (errno != 0)
+ return PROF_BAD_INTEGER;
+ /* Garbage in string. */
+ if (end_value != value + strlen(value))
+ return PROF_BAD_INTEGER;
+
+ *ret_double = double_val;
+ return 0;
+}
+
+static const char *const conf_yes[] = {
+ "y", "yes", "true", "t", "1", "on",
+ 0,
+};
+
+static const char *const conf_no[] = {
+ "n", "no", "false", "nil", "0", "off",
+ 0,
+};
+
+static errcode_t
+profile_parse_boolean(const char *s, int *ret_boolean)
+{
+ const char *const *p;
+
+ if (ret_boolean == NULL)
+ return PROF_EINVAL;
+
+ for(p=conf_yes; *p; p++) {
+ if (!strcasecmp(*p,s)) {
+ *ret_boolean = 1;
+ return 0;
+ }
+ }
+
+ for(p=conf_no; *p; p++) {
+ if (!strcasecmp(*p,s)) {
+ *ret_boolean = 0;
+ return 0;
+ }
+ }
+
+ return PROF_BAD_BOOLEAN;
+}
+
+errcode_t
+profile_get_boolean(profile_t profile, const char *name, const char *subname,
+ const char *subsubname, int def_val, int *ret_boolean)
+{
+ const char *value;
+ errcode_t retval;
+
+ if (profile == 0) {
+ *ret_boolean = def_val;
+ return 0;
+ }
+
+ retval = profile_get_value(profile, name, subname, subsubname, &value);
+ if (retval == PROF_NO_SECTION || retval == PROF_NO_RELATION) {
+ *ret_boolean = def_val;
+ return 0;
+ } else if (retval)
+ return retval;
+
+ return profile_parse_boolean (value, ret_boolean);
+}
+
+errcode_t
+profile_iterator(void **iter_p, char **ret_name, char **ret_value)
+{
+ char *name, *value;
+ errcode_t retval;
+
+ retval = profile_node_iterator(iter_p, 0, &name, &value);
+ if (retval)
+ return retval;
+
+ if (ret_name) {
+ if (name) {
+ *ret_name = malloc(strlen(name)+1);
+ if (!*ret_name)
+ return ENOMEM;
+ strcpy(*ret_name, name);
+ } else
+ *ret_name = 0;
+ }
+ if (ret_value) {
+ if (value) {
+ *ret_value = malloc(strlen(value)+1);
+ if (!*ret_value) {
+ if (ret_name) {
+ free(*ret_name);
+ *ret_name = 0;
+ }
+ return ENOMEM;
+ }
+ strcpy(*ret_value, value);
+ } else
+ *ret_value = 0;
+ }
+ return 0;
+}
+
+#ifdef DEBUG_PROGRAM
+
+/*
+ * test_profile.c --- testing program for the profile routine
+ */
+
+#include "argv_parse.h"
+#include "profile_helpers.h"
+
+const char *program_name = "test_profile";
+
+#define PRINT_VALUE 1
+#define PRINT_VALUES 2
+
+static void do_cmd(profile_t profile, char **argv)
+{
+ errcode_t retval;
+ const char **names, *value;
+ char **values, **cpp;
+ char *cmd;
+ int print_status;
+
+ cmd = *(argv);
+ names = (const char **) argv + 1;
+ print_status = 0;
+ retval = 0;
+ if (cmd == 0)
+ return;
+ if (!strcmp(cmd, "query")) {
+ retval = profile_get_values(profile, names, &values);
+ print_status = PRINT_VALUES;
+ } else if (!strcmp(cmd, "query1")) {
+ const char *name = 0;
+ const char *subname = 0;
+ const char *subsubname = 0;
+
+ name = names[0];
+ if (name)
+ subname = names[1];
+ if (subname)
+ subsubname = names[2];
+ if (subsubname && names[3]) {
+ fprintf(stderr,
+ "Only 3 levels are allowed with query1\n");
+ retval = EINVAL;
+ } else
+ retval = profile_get_value(profile, name, subname,
+ subsubname, &value);
+ print_status = PRINT_VALUE;
+ } else if (!strcmp(cmd, "list_sections")) {
+ retval = profile_get_subsection_names(profile, names,
+ &values);
+ print_status = PRINT_VALUES;
+ } else if (!strcmp(cmd, "list_relations")) {
+ retval = profile_get_relation_names(profile, names,
+ &values);
+ print_status = PRINT_VALUES;
+ } else if (!strcmp(cmd, "dump")) {
+ retval = profile_write_tree_file
+ (profile->first_file->root, stdout);
+#if 0
+ } else if (!strcmp(cmd, "clear")) {
+ retval = profile_clear_relation(profile, names);
+ } else if (!strcmp(cmd, "update")) {
+ retval = profile_update_relation(profile, names+2,
+ *names, *(names+1));
+#endif
+ } else if (!strcmp(cmd, "verify")) {
+ retval = profile_verify_node
+ (profile->first_file->root);
+#if 0
+ } else if (!strcmp(cmd, "rename_section")) {
+ retval = profile_rename_section(profile, names+1, *names);
+ } else if (!strcmp(cmd, "add")) {
+ value = *names;
+ if (strcmp(value, "NULL") == 0)
+ value = NULL;
+ retval = profile_add_relation(profile, names+1, value);
+ } else if (!strcmp(cmd, "flush")) {
+ retval = profile_flush(profile);
+#endif
+ } else {
+ printf("Invalid command.\n");
+ }
+ if (retval) {
+ com_err(cmd, retval, "");
+ print_status = 0;
+ }
+ switch (print_status) {
+ case PRINT_VALUE:
+ printf("%s\n", value);
+ break;
+ case PRINT_VALUES:
+ for (cpp = values; *cpp; cpp++)
+ printf("%s\n", *cpp);
+ profile_free_list(values);
+ break;
+ }
+}
+
+static void do_batchmode(profile_t profile)
+{
+ int argc, ret;
+ char **argv;
+ char buf[256];
+
+ while (!feof(stdin)) {
+ if (fgets(buf, sizeof(buf), stdin) == NULL)
+ break;
+ printf(">%s", buf);
+ ret = argv_parse(buf, &argc, &argv);
+ if (ret != 0) {
+ printf("Argv_parse returned %d!\n", ret);
+ continue;
+ }
+ do_cmd(profile, argv);
+ printf("\n");
+ argv_free(argv);
+ }
+ profile_release(profile);
+ exit(0);
+
+}
+
+void syntax_err_report(const char *filename, long err, int line_num)
+{
+ fprintf(stderr, "Syntax error in %s, line number %d: %s\n",
+ filename, line_num, error_message(err));
+ exit(1);
+}
+
+const char *default_str = "[foo]\n\tbar=quux\n\tsub = {\n\t\twin = true\n}\n";
+
+int main(int argc, char **argv)
+{
+ profile_t profile;
+ long retval;
+ char *cmd;
+
+ if (argc < 2) {
+ fprintf(stderr, "Usage: %s filename [cmd argset]\n", program_name);
+ exit(1);
+ }
+
+ initialize_prof_error_table();
+
+ profile_set_syntax_err_cb(syntax_err_report);
+
+ retval = profile_init_path(argv[1], &profile);
+ if (retval) {
+ com_err(program_name, retval, "while initializing profile");
+ exit(1);
+ }
+ retval = profile_set_default(profile, default_str);
+ if (retval) {
+ com_err(program_name, retval, "while setting default");
+ exit(1);
+ }
+
+ cmd = *(argv+2);
+ if (!cmd || !strcmp(cmd, "batch"))
+ do_batchmode(profile);
+ else
+ do_cmd(profile, argv+2);
+ profile_release(profile);
+
+ return 0;
+}
+
+#endif
diff --git a/lib/support/profile.h b/lib/support/profile.h
new file mode 100644
index 0000000..aec870a
--- /dev/null
+++ b/lib/support/profile.h
@@ -0,0 +1,107 @@
+/*
+ * profile.h
+ *
+ * Copyright (C) 2005 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ *
+ * Copyright (C) 1985-2005 by the Massachusetts Institute of Technology.
+ *
+ * All rights reserved.
+ *
+ * Export of this software from the United States of America may require
+ * a specific license from the United States Government. It is the
+ * responsibility of any person or organization contemplating export to
+ * obtain such a license before exporting.
+ *
+ * WITHIN THAT CONSTRAINT, permission to use, copy, modify, and
+ * distribute this software and its documentation for any purpose and
+ * without fee is hereby granted, provided that the above copyright
+ * notice appear in all copies and that both that copyright notice and
+ * this permission notice appear in supporting documentation, and that
+ * the name of M.I.T. not be used in advertising or publicity pertaining
+ * to distribution of the software without specific, written prior
+ * permission. Furthermore if you modify this software you must label
+ * your software as modified software and not distribute it in such a
+ * fashion that it might be confused with the original MIT software.
+ * M.I.T. makes no representations about the suitability of this software
+ * for any purpose. It is provided "as is" without express or implied
+ * warranty.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
+ * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
+ * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
+ */
+
+#ifndef _PROFILE_H
+#define _PROFILE_H
+
+typedef struct _profile_t *profile_t;
+
+typedef void (*profile_syntax_err_cb_t)(const char *file, long err,
+ int line_num);
+
+/*
+ * Used by the profile iterator in prof_get.c
+ */
+#define PROFILE_ITER_LIST_SECTION 0x0001
+#define PROFILE_ITER_SECTIONS_ONLY 0x0002
+#define PROFILE_ITER_RELATIONS_ONLY 0x0004
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+long profile_init
+ (const char * const *files, profile_t *ret_profile);
+
+void profile_release
+ (profile_t profile);
+
+long profile_set_default
+ (profile_t profile, const char *def_string);
+
+long profile_get_string
+ (profile_t profile, const char *name, const char *subname,
+ const char *subsubname, const char *def_val,
+ char **ret_string);
+long profile_get_integer
+ (profile_t profile, const char *name, const char *subname,
+ const char *subsubname, int def_val,
+ int *ret_default);
+
+long profile_get_uint
+ (profile_t profile, const char *name, const char *subname,
+ const char *subsubname, unsigned int def_val,
+ unsigned int *ret_int);
+
+long profile_get_double
+ (profile_t profile, const char *name, const char *subname,
+ const char *subsubname, double def_val,
+ double *ret_float);
+
+long profile_get_boolean
+ (profile_t profile, const char *name, const char *subname,
+ const char *subsubname, int def_val,
+ int *ret_default);
+
+long profile_iterator_create
+ (profile_t profile, const char *const *names,
+ int flags, void **ret_iter);
+
+void profile_iterator_free
+ (void **iter_p);
+
+long profile_iterator
+ (void **iter_p, char **ret_name, char **ret_value);
+
+profile_syntax_err_cb_t profile_set_syntax_err_cb(profile_syntax_err_cb_t hook);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* _KRB5_H */
diff --git a/lib/support/profile_helpers.c b/lib/support/profile_helpers.c
new file mode 100644
index 0000000..d7d6f15
--- /dev/null
+++ b/lib/support/profile_helpers.c
@@ -0,0 +1,317 @@
+/*
+ * profile_helpers.c -- Helper functions for the profile library
+ *
+ * These functions are not part of the "core" profile library, and do
+ * not require access to the internal functions and data structures of
+ * the profile library. They are mainly convenience functions for
+ * programs that want to do something unusual such as obtaining the
+ * list of sections or relations, or accessing multiple values from a
+ * relation that is listed more than once. This functionality can all
+ * be done using the profile_iterator abstraction, but it is less
+ * convenient.
+ *
+ * Copyright (C) 2006 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+
+#include <et/com_err.h>
+#include "profile.h"
+#include "profile_helpers.h"
+#include "prof_err.h"
+
+/*
+ * These functions --- init_list(), end_list(), and add_to_list() are
+ * internal functions used to build up a null-terminated char ** list
+ * of strings to be returned by functions like profile_get_values.
+ *
+ * The profile_string_list structure is used for internal booking
+ * purposes to build up the list, which is returned in *ret_list by
+ * the end_list() function.
+ *
+ * The publicly exported interface for freeing char** list is
+ * profile_free_list().
+ */
+
+struct profile_string_list {
+ char **list;
+ int num;
+ int max;
+};
+
+/*
+ * Initialize the string list abstraction.
+ */
+static errcode_t init_list(struct profile_string_list *list)
+{
+ list->num = 0;
+ list->max = 10;
+ list->list = malloc(list->max * sizeof(char *));
+ if (list->list == 0)
+ return ENOMEM;
+ list->list[0] = 0;
+ return 0;
+}
+
+/*
+ * Free any memory left over in the string abstraction, returning the
+ * built up list in *ret_list if it is non-null.
+ */
+static void end_list(struct profile_string_list *list, char ***ret_list)
+{
+ char **cp;
+
+ if (list == 0)
+ return;
+
+ if (ret_list) {
+ *ret_list = list->list;
+ return;
+ } else {
+ for (cp = list->list; *cp; cp++)
+ free(*cp);
+ free(list->list);
+ }
+ list->num = list->max = 0;
+ list->list = 0;
+}
+
+/*
+ * Add a string to the list.
+ */
+static errcode_t add_to_list(struct profile_string_list *list, char *str)
+{
+ char **newlist;
+ int newmax;
+
+ if (list->num+1 >= list->max) {
+ newmax = list->max + 10;
+ newlist = realloc(list->list, newmax * sizeof(char *));
+ if (newlist == 0)
+ return ENOMEM;
+ list->max = newmax;
+ list->list = newlist;
+ }
+
+ list->list[list->num++] = str;
+ list->list[list->num] = 0;
+ return 0;
+}
+
+/*
+ * Return TRUE if the string is already a member of the list.
+ */
+static int is_list_member(struct profile_string_list *list, const char *str)
+{
+ char **cpp;
+
+ if (!list->list)
+ return 0;
+
+ for (cpp = list->list; *cpp; cpp++) {
+ if (!strcmp(*cpp, str))
+ return 1;
+ }
+ return 0;
+}
+
+/*
+ * This function frees a null-terminated list as returned by
+ * profile_get_values.
+ */
+void profile_free_list(char **list)
+{
+ char **cp;
+
+ if (list == 0)
+ return;
+
+ for (cp = list; *cp; cp++)
+ free(*cp);
+ free(list);
+}
+
+errcode_t
+profile_get_values(profile_t profile, const char *const *names,
+ char ***ret_values)
+{
+ errcode_t retval;
+ void *state;
+ char *value;
+ struct profile_string_list values;
+
+ if ((retval = profile_iterator_create(profile, names,
+ PROFILE_ITER_RELATIONS_ONLY,
+ &state)))
+ return retval;
+
+ if ((retval = init_list(&values)))
+ goto cleanup_iterator;
+
+ do {
+ if ((retval = profile_iterator(&state, 0, &value)))
+ goto cleanup;
+ if (value)
+ add_to_list(&values, value);
+ } while (state);
+
+ if (values.num == 0) {
+ retval = PROF_NO_RELATION;
+ goto cleanup;
+ }
+
+ end_list(&values, ret_values);
+ return 0;
+
+cleanup:
+ end_list(&values, 0);
+cleanup_iterator:
+ profile_iterator_free(&state);
+ return retval;
+}
+
+/*
+ * This function will return the list of the names of subsections in the
+ * under the specified section name.
+ */
+errcode_t
+profile_get_subsection_names(profile_t profile, const char **names,
+ char ***ret_names)
+{
+ errcode_t retval;
+ void *state;
+ char *name;
+ struct profile_string_list values;
+
+ if ((retval = profile_iterator_create(profile, names,
+ PROFILE_ITER_LIST_SECTION | PROFILE_ITER_SECTIONS_ONLY,
+ &state)))
+ return retval;
+
+ if ((retval = init_list(&values)))
+ goto cleanup_iterator;
+
+ do {
+ if ((retval = profile_iterator(&state, &name, 0)))
+ goto cleanup;
+ if (name)
+ add_to_list(&values, name);
+ } while (state);
+
+ end_list(&values, ret_names);
+ return 0;
+
+cleanup:
+ end_list(&values, 0);
+cleanup_iterator:
+ profile_iterator_free(&state);
+ return retval;
+}
+
+/*
+ * This function will return the list of the names of relations in the
+ * under the specified section name.
+ */
+errcode_t
+profile_get_relation_names(profile_t profile, const char **names,
+ char ***ret_names)
+{
+ errcode_t retval;
+ void *state;
+ char *name;
+ struct profile_string_list values;
+
+ if ((retval = profile_iterator_create(profile, names,
+ PROFILE_ITER_LIST_SECTION | PROFILE_ITER_RELATIONS_ONLY,
+ &state)))
+ return retval;
+
+ if ((retval = init_list(&values)))
+ goto cleanup_iterator;
+
+ do {
+ if ((retval = profile_iterator(&state, &name, 0)))
+ goto cleanup;
+ if (name) {
+ if (is_list_member(&values, name))
+ free(name);
+ else
+ add_to_list(&values, name);
+ }
+ } while (state);
+
+ end_list(&values, ret_names);
+ return 0;
+
+cleanup:
+ end_list(&values, 0);
+cleanup_iterator:
+ profile_iterator_free(&state);
+ return retval;
+}
+
+
+void
+profile_release_string(char *str)
+{
+ free(str);
+}
+
+errcode_t
+profile_init_path(const char * filepath,
+ profile_t *ret_profile)
+{
+ int n_entries, i;
+ unsigned int ent_len;
+ const char *s, *t;
+ char **filenames;
+ errcode_t retval;
+
+ /* count the distinct filename components */
+ for(s = filepath, n_entries = 1; *s; s++) {
+ if (*s == ':')
+ n_entries++;
+ }
+
+ /* the array is NULL terminated */
+ filenames = (char **) malloc((n_entries+1) * sizeof(char*));
+ if (filenames == 0)
+ return ENOMEM;
+
+ /* measure, copy, and skip each one */
+ for(s = filepath, i=0; (t = strchr(s, ':')) || (t=s+strlen(s)); s=t+1, i++) {
+ ent_len = t-s;
+ filenames[i] = (char*) malloc(ent_len + 1);
+ if (filenames[i] == 0) {
+ /* if malloc fails, free the ones that worked */
+ while(--i >= 0) free(filenames[i]);
+ free(filenames);
+ return ENOMEM;
+ }
+ strncpy(filenames[i], s, ent_len);
+ filenames[i][ent_len] = 0;
+ if (*t == 0) {
+ i++;
+ break;
+ }
+ }
+ /* cap the array */
+ filenames[i] = 0;
+
+ retval = profile_init((const char * const *) filenames,
+ ret_profile);
+
+ /* count back down and free the entries */
+ while(--i >= 0) free(filenames[i]);
+ free(filenames);
+
+ return retval;
+}
diff --git a/lib/support/profile_helpers.h b/lib/support/profile_helpers.h
new file mode 100644
index 0000000..af63ca5
--- /dev/null
+++ b/lib/support/profile_helpers.h
@@ -0,0 +1,28 @@
+/*
+ * profile_helpers.h -- Function prototypes for profile helper functions
+ *
+ * Copyright (C) 2006 by Theodore Ts'o.
+ *
+ * %Begin-Header%
+ * This file may be redistributed under the terms of the GNU Public
+ * License.
+ * %End-Header%
+ */
+
+long profile_get_values
+ (profile_t profile, const char *const *names, char ***ret_values);
+
+void profile_free_list
+ (char **list);
+
+long profile_get_relation_names
+ (profile_t profile, const char **names, char ***ret_names);
+
+long profile_get_subsection_names
+ (profile_t profile, const char **names, char ***ret_names);
+
+void profile_release_string (char *str);
+
+long profile_init_path
+ (const char * filelist, profile_t *ret_profile);
+
diff --git a/lib/support/quotaio.c b/lib/support/quotaio.c
new file mode 100644
index 0000000..b41bb74
--- /dev/null
+++ b/lib/support/quotaio.c
@@ -0,0 +1,412 @@
+/** 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 "config.h"
+#include <stdio.h>
+#include <errno.h>
+#include <string.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <time.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/file.h>
+#include <assert.h>
+
+#include "common.h"
+#include "quotaio.h"
+
+static const char * const extensions[MAXQUOTAS] = {
+ [USRQUOTA] = "user",
+ [GRPQUOTA] = "group",
+ [PRJQUOTA] = "project",
+};
+static const char * const basenames[] = {
+ "", /* undefined */
+ "quota", /* QFMT_VFS_OLD */
+ "aquota", /* QFMT_VFS_V0 */
+ "", /* QFMT_OCFS2 */
+ "aquota" /* QFMT_VFS_V1 */
+};
+
+/* Header in all newer quotafiles */
+struct disk_dqheader {
+ __le32 dqh_magic;
+ __le32 dqh_version;
+} __attribute__ ((packed));
+
+/**
+ * Convert type of quota to written representation
+ */
+const char *quota_type2name(enum quota_type qtype)
+{
+ if (qtype >= MAXQUOTAS)
+ return "unknown";
+ return extensions[qtype];
+}
+
+ext2_ino_t quota_type2inum(enum quota_type qtype,
+ struct ext2_super_block *sb)
+{
+ switch (qtype) {
+ case USRQUOTA:
+ return EXT4_USR_QUOTA_INO;
+ case GRPQUOTA:
+ return EXT4_GRP_QUOTA_INO;
+ case PRJQUOTA:
+ return sb->s_prj_quota_inum;
+ default:
+ return 0;
+ }
+ return 0;
+}
+
+/**
+ * Creates a quota file name for given type and format.
+ */
+const char *quota_get_qf_name(enum quota_type type, int fmt, char *buf)
+{
+ if (!buf)
+ return NULL;
+ snprintf(buf, QUOTA_NAME_LEN, "%s.%s",
+ basenames[fmt], extensions[type]);
+
+ return 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;
+ }
+}
+
+errcode_t quota_inode_truncate(ext2_filsys fs, ext2_ino_t ino)
+{
+ struct ext2_inode inode;
+ errcode_t err;
+ enum quota_type qtype;
+
+ if ((err = ext2fs_read_inode(fs, ino, &inode)))
+ return err;
+
+ for (qtype = 0; qtype < MAXQUOTAS; qtype++)
+ if (ino == quota_type2inum(qtype, fs->super))
+ break;
+
+ if (qtype != MAXQUOTAS) {
+ inode.i_dtime = fs->now ? fs->now : time(0);
+ if (!ext2fs_inode_has_valid_blocks2(fs, &inode))
+ return 0;
+ err = ext2fs_punch(fs, ino, &inode, NULL, 0, ~0ULL);
+ if (err)
+ return err;
+ fs->flags &= ~EXT2_FLAG_SUPER_ONLY;
+ memset(&inode, 0, sizeof(struct ext2_inode));
+ } else {
+ inode.i_flags &= ~EXT2_IMMUTABLE_FL;
+ }
+ err = ext2fs_write_inode(fs, ino, &inode);
+ return err;
+}
+
+/* Functions to read/write quota file. */
+static unsigned int quota_write_nomount(struct quota_file *qf,
+ ext2_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,
+ ext2_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
+ */
+errcode_t quota_file_open(quota_ctx_t qctx, struct quota_handle *h,
+ ext2_ino_t qf_ino, enum quota_type qtype,
+ int fmt, int flags)
+{
+ ext2_filsys fs = qctx->fs;
+ ext2_file_t e2_file;
+ errcode_t err;
+ int allocated_handle = 0;
+
+ if (qtype >= MAXQUOTAS)
+ return EINVAL;
+
+ if (fmt == -1)
+ fmt = QFMT_VFS_V1;
+
+ err = ext2fs_read_bitmaps(fs);
+ if (err)
+ return err;
+
+ if (qf_ino == 0)
+ qf_ino = *quota_sb_inump(fs->super, qtype);
+
+ log_debug("Opening quota ino=%u, type=%d", qf_ino, qtype);
+ err = ext2fs_file_open(fs, qf_ino, flags, &e2_file);
+ if (err) {
+ log_err("ext2fs_file_open failed: %s", error_message(err));
+ return err;
+ }
+
+ if (!h) {
+ if (qctx->quota_file[qtype]) {
+ h = qctx->quota_file[qtype];
+ if (((flags & EXT2_FILE_WRITE) == 0) ||
+ (h->qh_file_flags & EXT2_FILE_WRITE)) {
+ ext2fs_file_close(e2_file);
+ return 0;
+ }
+ (void) quota_file_close(qctx, h);
+ }
+ err = ext2fs_get_mem(sizeof(struct quota_handle), &h);
+ if (err) {
+ log_err("Unable to allocate quota handle");
+ ext2fs_file_close(e2_file);
+ return err;
+ }
+ allocated_handle = 1;
+ }
+
+ h->qh_qf.e2_file = e2_file;
+ h->qh_qf.fs = fs;
+ h->qh_qf.ino = qf_ino;
+ h->e2fs_write = quota_write_nomount;
+ h->e2fs_read = quota_read_nomount;
+ h->qh_file_flags = flags;
+ h->qh_io_flags = 0;
+ h->qh_type = qtype;
+ h->qh_fmt = fmt;
+ memset(&h->qh_info, 0, sizeof(h->qh_info));
+ h->qh_ops = &quotafile_ops_2;
+
+ if (h->qh_ops->check_file &&
+ (h->qh_ops->check_file(h, qtype, fmt) == 0)) {
+ log_err("qh_ops->check_file failed");
+ err = EIO;
+ goto errout;
+ }
+
+ if (h->qh_ops->init_io && (h->qh_ops->init_io(h) < 0)) {
+ log_err("qh_ops->init_io failed");
+ err = EIO;
+ goto errout;
+ }
+ if (allocated_handle)
+ qctx->quota_file[qtype] = h;
+
+ return 0;
+errout:
+ ext2fs_file_close(e2_file);
+ if (allocated_handle)
+ ext2fs_free_mem(&h);
+ return err;
+}
+
+static errcode_t quota_inode_init_new(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)) {
+ err = quota_inode_truncate(fs, ino);
+ if (err)
+ return err;
+ }
+
+ 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 (ext2fs_has_feature_extents(fs->super))
+ 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
+ */
+errcode_t quota_file_create(struct quota_handle *h, ext2_filsys fs,
+ enum quota_type qtype, int fmt)
+{
+ ext2_file_t e2_file;
+ errcode_t err;
+ ext2_ino_t qf_inum = 0;
+
+ if (fmt == -1)
+ fmt = QFMT_VFS_V1;
+
+ h->qh_qf.fs = fs;
+ qf_inum = quota_type2inum(qtype, fs->super);
+ if (qf_inum == 0 && qtype == PRJQUOTA) {
+ err = ext2fs_new_inode(fs, EXT2_ROOT_INO, LINUX_S_IFREG | 0600,
+ 0, &qf_inum);
+ if (err)
+ return err;
+ ext2fs_inode_alloc_stats2(fs, qf_inum, +1, 0);
+ ext2fs_mark_ib_dirty(fs);
+ } else if (qf_inum == 0) {
+ return EXT2_ET_BAD_INODE_NUM;
+ }
+
+ err = ext2fs_read_bitmaps(fs);
+ if (err)
+ goto out_err;
+
+ err = quota_inode_init_new(fs, qf_inum);
+ if (err) {
+ log_err("init_new_quota_inode failed");
+ goto out_err;
+ }
+ h->qh_qf.ino = qf_inum;
+ h->qh_file_flags = EXT2_FILE_WRITE | EXT2_FILE_CREATE;
+ h->e2fs_write = quota_write_nomount;
+ h->e2fs_read = quota_read_nomount;
+
+ log_debug("Creating quota ino=%u, type=%d", qf_inum, qtype);
+ err = ext2fs_file_open(fs, qf_inum, h->qh_file_flags, &e2_file);
+ if (err) {
+ log_err("ext2fs_file_open failed: %ld", err);
+ goto out_err;
+ }
+ h->qh_qf.e2_file = e2_file;
+
+ h->qh_io_flags = 0;
+ h->qh_type = qtype;
+ h->qh_fmt = fmt;
+ memset(&h->qh_info, 0, sizeof(h->qh_info));
+ h->qh_ops = &quotafile_ops_2;
+
+ if (h->qh_ops->new_io && (h->qh_ops->new_io(h) < 0)) {
+ log_err("qh_ops->new_io failed");
+ err = EIO;
+ goto out_err1;
+ }
+
+ return 0;
+
+out_err1:
+ ext2fs_file_close(e2_file);
+out_err:
+
+ if (qf_inum)
+ quota_inode_truncate(fs, qf_inum);
+
+ return err;
+}
+
+/*
+ * Close quotafile and release handle
+ */
+errcode_t quota_file_close(quota_ctx_t qctx, 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 EIO;
+ h->qh_io_flags &= ~IOFL_INFODIRTY;
+ }
+
+ if (h->qh_ops->end_io && h->qh_ops->end_io(h) < 0)
+ return EIO;
+ if (h->qh_qf.e2_file)
+ ext2fs_file_close(h->qh_qf.e2_file);
+ if (qctx->quota_file[h->qh_type] == h)
+ ext2fs_free_mem(&qctx->quota_file[h->qh_type]);
+ return 0;
+}
+
+/*
+ * Create empty quota structure
+ */
+struct dquot *get_empty_dquot(void)
+{
+ struct dquot *dquot;
+
+ if (ext2fs_get_memzero(sizeof(struct dquot), &dquot)) {
+ log_err("Failed to allocate dquot");
+ return NULL;
+ }
+
+ dquot->dq_id = -1;
+ return dquot;
+}
diff --git a/lib/support/quotaio.h b/lib/support/quotaio.h
new file mode 100644
index 0000000..84fac35
--- /dev/null
+++ b/lib/support/quotaio.h
@@ -0,0 +1,268 @@
+/** quotaio.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;
+ *
+ * quota_init_context(&qctx, fs, QUOTA_ALL_BIT);
+ * {
+ * quota_compute_usage(qctx);
+ * AND/OR
+ * quota_data_add/quota_data_sub/quota_data_inodes();
+ * }
+ * quota_write_inode(qctx, USRQUOTA);
+ * quota_write_inode(qctx, GRPQUOTA);
+ * quota_release_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>
+ * 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/ext2_fs.h"
+#include "ext2fs/ext2fs.h"
+#include "dqblk_v2.h"
+
+typedef int64_t qsize_t; /* Type in which we store size limitations */
+
+enum quota_type {
+ USRQUOTA = 0,
+ GRPQUOTA = 1,
+ PRJQUOTA = 2,
+ MAXQUOTAS = 3,
+};
+
+#if MAXQUOTAS > 32
+#error "cannot have more than 32 quota types to fit in qtype_bits"
+#endif
+
+#define QUOTA_USR_BIT (1 << USRQUOTA)
+#define QUOTA_GRP_BIT (1 << GRPQUOTA)
+#define QUOTA_PRJ_BIT (1 << PRJQUOTA)
+#define QUOTA_ALL_BIT (QUOTA_USR_BIT | QUOTA_GRP_BIT | QUOTA_PRJ_BIT)
+
+typedef struct quota_ctx *quota_ctx_t;
+struct dict_t;
+
+struct quota_ctx {
+ ext2_filsys fs;
+ struct dict_t *quota_dict[MAXQUOTAS];
+ struct quota_handle *quota_file[MAXQUOTAS];
+};
+
+/*
+ * Definitions of magics and versions of current quota files
+ */
+#define INITQMAGICS {\
+ 0xd9c01f11, /* USRQUOTA */\
+ 0xd9c01927, /* GRPQUOTA */\
+ 0xd9c03f14 /* PRJQUOTA */\
+}
+
+/* Size of blocks in which are counted size limits in generic utility parts */
+#define QUOTABLOCK_BITS 10
+#define QUOTABLOCK_SIZE (1 << QUOTABLOCK_BITS)
+#define toqb(x) (((x) + QUOTABLOCK_SIZE - 1) >> QUOTABLOCK_BITS)
+
+/* Quota format type IDs */
+#define QFMT_VFS_OLD 1
+#define QFMT_VFS_V0 2
+#define QFMT_VFS_V1 4
+
+/*
+ * 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_INFODIRTY 0x01 /* Did info change? */
+
+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 {
+ enum quota_type qh_type; /* Type of quotafile */
+ int qh_fmt; /* Quotafile format */
+ int qh_file_flags;
+ int qh_io_flags; /* IO flags for file */
+ struct quota_file qh_qf;
+ unsigned int (*e2fs_read)(struct quota_file *qf, ext2_loff_t offset,
+ void *buf, unsigned int size);
+ unsigned int (*e2fs_write)(struct quota_file *qf, ext2_loff_t offset,
+ void *buf, unsigned int size);
+ struct quotafile_ops *qh_ops; /* Operations on quotafile */
+ struct util_dqinfo qh_info; /* Generic quotafile info */
+};
+
+/* 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 */
+};
+
+#define DQF_SEEN 0x0001
+
+/* 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);
+ /* Scan quotafile and call callback on every structure */
+ int (*scan_dquots) (struct quota_handle *h,
+ int (*process_dquot) (struct dquot *dquot,
+ void *data),
+ void *data);
+ /* 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;
+
+/* Open existing quotafile of given type (and verify its format) on given
+ * filesystem. */
+errcode_t quota_file_open(quota_ctx_t qctx, struct quota_handle *h,
+ ext2_ino_t qf_ino, enum quota_type type,
+ int fmt, int flags);
+
+
+/* Create new quotafile of specified format on given filesystem */
+errcode_t quota_file_create(struct quota_handle *h, ext2_filsys fs,
+ enum quota_type qtype, int fmt);
+
+/* Close quotafile */
+errcode_t quota_file_close(quota_ctx_t qctx, struct quota_handle *h);
+
+/* Get empty quota structure */
+struct dquot *get_empty_dquot(void);
+
+errcode_t quota_inode_truncate(ext2_filsys fs, ext2_ino_t ino);
+
+const char *quota_type2name(enum quota_type qtype);
+ext2_ino_t quota_type2inum(enum quota_type qtype, struct ext2_super_block *);
+
+void update_grace_times(struct dquot *q);
+
+/* size for the buffer returned by quota_get_qf_name(); must be greater
+ than maxlen of extensions[] and fmtnames[] (plus 2) found in quotaio.c */
+#define QUOTA_NAME_LEN 16
+
+const char *quota_get_qf_name(enum quota_type, int fmt, char *buf);
+
+/* In mkquota.c */
+errcode_t quota_init_context(quota_ctx_t *qctx, ext2_filsys fs,
+ unsigned int qtype_bits);
+void quota_data_inodes(quota_ctx_t qctx, struct ext2_inode_large *inode,
+ ext2_ino_t ino, int adjust);
+void quota_data_add(quota_ctx_t qctx, struct ext2_inode_large *inode,
+ ext2_ino_t ino, qsize_t space);
+void quota_data_sub(quota_ctx_t qctx, struct ext2_inode_large *inode,
+ ext2_ino_t ino, qsize_t space);
+errcode_t quota_write_inode(quota_ctx_t qctx, enum quota_type qtype);
+/* Flags for quota_read_all_dquots() */
+#define QREAD_USAGE 0x01
+#define QREAD_LIMITS 0x02
+errcode_t quota_read_all_dquots(quota_ctx_t qctx, ext2_ino_t qf_ino,
+ enum quota_type type, unsigned int flags);
+errcode_t quota_compute_usage(quota_ctx_t qctx);
+void quota_release_context(quota_ctx_t *qctx);
+errcode_t quota_remove_inode(ext2_filsys fs, enum quota_type qtype);
+int quota_file_exists(ext2_filsys fs, enum quota_type qtype);
+void quota_set_sb_inum(ext2_filsys fs, ext2_ino_t ino, enum quota_type qtype);
+errcode_t quota_compare_and_update(quota_ctx_t qctx, enum quota_type qtype,
+ int *usage_inconsistent);
+int parse_quota_opts(const char *opts, int (*func)(char *));
+
+/* parse_qtype.c */
+int parse_quota_types(const char *in_str, unsigned int *qtype_bits,
+ char **err_token);
+
+/*
+ * Return pointer to reserved inode field in superblock for given quota type.
+ *
+ * This allows the caller to get or set the quota inode by type without the
+ * need for the quota array to be contiguous in the superblock.
+ */
+static inline ext2_ino_t *quota_sb_inump(struct ext2_super_block *sb,
+ enum quota_type qtype)
+{
+ switch (qtype) {
+ case USRQUOTA:
+ return &sb->s_usr_quota_inum;
+ case GRPQUOTA:
+ return &sb->s_grp_quota_inum;
+ case PRJQUOTA:
+ return &sb->s_prj_quota_inum;
+ default:
+ return NULL;
+ }
+
+ return NULL;
+}
+
+#endif /* GUARD_QUOTAIO_H */
diff --git a/lib/support/quotaio_tree.c b/lib/support/quotaio_tree.c
new file mode 100644
index 0000000..5910e63
--- /dev/null
+++ b/lib/support/quotaio_tree.c
@@ -0,0 +1,687 @@
+/*
+ * Implementation of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "quotaio_tree.h"
+#include "quotaio.h"
+
+typedef char *dqbuf_t;
+
+#define freedqbuf(buf) ext2fs_free_mem(&buf)
+
+static inline dqbuf_t getdqbuf(void)
+{
+ dqbuf_t buf;
+ if (ext2fs_get_memzero(QT_BLKSIZE, &buf)) {
+ log_err("Failed to allocate dqbuf");
+ return NULL;
+ }
+
+ return buf;
+}
+
+/* Is given dquot empty? */
+int qtree_entry_unused(struct qtree_mem_dqinfo *info, char *disk)
+{
+ unsigned 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;
+}
+
+static inline void mark_quotafile_info_dirty(struct quota_handle *h)
+{
+ h->qh_io_flags |= IOFL_INFODIRTY;
+}
+
+/* Read given block */
+static void read_blk(struct quota_handle *h, unsigned int blk, dqbuf_t buf)
+{
+ int err;
+
+ err = h->e2fs_read(&h->qh_qf, blk << QT_BLKSIZE_BITS, buf,
+ QT_BLKSIZE);
+ if (err < 0)
+ log_err("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, unsigned int 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_err("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 (!buf)
+ return -ENOMEM;
+
+ if (info->dqi_free_blk) {
+ blk = info->dqi_free_blk;
+ read_blk(h, blk, buf);
+ info->dqi_free_blk = ext2fs_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,
+ unsigned int 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 = ext2fs_cpu_to_le32(info->dqi_free_blk);
+ dh->dqdh_prev_free = ext2fs_cpu_to_le32(0);
+ dh->dqdh_entries = ext2fs_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,
+ unsigned int blk)
+{
+ dqbuf_t tmpbuf = getdqbuf();
+ struct qt_disk_dqdbheader *dh = (struct qt_disk_dqdbheader *)buf;
+ unsigned int nextblk = ext2fs_le32_to_cpu(dh->dqdh_next_free), prevblk =
+
+ ext2fs_le32_to_cpu(dh->dqdh_prev_free);
+
+ if (!tmpbuf)
+ return;
+
+ 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 = ext2fs_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,
+ unsigned int 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;
+
+ if (!tmpbuf)
+ return;
+
+ dh->dqdh_next_free = ext2fs_cpu_to_le32(info->dqi_free_entry);
+ dh->dqdh_prev_free = ext2fs_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 =
+ ext2fs_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 unsigned int 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();
+ if (!buf) {
+ *err = -ENOMEM;
+ return 0;
+ }
+
+ 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 (ext2fs_le16_to_cpu(dh->dqdh_entries) + 1 >=
+ qtree_dqstr_in_blk(info))
+ remove_free_dqentry(h, buf, blk);
+
+ dh->dqdh_entries =
+ ext2fs_cpu_to_le16(ext2fs_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_err("find_free_dqentry(): Data block full unexpectedly.");
+
+ 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,
+ unsigned int * treeblk, int depth)
+{
+ dqbuf_t buf;
+ int newson = 0, newact = 0;
+ __le32 *ref;
+ unsigned int newblk;
+ int ret = 0;
+
+ log_debug("inserting in tree: treeblk=%u, depth=%d", *treeblk, depth);
+ buf = getdqbuf();
+ if (!buf)
+ return -ENOMEM;
+
+ 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 = (__le32 *) buf;
+ newblk = ext2fs_le32_to_cpu(ref[get_index(dquot->dq_id, depth)]);
+ if (!newblk)
+ newson = 1;
+ if (depth == QT_TREEDEPTH - 1) {
+ if (newblk)
+ log_err("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)] =
+ ext2fs_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)
+{
+ unsigned int tmp = QT_TREEOFF;
+
+ if (do_insert_tree(h, dquot, &tmp, 0) < 0)
+ log_err("Cannot write quota (id %u): %s",
+ (unsigned int) dquot->dq_id, strerror(errno));
+}
+
+/* Write dquot to file */
+void qtree_write_dquot(struct dquot *dquot)
+{
+ errcode_t retval;
+ unsigned int ret;
+ char *ddquot;
+ 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);
+ retval = ext2fs_get_mem(info->dqi_entry_size, &ddquot);
+ if (retval) {
+ errno = ENOMEM;
+ log_err("Quota write failed (id %u): %s",
+ (unsigned int)dquot->dq_id, strerror(errno));
+ return;
+ }
+ memset(ddquot, 0, 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_err("Quota write failed (id %u): %s",
+ (unsigned int)dquot->dq_id, strerror(errno));
+ }
+ ext2fs_free_mem(&ddquot);
+}
+
+/* Free dquot entry in data block */
+static void free_dqentry(struct quota_handle *h, struct dquot *dquot,
+ unsigned int blk)
+{
+ struct qt_disk_dqdbheader *dh;
+ struct qtree_mem_dqinfo *info = &h->qh_info.u.v2_mdqi.dqi_qtree;
+ dqbuf_t buf = getdqbuf();
+
+ if (!buf)
+ return;
+
+ if (dquot->dq_dqb.u.v2_mdqb.dqb_off >> QT_BLKSIZE_BITS != blk)
+ log_err("Quota structure has offset to other block (%u) "
+ "than it should (%u).", blk,
+ (unsigned int) (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 =
+ ext2fs_cpu_to_le16(ext2fs_le16_to_cpu(dh->dqdh_entries) - 1);
+
+ if (!ext2fs_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 (ext2fs_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,
+ unsigned int * blk, int depth)
+{
+ dqbuf_t buf = getdqbuf();
+ unsigned int newblk;
+ __le32 *ref = (__le32 *) buf;
+
+ if (!buf)
+ return;
+
+ read_blk(h, *blk, buf);
+ newblk = ext2fs_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)] = ext2fs_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)
+{
+ unsigned int 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 ext2_loff_t find_block_dqentry(struct quota_handle *h,
+ struct dquot *dquot, unsigned int 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);
+
+ if (!buf)
+ return -ENOMEM;
+
+ 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_err("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 ext2_loff_t find_tree_dqentry(struct quota_handle *h,
+ struct dquot *dquot,
+ unsigned int blk, int depth)
+{
+ dqbuf_t buf = getdqbuf();
+ ext2_loff_t ret = 0;
+ __le32 *ref = (__le32 *) buf;
+
+ if (!buf)
+ return -ENOMEM;
+
+ read_blk(h, blk, buf);
+ ret = 0;
+ blk = ext2fs_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 ext2_loff_t find_dqentry(struct quota_handle *h,
+ struct dquot *dquot)
+{
+ return find_tree_dqentry(h, dquot, QT_TREEOFF, 0);
+}
+
+/*
+ * Read dquot from disk.
+ */
+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;
+ ext2_loff_t offset;
+ unsigned int ret;
+ char *ddquot;
+ struct dquot *dquot = get_empty_dquot();
+
+ if (!dquot)
+ return NULL;
+ if (ext2fs_get_mem(info->dqi_entry_size, &ddquot)) {
+ ext2fs_free_mem(&dquot);
+ return NULL;
+ }
+
+ 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_err("Cannot read quota structure for id %u: %s",
+ dquot->dq_id, strerror(errno));
+ }
+ info->dqi_ops->disk2mem_dqblk(dquot, ddquot);
+ }
+ ext2fs_free_mem(&ddquot);
+ return dquot;
+}
+
+static int check_reference(struct quota_handle *h, unsigned int blk)
+{
+ if (blk >= h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks) {
+ log_err("Illegal reference (%u >= %u) in %s quota file",
+ blk, h->qh_info.u.v2_mdqi.dqi_qtree.dqi_blocks,
+ quota_type2name(h->qh_type));
+ return -1;
+ }
+ 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, unsigned int blk, char *bitmap,
+ int (*process_dquot) (struct dquot *, void *),
+ void *data)
+{
+ 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;
+
+ if (!buf)
+ return -1;
+
+ 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 = ext2fs_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)) {
+ dquot->dq_dqb.u.v2_mdqb.dqb_off =
+ (blk << QT_BLKSIZE_BITS) +
+ sizeof(struct qt_disk_dqdbheader) +
+ i * info->dqi_entry_size;
+ info->dqi_ops->disk2mem_dqblk(dquot, ddata);
+ if (process_dquot(dquot, data) < 0)
+ break;
+ }
+ freedqbuf(buf);
+ return entries;
+}
+
+static int report_tree(struct dquot *dquot, unsigned int blk, int depth,
+ char *bitmap,
+ int (*process_dquot) (struct dquot *, void *),
+ void *data)
+{
+ int entries = 0, ret, i;
+ dqbuf_t buf = getdqbuf();
+ __le32 *ref = (__le32 *) buf;
+
+ if (!buf)
+ return -1;
+
+ read_blk(dquot->dq_h, blk, buf);
+ if (depth == QT_TREEDEPTH - 1) {
+ for (i = 0; i < QT_BLKSIZE >> 2; i++) {
+ blk = ext2fs_le32_to_cpu(ref[i]);
+ if (check_reference(dquot->dq_h, blk)) {
+ entries = -1;
+ goto errout;
+ }
+ if (blk && !get_bit(bitmap, blk)) {
+ ret = report_block(dquot, blk, bitmap,
+ process_dquot, data);
+ if (ret < 0) {
+ entries = ret;
+ goto errout;
+ }
+ entries += ret;
+ }
+ }
+ } else {
+ for (i = 0; i < QT_BLKSIZE >> 2; i++) {
+ blk = ext2fs_le32_to_cpu(ref[i]);
+ if (blk) {
+ if (check_reference(dquot->dq_h, blk)) {
+ entries = -1;
+ goto errout;
+ }
+ ret = report_tree(dquot, blk, depth + 1,
+ bitmap, process_dquot,
+ data);
+ if (ret < 0) {
+ entries = ret;
+ goto errout;
+ }
+ entries += ret;
+ }
+ }
+ }
+errout:
+ freedqbuf(buf);
+ return entries;
+}
+
+static unsigned int find_set_bits(char *bmp, int blocks)
+{
+ unsigned int used = 0;
+ int i;
+
+ 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 *, void *),
+ void *data)
+{
+ int ret;
+ 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();
+
+ if (!dquot)
+ return -1;
+
+ dquot->dq_h = h;
+ if (ext2fs_get_memzero((info->dqi_blocks + 7) >> 3, &bitmap)) {
+ ext2fs_free_mem(&dquot);
+ return -1;
+ }
+ ret = report_tree(dquot, QT_TREEOFF, 0, bitmap, process_dquot, data);
+ if (ret < 0)
+ goto errout;
+ v2info->dqi_used_entries = ret;
+ v2info->dqi_data_blocks = find_set_bits(bitmap, info->dqi_blocks);
+ ret = 0;
+errout:
+ ext2fs_free_mem(&bitmap);
+ ext2fs_free_mem(&dquot);
+ return ret;
+}
diff --git a/lib/support/quotaio_tree.h b/lib/support/quotaio_tree.h
new file mode 100644
index 0000000..b0b7257
--- /dev/null
+++ b/lib/support/quotaio_tree.h
@@ -0,0 +1,64 @@
+/*
+ * Definitions of structures for vfsv0 quota format
+ */
+
+#ifndef _LINUX_QUOTA_TREE_H
+#define _LINUX_QUOTA_TREE_H
+
+#include <sys/types.h>
+
+typedef __u32 qid_t; /* Type in which we store ids in memory */
+
+#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 {
+ __le32 dqdh_next_free; /* Number of next block with free
+ * entry */
+ __le32 dqdh_prev_free; /* Number of previous block with free
+ * entry */
+ __le16 dqdh_entries; /* Number of valid entries in block */
+ __le16 dqdh_pad1;
+ __le32 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);
+};
+
+/* In-memory 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 *, void *), void *data);
+
+int qtree_dqstr_in_blk(struct qtree_mem_dqinfo *info);
+
+#endif /* _LINUX_QUOTAIO_TREE_H */
diff --git a/lib/support/quotaio_v2.c b/lib/support/quotaio_v2.c
new file mode 100644
index 0000000..d09294b
--- /dev/null
+++ b/lib/support/quotaio_v2.c
@@ -0,0 +1,389 @@
+/*
+ * Implementation of new quotafile format
+ *
+ * Jan Kara <jack@suse.cz> - sponsored by SuSE CR
+ */
+
+#include "config.h"
+#include <sys/types.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "common.h"
+#include "quotaio_v2.h"
+#include "dqblk_v2.h"
+#include "quotaio.h"
+#include "quotaio_tree.h"
+
+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);
+static int v2_scan_dquots(struct quota_handle *h,
+ int (*process_dquot) (struct dquot *dquot,
+ void *data),
+ void *data);
+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,
+};
+
+/*
+ * Copy dquot from disk to memory
+ */
+static void v2r0_disk2memdqblk(struct dquot *dquot, void *dp)
+{
+ struct util_dqblk *m = &dquot->dq_dqb;
+ struct v2r0_disk_dqblk *d = dp, empty;
+
+ dquot->dq_id = ext2fs_le32_to_cpu(d->dqb_id);
+ m->dqb_ihardlimit = ext2fs_le32_to_cpu(d->dqb_ihardlimit);
+ m->dqb_isoftlimit = ext2fs_le32_to_cpu(d->dqb_isoftlimit);
+ m->dqb_bhardlimit = ext2fs_le32_to_cpu(d->dqb_bhardlimit);
+ m->dqb_bsoftlimit = ext2fs_le32_to_cpu(d->dqb_bsoftlimit);
+ m->dqb_curinodes = ext2fs_le32_to_cpu(d->dqb_curinodes);
+ m->dqb_curspace = ext2fs_le64_to_cpu(d->dqb_curspace);
+ m->dqb_itime = ext2fs_le64_to_cpu(d->dqb_itime);
+ m->dqb_btime = ext2fs_le64_to_cpu(d->dqb_btime);
+
+ memset(&empty, 0, sizeof(struct v2r0_disk_dqblk));
+ empty.dqb_itime = ext2fs_cpu_to_le64(1);
+ if (!memcmp(&empty, dp, sizeof(struct v2r0_disk_dqblk)))
+ m->dqb_itime = 0;
+}
+
+/*
+ * Copy dquot from memory to disk
+ */
+static void v2r0_mem2diskdqblk(void *dp, struct dquot *dquot)
+{
+ struct util_dqblk *m = &dquot->dq_dqb;
+ struct v2r0_disk_dqblk *d = dp;
+
+ d->dqb_ihardlimit = ext2fs_cpu_to_le32(m->dqb_ihardlimit);
+ d->dqb_isoftlimit = ext2fs_cpu_to_le32(m->dqb_isoftlimit);
+ d->dqb_bhardlimit = ext2fs_cpu_to_le32(m->dqb_bhardlimit);
+ d->dqb_bsoftlimit = ext2fs_cpu_to_le32(m->dqb_bsoftlimit);
+ d->dqb_curinodes = ext2fs_cpu_to_le32(m->dqb_curinodes);
+ d->dqb_curspace = ext2fs_cpu_to_le64(m->dqb_curspace);
+ d->dqb_itime = ext2fs_cpu_to_le64(m->dqb_itime);
+ d->dqb_btime = ext2fs_cpu_to_le64(m->dqb_btime);
+ d->dqb_id = ext2fs_cpu_to_le32(dquot->dq_id);
+ if (qtree_entry_unused(&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree, dp))
+ d->dqb_itime = ext2fs_cpu_to_le64(1);
+}
+
+static int v2r0_is_id(void *dp, struct dquot *dquot)
+{
+ struct v2r0_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 ext2fs_le32_to_cpu(d->dqb_id) == dquot->dq_id;
+}
+
+static struct qtree_fmt_operations v2r0_fmt_ops = {
+ .mem2disk_dqblk = v2r0_mem2diskdqblk,
+ .disk2mem_dqblk = v2r0_disk2memdqblk,
+ .is_id = v2r0_is_id,
+};
+
+/*
+ * 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 = ext2fs_le32_to_cpu(d->dqb_id);
+ m->dqb_ihardlimit = ext2fs_le64_to_cpu(d->dqb_ihardlimit);
+ m->dqb_isoftlimit = ext2fs_le64_to_cpu(d->dqb_isoftlimit);
+ m->dqb_bhardlimit = ext2fs_le64_to_cpu(d->dqb_bhardlimit);
+ m->dqb_bsoftlimit = ext2fs_le64_to_cpu(d->dqb_bsoftlimit);
+ m->dqb_curinodes = ext2fs_le64_to_cpu(d->dqb_curinodes);
+ m->dqb_curspace = ext2fs_le64_to_cpu(d->dqb_curspace);
+ m->dqb_itime = ext2fs_le64_to_cpu(d->dqb_itime);
+ m->dqb_btime = ext2fs_le64_to_cpu(d->dqb_btime);
+
+ memset(&empty, 0, sizeof(struct v2r1_disk_dqblk));
+ empty.dqb_itime = ext2fs_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 = ext2fs_cpu_to_le64(m->dqb_ihardlimit);
+ d->dqb_isoftlimit = ext2fs_cpu_to_le64(m->dqb_isoftlimit);
+ d->dqb_bhardlimit = ext2fs_cpu_to_le64(m->dqb_bhardlimit);
+ d->dqb_bsoftlimit = ext2fs_cpu_to_le64(m->dqb_bsoftlimit);
+ d->dqb_curinodes = ext2fs_cpu_to_le64(m->dqb_curinodes);
+ d->dqb_curspace = ext2fs_cpu_to_le64(m->dqb_curspace);
+ d->dqb_itime = ext2fs_cpu_to_le64(m->dqb_itime);
+ d->dqb_btime = ext2fs_cpu_to_le64(m->dqb_btime);
+ d->dqb_id = ext2fs_cpu_to_le32(dquot->dq_id);
+ if (qtree_entry_unused(&dquot->dq_h->qh_info.u.v2_mdqi.dqi_qtree, dp))
+ d->dqb_itime = ext2fs_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 ext2fs_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 = ext2fs_le32_to_cpu(d->dqi_bgrace);
+ m->dqi_igrace = ext2fs_le32_to_cpu(d->dqi_igrace);
+ m->u.v2_mdqi.dqi_flags = ext2fs_le32_to_cpu(d->dqi_flags) & V2_DQF_MASK;
+ m->u.v2_mdqi.dqi_qtree.dqi_blocks = ext2fs_le32_to_cpu(d->dqi_blocks);
+ m->u.v2_mdqi.dqi_qtree.dqi_free_blk =
+ ext2fs_le32_to_cpu(d->dqi_free_blk);
+ m->u.v2_mdqi.dqi_qtree.dqi_free_entry =
+ ext2fs_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 = ext2fs_cpu_to_le32(m->dqi_bgrace);
+ d->dqi_igrace = ext2fs_cpu_to_le32(m->dqi_igrace);
+ d->dqi_flags = ext2fs_cpu_to_le32(m->u.v2_mdqi.dqi_flags & V2_DQF_MASK);
+ d->dqi_blocks = ext2fs_cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_blocks);
+ d->dqi_free_blk =
+ ext2fs_cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_blk);
+ d->dqi_free_entry =
+ ext2fs_cpu_to_le32(m->u.v2_mdqi.dqi_qtree.dqi_free_entry);
+}
+
+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 be_magic;
+
+ if (fmt != QFMT_VFS_V1)
+ return 0;
+
+ if (!v2_read_header(h, &dqh))
+ return 0;
+
+ be_magic = ext2fs_be32_to_cpu((__force __be32)dqh.dqh_magic);
+ if (be_magic == file_magics[type]) {
+ log_err("Your quota file is stored in wrong endianness");
+ return 0;
+ }
+ if (V2_VERSION_R0 != ext2fs_le32_to_cpu(dqh.dqh_version) &&
+ V2_VERSION_R1 != ext2fs_le32_to_cpu(dqh.dqh_version))
+ return 0;
+ return 1;
+}
+
+/*
+ * Open quotafile
+ */
+static int v2_init_io(struct quota_handle *h)
+{
+ struct v2_disk_dqheader dqh;
+ struct v2_disk_dqinfo ddqinfo;
+ struct v2_mem_dqinfo *info;
+ __u64 filesize;
+ int version;
+
+ if (!v2_read_header(h, &dqh))
+ return -1;
+ version = ext2fs_le32_to_cpu(dqh.dqh_version);
+
+ if (version == V2_VERSION_R0) {
+ h->qh_info.u.v2_mdqi.dqi_qtree.dqi_entry_size =
+ sizeof(struct v2r0_disk_dqblk);
+ h->qh_info.u.v2_mdqi.dqi_qtree.dqi_ops = &v2r0_fmt_ops;
+ } else {
+ 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;
+ }
+
+ /* Read information about quotafile */
+ if (h->e2fs_read(&h->qh_qf, V2_DQINFOOFF, &ddqinfo,
+ sizeof(ddqinfo)) != sizeof(ddqinfo))
+ return -1;
+ v2_disk2memdqinfo(&h->qh_info, &ddqinfo);
+
+ /* Check to make sure quota file info is sane */
+ info = &h->qh_info.u.v2_mdqi;
+ if (ext2fs_file_get_lsize(h->qh_qf.e2_file, &filesize))
+ return -1;
+ if ((filesize > (1U << 31)) ||
+ (info->dqi_qtree.dqi_blocks >
+ (filesize + QT_BLKSIZE - 1) >> QT_BLKSIZE_BITS)) {
+ log_err("Quota inode %u corrupted: file size %llu; "
+ "dqi_blocks %u", h->qh_qf.ino,
+ (unsigned long long) filesize,
+ info->dqi_qtree.dqi_blocks);
+ return -1;
+ }
+ if (info->dqi_qtree.dqi_free_blk >= info->dqi_qtree.dqi_blocks) {
+ log_err("Quota inode %u corrupted: free_blk %u; dqi_blocks %u",
+ h->qh_qf.ino, info->dqi_qtree.dqi_free_blk,
+ info->dqi_qtree.dqi_blocks);
+ return -1;
+ }
+ if (info->dqi_qtree.dqi_free_entry >= info->dqi_qtree.dqi_blocks) {
+ log_err("Quota inode %u corrupted: free_entry %u; "
+ "dqi_blocks %u", h->qh_qf.ino,
+ info->dqi_qtree.dqi_free_entry,
+ info->dqi_qtree.dqi_blocks);
+ return -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;
+
+ if (h->qh_fmt != QFMT_VFS_V1)
+ return -1;
+
+ /* Write basic quota header */
+ ddqheader.dqh_magic = ext2fs_cpu_to_le32(file_magics[h->qh_type]);
+ ddqheader.dqh_version = ext2fs_cpu_to_le32(V2_VERSION_R1);
+ 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 from disk
+ */
+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)
+{
+ 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 *, void *),
+ void *data)
+{
+ return qtree_scan_dquots(h, process_dquot, data);
+}
+
+/* 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 EXT2FS_ATTR((unused)),
+ int verbose EXT2FS_ATTR((unused)))
+{
+ log_err("Not Implemented.");
+ return -1;
+}
diff --git a/lib/support/quotaio_v2.h b/lib/support/quotaio_v2.h
new file mode 100644
index 0000000..35054ca
--- /dev/null
+++ b/lib/support/quotaio_v2.h
@@ -0,0 +1,69 @@
+/*
+ *
+ * Header file for disk format of new quotafile format
+ *
+ */
+
+#ifndef GUARD_QUOTAIO_V2_H
+#define GUARD_QUOTAIO_V2_H
+
+#include <sys/types.h>
+#include "quotaio.h"
+
+/* Offset of info header in file */
+#define V2_DQINFOOFF sizeof(struct v2_disk_dqheader)
+/* Supported version of quota-tree format */
+#define V2_VERSION_R1 1
+#define V2_VERSION_R0 0
+
+struct v2_disk_dqheader {
+ __le32 dqh_magic; /* Magic number identifying file */
+ __le32 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 {
+ __le32 dqi_bgrace; /* Time before block soft limit becomes
+ * hard limit */
+ __le32 dqi_igrace; /* Time before inode soft limit becomes
+ * hard limit */
+ __le32 dqi_flags; /* Flags for quotafile (DQF_*) */
+ __le32 dqi_blocks; /* Number of blocks in file */
+ __le32 dqi_free_blk; /* Number of first free block in the list */
+ __le32 dqi_free_entry; /* Number of block with at least one
+ * free entry */
+} __attribute__ ((packed));
+
+struct v2r0_disk_dqblk {
+ __le32 dqb_id; /* id this quota applies to */
+ __le32 dqb_ihardlimit; /* absolute limit on allocated inodes */
+ __le32 dqb_isoftlimit; /* preferred inode limit */
+ __le32 dqb_curinodes; /* current # allocated inodes */
+ __le32 dqb_bhardlimit; /* absolute limit on disk space
+ * (in QUOTABLOCK_SIZE) */
+ __le32 dqb_bsoftlimit; /* preferred limit on disk space
+ * (in QUOTABLOCK_SIZE) */
+ __le64 dqb_curspace; /* current space occupied (in bytes) */
+ __le64 dqb_btime; /* time limit for excessive disk use */
+ __le64 dqb_itime; /* time limit for excessive inode use */
+} __attribute__ ((packed));
+
+struct v2r1_disk_dqblk {
+ __le32 dqb_id; /* id this quota applies to */
+ __le32 dqb_pad;
+ __le64 dqb_ihardlimit; /* absolute limit on allocated inodes */
+ __le64 dqb_isoftlimit; /* preferred inode limit */
+ __le64 dqb_curinodes; /* current # allocated inodes */
+ __le64 dqb_bhardlimit; /* absolute limit on disk space
+ * (in QUOTABLOCK_SIZE) */
+ __le64 dqb_bsoftlimit; /* preferred limit on disk space
+ * (in QUOTABLOCK_SIZE) */
+ __le64 dqb_curspace; /* current space occupied (in bytes) */
+ __le64 dqb_btime; /* time limit for excessive disk use */
+ __le64 dqb_itime; /* time limit for excessive inode use */
+} __attribute__ ((packed));
+
+#endif
diff --git a/lib/support/sort_r.h b/lib/support/sort_r.h
new file mode 100644
index 0000000..8473ca8
--- /dev/null
+++ b/lib/support/sort_r.h
@@ -0,0 +1,325 @@
+/* Isaac Turner 29 April 2014 Public Domain */
+#ifndef SORT_R_H_
+#define SORT_R_H_
+
+#include <stdlib.h>
+#include <string.h>
+
+/*
+
+sort_r function to be exported.
+
+Parameters:
+ base is the array to be sorted
+ nel is the number of elements in the array
+ width is the size in bytes of each element of the array
+ compar is the comparison function
+ arg is a pointer to be passed to the comparison function
+
+void sort_r(void *base, size_t nel, size_t width,
+ int (*compar)(const void *_a, const void *_b, void *_arg),
+ void *arg);
+
+*/
+
+#define _SORT_R_INLINE inline
+
+#if (defined HAVE_GNU_QSORT_R)
+# define _SORT_R_GNU
+#elif (defined HAVE_BSD_QSORT_R)
+# define _SORT_R_BSD
+#elif (defined __gnu_hurd__ || defined __GNU__ || \
+ defined __MINGW32__ || defined __GLIBC__)
+# define _SORT_R_GNU
+#elif (defined __APPLE__ || defined __MACH__ || defined __DARWIN__ || \
+ defined __FreeBSD__ || defined __DragonFly__)
+# define _SORT_R_BSD
+#elif (defined _WIN32 || defined _WIN64 || defined __WINDOWS__)
+# define _SORT_R_WINDOWS
+# undef _SORT_R_INLINE
+# define _SORT_R_INLINE __inline
+#else
+ /* Using our own recursive quicksort sort_r_simple() */
+#endif
+
+#if (defined NESTED_QSORT && NESTED_QSORT == 0)
+# undef NESTED_QSORT
+#endif
+
+#define SORT_R_SWAP(a,b,tmp) ((tmp) = (a), (a) = (b), (b) = (tmp))
+
+/* swap a and b */
+/* a and b must not be equal! */
+static _SORT_R_INLINE void sort_r_swap(char *__restrict a, char *__restrict b,
+ size_t w)
+{
+ char tmp, *end = a+w;
+ for(; a < end; a++, b++) { SORT_R_SWAP(*a, *b, tmp); }
+}
+
+/* swap a, b iff a>b */
+/* a and b must not be equal! */
+/* __restrict is same as restrict but better support on old machines */
+static _SORT_R_INLINE int sort_r_cmpswap(char *__restrict a,
+ char *__restrict b, size_t w,
+ int (*compar)(const void *_a,
+ const void *_b,
+ void *_arg),
+ void *arg)
+{
+ if(compar(a, b, arg) > 0) {
+ sort_r_swap(a, b, w);
+ return 1;
+ }
+ return 0;
+}
+
+/*
+Swap consecutive blocks of bytes of size na and nb starting at memory addr ptr,
+with the smallest swap so that the blocks are in the opposite order. Blocks may
+be internally re-ordered e.g.
+
+ 12345ab -> ab34512
+ 123abc -> abc123
+ 12abcde -> deabc12
+*/
+static _SORT_R_INLINE void sort_r_swap_blocks(char *ptr, size_t na, size_t nb)
+{
+ if(na > 0 && nb > 0) {
+ if(na > nb) { sort_r_swap(ptr, ptr+na, nb); }
+ else { sort_r_swap(ptr, ptr+nb, na); }
+ }
+}
+
+/* Implement recursive quicksort ourselves */
+/* Note: quicksort is not stable, equivalent values may be swapped */
+static _SORT_R_INLINE void sort_r_simple(void *base, size_t nel, size_t w,
+ int (*compar)(const void *_a,
+ const void *_b,
+ void *_arg),
+ void *arg)
+{
+ char *b = (char *)base, *end = b + nel*w;
+
+ /* for(size_t i=0; i<nel; i++) {printf("%4i", *(int*)(b + i*sizeof(int)));}
+ printf("\n"); */
+
+ if(nel < 10) {
+ /* Insertion sort for arbitrarily small inputs */
+ char *pi, *pj;
+ for(pi = b+w; pi < end; pi += w) {
+ for(pj = pi; pj > b && sort_r_cmpswap(pj-w,pj,w,compar,arg); pj -= w) {}
+ }
+ }
+ else
+ {
+ /* nel > 6; Quicksort */
+
+ int cmp;
+ char *pl, *ple, *pr, *pre, *pivot;
+ char *last = b+w*(nel-1), *tmp;
+
+ /*
+ Use median of second, middle and second-last items as pivot.
+ First and last may have been swapped with pivot and therefore be extreme
+ */
+ char *l[3];
+ l[0] = b + w;
+ l[1] = b+w*(nel/2);
+ l[2] = last - w;
+
+ /* printf("pivots: %i, %i, %i\n", *(int*)l[0], *(int*)l[1], *(int*)l[2]); */
+
+ if(compar(l[0],l[1],arg) > 0) { SORT_R_SWAP(l[0], l[1], tmp); }
+ if(compar(l[1],l[2],arg) > 0) {
+ SORT_R_SWAP(l[1], l[2], tmp);
+ if(compar(l[0],l[1],arg) > 0) { SORT_R_SWAP(l[0], l[1], tmp); }
+ }
+
+ /* swap mid value (l[1]), and last element to put pivot as last element */
+ if(l[1] != last) { sort_r_swap(l[1], last, w); }
+
+ /*
+ pl is the next item on the left to be compared to the pivot
+ pr is the last item on the right that was compared to the pivot
+ ple is the left position to put the next item that equals the pivot
+ ple is the last right position where we put an item that equals the pivot
+
+ v- end (beyond the array)
+ EEEEEELLLLLLLLuuuuuuuuGGGGGGGEEEEEEEE.
+ ^- b ^- ple ^- pl ^- pr ^- pre ^- last (where the pivot is)
+
+ Pivot comparison key:
+ E = equal, L = less than, u = unknown, G = greater than, E = equal
+ */
+ pivot = last;
+ ple = pl = b;
+ pre = pr = last;
+
+ /*
+ Strategy:
+ Loop into the list from the left and right at the same time to find:
+ - an item on the left that is greater than the pivot
+ - an item on the right that is less than the pivot
+ Once found, they are swapped and the loop continues.
+ Meanwhile items that are equal to the pivot are moved to the edges of the
+ array.
+ */
+ while(pl < pr) {
+ /* Move left hand items which are equal to the pivot to the far left.
+ break when we find an item that is greater than the pivot */
+ for(; pl < pr; pl += w) {
+ cmp = compar(pl, pivot, arg);
+ if(cmp > 0) { break; }
+ else if(cmp == 0) {
+ if(ple < pl) { sort_r_swap(ple, pl, w); }
+ ple += w;
+ }
+ }
+ /* break if last batch of left hand items were equal to pivot */
+ if(pl >= pr) { break; }
+ /* Move right hand items which are equal to the pivot to the far right.
+ break when we find an item that is less than the pivot */
+ for(; pl < pr; ) {
+ pr -= w; /* Move right pointer onto an unprocessed item */
+ cmp = compar(pr, pivot, arg);
+ if(cmp == 0) {
+ pre -= w;
+ if(pr < pre) { sort_r_swap(pr, pre, w); }
+ }
+ else if(cmp < 0) {
+ if(pl < pr) { sort_r_swap(pl, pr, w); }
+ pl += w;
+ break;
+ }
+ }
+ }
+
+ pl = pr; /* pr may have gone below pl */
+
+ /*
+ Now we need to go from: EEELLLGGGGEEEE
+ to: LLLEEEEEEEGGGG
+
+ Pivot comparison key:
+ E = equal, L = less than, u = unknown, G = greater than, E = equal
+ */
+ sort_r_swap_blocks(b, ple-b, pl-ple);
+ sort_r_swap_blocks(pr, pre-pr, end-pre);
+
+ /*for(size_t i=0; i<nel; i++) {printf("%4i", *(int*)(b + i*sizeof(int)));}
+ printf("\n");*/
+
+ sort_r_simple(b, (pl-ple)/w, w, compar, arg);
+ sort_r_simple(end-(pre-pr), (pre-pr)/w, w, compar, arg);
+ }
+}
+
+
+#if defined NESTED_QSORT
+
+ static _SORT_R_INLINE void sort_r(void *base, size_t nel, size_t width,
+ int (*compar)(const void *_a,
+ const void *_b,
+ void *aarg),
+ void *arg)
+ {
+ int nested_cmp(const void *a, const void *b)
+ {
+ return compar(a, b, arg);
+ }
+
+ qsort(base, nel, width, nested_cmp);
+ }
+
+#else /* !NESTED_QSORT */
+
+ /* Declare structs and functions */
+
+ #if defined _SORT_R_BSD
+
+ /* Ensure qsort_r is defined */
+ extern void qsort_r(void *base, size_t nel, size_t width, void *thunk,
+ int (*compar)(void *_thunk,
+ const void *_a, const void *_b));
+
+ #endif
+
+ #if defined _SORT_R_BSD || defined _SORT_R_WINDOWS
+
+ /* BSD (qsort_r), Windows (qsort_s) require argument swap */
+
+ struct sort_r_data
+ {
+ void *arg;
+ int (*compar)(const void *_a, const void *_b, void *_arg);
+ };
+
+ static _SORT_R_INLINE int sort_r_arg_swap(void *s,
+ const void *a, const void *b)
+ {
+ struct sort_r_data *ss = (struct sort_r_data*)s;
+ return (ss->compar)(a, b, ss->arg);
+ }
+
+ #endif
+
+ #if defined _SORT_R_GNU
+
+ typedef int(* __compar_d_fn_t)(const void *, const void *, void *);
+ extern void qsort_r(void *base, size_t nel, size_t width,
+ __compar_d_fn_t __compar, void *arg)
+ __attribute__((nonnull (1, 4)));
+
+ #endif
+
+ /* implementation */
+
+ static _SORT_R_INLINE void sort_r(void *base, size_t nel, size_t width,
+ int (*compar)(const void *_a,
+ const void *_b, void *_arg),
+ void *arg)
+ {
+ #if defined _SORT_R_GNU
+
+ #if defined __GLIBC__ && ((__GLIBC__ < 2) || (__GLIBC__ == 2 && __GLIBC_MINOR__ < 8))
+
+ /* no qsort_r in glibc before 2.8, need to use nested qsort */
+ sort_r_simple(base, nel, width, compar, arg);
+
+ #else
+
+ qsort_r(base, nel, width, compar, arg);
+
+ #endif
+
+ #elif defined _SORT_R_BSD
+
+ struct sort_r_data tmp;
+ tmp.arg = arg;
+ tmp.compar = compar;
+ qsort_r(base, nel, width, &tmp, sort_r_arg_swap);
+
+ #elif defined _SORT_R_WINDOWS
+
+ struct sort_r_data tmp;
+ tmp.arg = arg;
+ tmp.compar = compar;
+ qsort_s(base, nel, width, sort_r_arg_swap, &tmp);
+
+ #else
+
+ /* Fall back to our own quicksort implementation */
+ sort_r_simple(base, nel, width, compar, arg);
+
+ #endif
+ }
+
+#endif /* !NESTED_QSORT */
+
+#undef _SORT_R_INLINE
+#undef _SORT_R_WINDOWS
+#undef _SORT_R_GNU
+#undef _SORT_R_BSD
+
+#endif /* SORT_R_H_ */