diff options
-rw-r--r-- | Makefile.in | 11 | ||||
-rw-r--r-- | configure.in | 30 | ||||
-rw-r--r-- | quota.h | 30 | ||||
-rw-r--r-- | quota_nld.c | 367 |
4 files changed, 434 insertions, 4 deletions
diff --git a/Makefile.in b/Makefile.in index f6af35d..33d5514 100644 --- a/Makefile.in +++ b/Makefile.in @@ -1,8 +1,10 @@ -PROGS = quotacheck quotaon quota quot repquota warnquota quotastats xqmstats edquota setquota convertquota rpc.rquotad +PROGS = quotacheck quotaon quota quot repquota warnquota quotastats xqmstats edquota setquota convertquota rpc.rquotad quota_nld SOURCES = bylabel.c common.c convertquota.c edquota.c pot.c quot.c quota.c quotacheck.c quotacheck_v1.c quotacheck_v2.c quotaio.c quotaio_rpc.c quotaio_v1.c quotaio_v2.c quotaio_xfs.c quotaio_generic.c quotaon.c quotaon_xfs.c quotaops.c quotastats.c quotasys.c repquota.c rquota_client.c rquota_server.c rquota_svc.c setquota.c warnquota.c xqmstats.c svc_socket.c -VERSIONDEF = -DQUOTA_VERSION=\"3.14\" +VERSIONDEF = -DQUOTA_VERSION=\"3.15\" CFLAGS = @CFLAGS@ @EXT2_DIRECT@ -D_GNU_SOURCE -Wall -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 $(VERSIONDEF) +CPPFLAGS = @CPPFLAGS@ EXT2LIBS = @EXT2LIBS@ +NETLINKLIBS = @NETLINKLIBS@ RPCSRC = rquota.h rquota_xdr.c rquota_clnt.c LIBS = @LIBS@ LDFLAGS = @LDFLAGS@ @@ -130,6 +132,11 @@ convertquota: convertquota.o $(LIBOBJS) rpc.rquotad: rquota_server.o rquota_svc.o svc_socket.o $(LIBOBJS) $(CC) $(LDFLAGS) -o $@ $^ $(LIBS) +ifneq ($(NETLINKLIBS),) +quota_nld: quota_nld.o $(LIBOBJS) + $(CC) $(LDFLAGS) -o $@ $^ $(NETLINKLIBS) +endif + pot.o: pot.c pot.h rquota.h: rquota.x diff --git a/configure.in b/configure.in index 7063704..4350a51 100644 --- a/configure.in +++ b/configure.in @@ -93,6 +93,31 @@ if test "x$enable_ext2direct" != "xno"; then fi AC_SUBST(EXT2LIBS) +AC_ARG_WITH(dbus_include, + [ --with-dbus_include=path Path to directory with dbus include directory [default=/usr/include/dbus-1.0/]], + DBUS_INCLUDE="$with_dbus_include", + DBUS_INCLUDE="/usr/include/dbus-1.0/") +AC_ARG_WITH(dbus_arch_include, + [ --with-dbus_arch_include=path Path to directory with dbus arch-dependent include directory [default=/usr/lib/dbus-1.0/include/]], + DBUS_ARCH_INCLUDE="$with_dbus_arch_include", + DBUS_ARCH_INCLUDE="/usr/lib/dbus-1.0/include/") + +AC_ARG_ENABLE(netlink, + [ --enable-netlink=[yes/no] Compile daemon receiving quota messages via netlink [default=yes].], + , + enable_netlink="yes") +if test "x$enable_netlink" != "xno"; then + AC_CHECK_LIB(nl, genl_register, NETLINKLIBS="-lnl $NETLINKLIBS") + AC_CHECK_LIB(dbus-1, dbus_bus_get, NETLINKLIBS="-ldbus-1 $NETLINKLIBS") + if test "${ac_cv_lib_nl_genl_register}" != "yes" -o "${ac_cv_lib_dbus_1_dbus_bus_get}" != "yes"; then + AC_MSG_ERROR([Required libraries for quota netlink daemon not found.]); + fi + CPPFLAGS="-I $DBUS_INCLUDE -I $DBUS_ARCH_INCLUDE $CPPFLAGS" + AC_CHECK_HEADERS(dbus/dbus.h, , AC_MSG_ERROR([Required headers for quota netlink daemon not found.])) + +fi +AC_SUBST(NETLINKLIBS) + AC_SEARCH_LIBS(gethostbyname, nsl) AC_CACHE_VAL(ac_cv_lib_wrap_main, saved_LIBS="$LIBS" @@ -135,11 +160,11 @@ AC_ARG_ENABLE(rpcsetquota, , enable_rpcsetquota="no") AC_ARG_ENABLE(xfs_roothack, - [ --enable-xfs_roothack=[yes/no] Support old XFS root filesystems [default=no].], + [ --enable-xfs_roothack=[yes/no] Support old XFS root filesystems [default=no].], , enable_xfs_roothack="no") AC_ARG_ENABLE(bsd_behaviour, - [ --enable-bsd_behaviour=[yes/no] Mimic BSD behaviour [default=yes].], + [ --enable-bsd_behaviour=[yes/no] Mimic BSD behaviour [default=yes].], , enable_bsd_behaviour="yes") AC_ARG_ENABLE(libefence, @@ -186,5 +211,6 @@ fi AC_SUBST(LIBMALLOC) AC_SUBST(INSTMO) AC_SUBST(ROOTSBIN) +AC_SUBST(CPPFLAGS) AC_OUTPUT(Makefile) @@ -102,6 +102,36 @@ struct if_dqinfo { u_int32_t dqi_valid; }; +/* + * Definitions for quota netlink interface + */ +#define QUOTA_NL_NOWARN 0 +#define QUOTA_NL_IHARDWARN 1 /* Inode hardlimit reached */ +#define QUOTA_NL_ISOFTLONGWARN 2 /* Inode grace time expired */ +#define QUOTA_NL_ISOFTWARN 3 /* Inode softlimit reached */ +#define QUOTA_NL_BHARDWARN 4 /* Block hardlimit reached */ +#define QUOTA_NL_BSOFTLONGWARN 5 /* Block grace time expired */ +#define QUOTA_NL_BSOFTWARN 6 /* Block softlimit reached */ + +enum { + QUOTA_NL_C_UNSPEC, + QUOTA_NL_C_WARNING, + __QUOTA_NL_C_MAX, +}; +#define QUOTA_NL_C_MAX (__QUOTA_NL_C_MAX - 1) + +enum { + QUOTA_NL_A_UNSPEC, + QUOTA_NL_A_QTYPE, + QUOTA_NL_A_EXCESS_ID, + QUOTA_NL_A_WARNING, + QUOTA_NL_A_DEV_MAJOR, + QUOTA_NL_A_DEV_MINOR, + QUOTA_NL_A_CAUSED_ID, + __QUOTA_NL_A_MAX, +}; +#define QUOTA_NL_A_MAX (__QUOTA_NL_A_MAX - 1) + /* Quota format identifiers */ #define QFMT_VFS_OLD 1 #define QFMT_VFS_V0 2 diff --git a/quota_nld.c b/quota_nld.c new file mode 100644 index 0000000..827833c --- /dev/null +++ b/quota_nld.c @@ -0,0 +1,367 @@ +/* + * A deamon to read quota warning messages from the kernel netlink socket + * and either pipe them to the system DBUS or write them to user's console + * + * Copyright (c) 2007 SUSE CR, All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it would be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + */ + +#include <stdio.h> +#include <unistd.h> +#include <getopt.h> +#include <utmp.h> +#include <errno.h> +#include <string.h> +#include <fcntl.h> + +#include <netlink/netlink.h> +#include <netlink/netlink-kernel.h> +#include <netlink/genl/mngt.h> + +#include <dbus/dbus.h> + +#include "pot.h" +#include "common.h" +#include "quotasys.h" +#include "quota.h" + +char *progname; + +static const struct option options[] = { + { "version", 0, NULL, 'V' }, + { "help", 0, NULL, 'h' }, + { "no-dbus", 0, NULL, 'D' }, + { "no-console", 0, NULL, 'C' }, + { "no-daemon", 0, NULL, 'n' }, + { NULL, 0, NULL, 0 } +}; + +struct quota_warning { + uint32_t qtype; + uint64_t excess_id; + uint32_t warntype; + uint32_t dev_major; + uint32_t dev_minor; + uint64_t caused_id; +}; + +static int quota_nl_warn_cmd_parser(struct genl_ops *ops, struct genl_cmd *cmd, + struct genl_info *info, void *arg); + +static struct nla_policy quota_nl_warn_cmd_policy[QUOTA_NL_A_MAX+1] = { + [QUOTA_NL_A_QTYPE] = { .type = NLA_U32 }, + [QUOTA_NL_A_EXCESS_ID] = { .type = NLA_U64 }, + [QUOTA_NL_A_WARNING] = { .type = NLA_U32 }, + [QUOTA_NL_A_DEV_MAJOR] = { .type = NLA_U32 }, + [QUOTA_NL_A_DEV_MINOR] = { .type = NLA_U32 }, + [QUOTA_NL_A_CAUSED_ID] = { .type = NLA_U64 }, +}; + +static struct genl_cmd quota_nl_warn_cmd = { + .c_id = QUOTA_NL_C_WARNING, + .c_name = "Quota warning", + .c_maxattr = QUOTA_NL_A_MAX, + .c_attr_policy = quota_nl_warn_cmd_policy, + .c_msg_parser = quota_nl_warn_cmd_parser, +}; + +static struct genl_ops quota_nl_ops = { + .o_cmds = "a_nl_warn_cmd, + .o_ncmds = 1, + .o_name = "VFS_DQUOT", + .o_hdrsize = 0, +}; + +/* User options */ +#define FL_NODBUS 1 +#define FL_NOCONSOLE 2 +#define FL_NODAEMON 4 + +int flags; + +void show_help(void) +{ + errstr(_("Usage: %s [options]\nOptions are:\n\ + -h --help shows this text\n\ + -V --version shows version information\n\ + -C --no-console do not try to write messages to console\n\ + -D --no-dbus do not try to write messages to DBUS\n\ + -n --no-daemon do not detach from tty\n"), progname); +} + +static void parse_options(int argc, char **argv) +{ + int opt; + + while ((opt = getopt_long(argc, argv, "VhDCn", options, NULL)) >= 0) { + switch (opt) { + case 'V': + version(); + exit(0); + case 'h': + show_help(); + exit(0); + case 'D': + flags |= FL_NODBUS; + break; + case 'C': + flags |= FL_NOCONSOLE; + break; + case 'n': + flags |= FL_NODAEMON; + break; + default: + errstr(_("Unknown option '%c'.\n"), opt); + show_help(); + exit(1); + } + } + if (flags & FL_NODBUS && flags & FL_NOCONSOLE) { + errstr(_("No possible destination for messages. Nothing to do.\n")); + exit(0); + } +} + +/* Parse netlink message and process it. */ +static int quota_nl_warn_cmd_parser(struct genl_ops *ops, struct genl_cmd *cmd, + struct genl_info *info, void *arg) +{ + struct quota_warning *warn = (struct quota_warning *)arg; + + warn->qtype = nla_get_u32(info->attrs[QUOTA_NL_A_QTYPE]); + warn->excess_id = nla_get_u64(info->attrs[QUOTA_NL_A_EXCESS_ID]); + warn->warntype = nla_get_u32(info->attrs[QUOTA_NL_A_WARNING]); + warn->dev_major = nla_get_u32(info->attrs[QUOTA_NL_A_DEV_MAJOR]); + warn->dev_minor = nla_get_u32(info->attrs[QUOTA_NL_A_DEV_MINOR]); + warn->caused_id = nla_get_u64(info->attrs[QUOTA_NL_A_CAUSED_ID]); + + return 0; +} + +static struct nl_handle *init_netlink(void) +{ + struct nl_handle *handle; + int ret; + + handle = nl_handle_alloc(); + if (!handle) + die(2, _("Cannot allocate netlink handle!\n")); + nl_disable_sequence_check(handle); + ret = nl_connect(handle, NETLINK_GENERIC); + if (ret < 0) + die(2, _("Cannot connect to netlink socket: %s\n"), strerror(-ret)); + ret = genl_ops_resolve(handle, "a_nl_ops); + if (ret < 0) + die(2, _("Cannot resolve quota netlink name: %s\n"), strerror(-ret)); + + ret = nl_socket_add_membership(handle, quota_nl_ops.o_id); + if (ret < 0) + die(2, _("Cannot join quota multicast group: %s\n"), strerror(-ret)); + + ret = genl_register("a_nl_ops); + if (ret < 0) + die(2, _("Cannot register netlink family: %s\n"), strerror(-ret)); + + return handle; +} + +static DBusConnection *init_dbus(void) +{ + DBusConnection *handle; + DBusError err; + + dbus_error_init(&err); + handle = dbus_bus_get(DBUS_BUS_SYSTEM, &err); + if (dbus_error_is_set(&err)) + die(2, _("Cannot connect to system DBUS: %s\n"), err.message); + + dbus_connection_set_exit_on_disconnect(handle, FALSE); + return handle; +} + +static int write_all(int fd, char *buf, int len) +{ + int ret; + + while (len) { + ret = write(fd, buf, len); + if (ret < 0) + return -1; + buf += ret; + len -= ret; + } + return 0; +} + +#define WARN_BUF_SIZE 512 + +/* Scan through utmp, find latest used controlling tty and write to it */ +static void write_console_warning(struct quota_warning *warn) +{ + struct utmp *uent; + char user[MAXNAMELEN]; + struct stat st; + char dev[PATH_MAX]; + time_t max_atime = 0; + char max_dev[PATH_MAX]; + int fd; + char warnbuf[WARN_BUF_SIZE]; + char *level, *msg; + + uid2user(warn->caused_id, user); + strcpy(dev, "/dev/"); + + setutent(); + endutent(); + while ((uent = getutent())) { + if (uent->ut_type != USER_PROCESS) + continue; + /* Entry for a different user? */ + if (strcmp(user, uent->ut_user)) + continue; + sstrncpy(dev+5, uent->ut_line, PATH_MAX-5); + if (stat(dev, &st) < 0) + continue; /* Failed to stat - not a good candidate for warning... */ + if (max_atime < st.st_atime) { + max_atime = st.st_atime; + strcpy(max_dev, dev); + } + } + if (!max_atime) { + errstr(_("Failed to find tty of user %Lu to report warning to.\n"), (unsigned long long)warn->caused_id); + return; + } + fd = open(max_dev, O_WRONLY); + if (fd < 0) { + errstr(_("Failed to open tty %s of user %Lu to report warning.\n"), dev, (unsigned long long)warn->caused_id); + return; + } + id2name(warn->excess_id, warn->qtype, user); + if (warn->warntype == QUOTA_NL_ISOFTWARN || warn->warntype == QUOTA_NL_BSOFTWARN) + level = "Warning"; + else + level = "Error"; + switch (warn->warntype) { + case QUOTA_NL_IHARDWARN: + msg = "file limit reached"; + break; + case QUOTA_NL_ISOFTLONGWARN: + msg = "file quota exceeded too long"; + break; + case QUOTA_NL_ISOFTWARN: + msg = "file quota exceeded"; + break; + case QUOTA_NL_BHARDWARN: + msg = "block limit reached"; + break; + case QUOTA_NL_BSOFTLONGWARN: + msg = "block quota exceeded too long"; + break; + case QUOTA_NL_BSOFTWARN: + msg = "block quota exceeded"; + break; + default: + msg = "unknown quota warning"; + } + sprintf(warnbuf, "%s: %s %s %s.\r\n", level, type2name(warn->qtype), user, msg); + if (write_all(fd, warnbuf, strlen(warnbuf)) < 0) + errstr(_("Failed to write quota message for user %Lu to %s: %s\n"), (unsigned long long)warn->caused_id, dev, strerror(errno)); + close(fd); +} + +/* Send warning through DBUS */ +static void write_dbus_warning(struct DBusConnection *dhandle, struct quota_warning *warn) +{ + DBusMessage* msg; + DBusMessageIter args; + + msg = dbus_message_new_signal("/", "com.system.quota.warning", "warning"); + if (!msg) { +no_mem: + errstr(_("Cannot create DBUS message: No enough memory.\n")); + goto out; + } + dbus_message_iter_init_append(msg, &args); + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->qtype)) + goto no_mem; + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT64, &warn->excess_id)) + goto no_mem; + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->warntype)) + goto no_mem; + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->dev_major)) + goto no_mem; + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->dev_minor)) + goto no_mem; + if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT64, &warn->caused_id)) + goto no_mem; + + if (!dbus_connection_send(dhandle, msg, NULL)) { + errstr(_("Failed to write message to dbus: No enough memory.\n")); + goto out; + } + dbus_connection_flush(dhandle); +out: + if (msg) + dbus_message_unref(msg); +} + +static void run(struct nl_handle *nhandle, struct DBusConnection *dhandle) +{ + struct sockaddr_nl nla; + struct ucred *creds; + struct quota_warning warn; + unsigned char *buf; + struct nlmsghdr *hdr; + int ret; + + while (1) { + ret = nl_recv(nhandle, &nla, &buf, &creds); + if (ret < 0) + die(2, _("Read from netlink socket failed: %s\n"), strerror(-ret)); + hdr = (struct nlmsghdr *)buf; + /* Not message from quota? */ + if (hdr->nlmsg_type != quota_nl_ops.o_id) { + free(buf); + continue; + } + ret = genl_msg_parser("a_nl_ops, &nla, (struct nlmsghdr *)buf, &warn); + if (!ret) { /* Parsing successful? */ + if (!(flags & FL_NOCONSOLE)) + write_console_warning(&warn); + if (!(flags & FL_NODBUS)) + write_dbus_warning(dhandle, &warn); + } + else + errstr(_("Failed parsing netlink command: %s\n"), strerror(errno)); + free(buf); + } +} + +int main(int argc, char **argv) +{ + struct nl_handle *nhandle; + DBusConnection *dhandle = NULL; + + gettexton(); + progname = basename(argv[0]); + parse_options(argc, argv); + + nhandle = init_netlink(); + if (!(flags & FL_NODBUS)) + dhandle = init_dbus(); + if (!(flags & FL_NODAEMON)) { + use_syslog(); + daemon(0, 0); + } + run(nhandle, dhandle); + return 0; +} |