/* * 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 "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "pot.h" #include "common.h" #include "quotasys.h" #include "quota.h" char *progname; 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 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 }, }; /* User options */ #define FL_NODBUS 1 #define FL_NOCONSOLE 2 #define FL_NODAEMON 4 #define FL_PRINTBELOW 8 static int flags; static DBusConnection *dhandle; static const struct option options[] = { { "version", 0, NULL, 'V' }, { "help", 0, NULL, 'h' }, { "no-dbus", 0, NULL, 'D' }, { "no-console", 0, NULL, 'C' }, { "foreground", 0, NULL, 'F' }, { "print-below", 0, NULL, 'b' }, { NULL, 0, NULL, 0 } }; static 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\ -b --print-below write to console also information about getting below hard/soft limits\n\ -D --no-dbus do not try to write messages to DBUS\n\ -F --foreground run daemon in foreground\n"), progname); } static void parse_options(int argc, char **argv) { int opt; while ((opt = getopt_long(argc, argv, "VhDCFb", 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 'F': flags |= FL_NODAEMON; break; case 'b': flags |= FL_PRINTBELOW; 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); } } static void write_console_warning(struct quota_warning *warn); static void write_dbus_warning(struct DBusConnection *dhandle, struct quota_warning *warn); /* Parse netlink message and process it. */ static int quota_nl_parser(struct nl_msg *msg, void *arg) { struct nlmsghdr *nlh = nlmsg_hdr(msg); struct genlmsghdr *ghdr; struct nlattr *attrs[QUOTA_NL_A_MAX+1]; struct quota_warning warn; int ret; if (!genlmsg_valid_hdr(nlh, 0)) return 0; ghdr = nlmsg_data(nlh); /* Unknown message? Ignore... */ if (ghdr->cmd != QUOTA_NL_C_WARNING) return 0; ret = genlmsg_parse(nlh, 0, attrs, QUOTA_NL_A_MAX, quota_nl_warn_cmd_policy); if (ret < 0) { errstr(_("Error parsing netlink message.\n")); return ret; } if (!attrs[QUOTA_NL_A_QTYPE] || !attrs[QUOTA_NL_A_EXCESS_ID] || !attrs[QUOTA_NL_A_WARNING] || !attrs[QUOTA_NL_A_DEV_MAJOR] || !attrs[QUOTA_NL_A_DEV_MAJOR] || !attrs[QUOTA_NL_A_DEV_MINOR] || !attrs[QUOTA_NL_A_CAUSED_ID]) { errstr(_("Unknown format of kernel netlink message!\nMaybe your quota tools are too old?\n")); return -EINVAL; } warn.qtype = nla_get_u32(attrs[QUOTA_NL_A_QTYPE]); warn.excess_id = nla_get_u64(attrs[QUOTA_NL_A_EXCESS_ID]); warn.warntype = nla_get_u32(attrs[QUOTA_NL_A_WARNING]); warn.dev_major = nla_get_u32(attrs[QUOTA_NL_A_DEV_MAJOR]); warn.dev_minor = nla_get_u32(attrs[QUOTA_NL_A_DEV_MINOR]); warn.caused_id = nla_get_u64(attrs[QUOTA_NL_A_CAUSED_ID]); if (!(flags & FL_NOCONSOLE) && warn.qtype != PRJQUOTA) write_console_warning(&warn); if (!(flags & FL_NODBUS)) write_dbus_warning(dhandle, &warn); return 0; } static struct nl_sock *init_netlink(void) { struct nl_sock *sock; int ret, mc_family; sock = nl_socket_alloc(); if (!sock) die(2, _("Cannot allocate netlink socket!\n")); nl_socket_disable_seq_check(sock); ret = nl_socket_modify_cb(sock, NL_CB_VALID, NL_CB_CUSTOM, quota_nl_parser, NULL); if (ret < 0) die(2, _("Cannot register callback for" " netlink messages: %s\n"), strerror(-ret)); ret = genl_connect(sock); if (ret < 0) die(2, _("Cannot connect to netlink socket: %s\n"), strerror(-ret)); mc_family = genl_ctrl_resolve_grp(sock, "VFS_DQUOT", "events"); if (mc_family < 0) { /* * Using family id for multicasting is wrong but I messed up * kernel netlink interface by using family id as a multicast * group id in kernel so we have to carry this code to keep * compatibility with older kernels. */ mc_family = genl_ctrl_resolve(sock, "VFS_DQUOT"); if (mc_family < 0) die(2, _("Cannot resolve quota netlink name: %s\n"), strerror(-mc_family)); } ret = nl_socket_add_membership(sock, mc_family); if (ret < 0) die(2, _("Cannot join quota multicast group: %s\n"), strerror(-ret)); return sock; } 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; if ((warn->warntype == QUOTA_NL_IHARDBELOW || warn->warntype == QUOTA_NL_ISOFTBELOW || warn->warntype == QUOTA_NL_BHARDBELOW || warn->warntype == QUOTA_NL_BSOFTBELOW) && !(flags & FL_PRINTBELOW)) return; uid2user(warn->caused_id, user); if (strlen(user) > UT_NAMESIZE) goto skip_utmp; strcpy(dev, "/dev/"); setutent(); endutent(); while ((uent = getutent())) { if (uent->ut_type != USER_PROCESS) continue; /* Entry for a different user? */ if (strncmp(user, uent->ut_user, UT_NAMESIZE)) 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) { skip_utmp: /* * This can happen quite easily so don't spam syslog with * the error */ if (flags & FL_NODAEMON) errstr(_("Failed to find tty of user %llu 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 %llu 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 if (warn->warntype == QUOTA_NL_IHARDWARN || warn->warntype == QUOTA_NL_BHARDWARN) level = _("Error"); else level = _("Info"); 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; case QUOTA_NL_IHARDBELOW: msg = _("got below file limit"); break; case QUOTA_NL_ISOFTBELOW: msg = _("got below file quota"); break; case QUOTA_NL_BHARDBELOW: msg = _("got below block limit"); break; case QUOTA_NL_BSOFTBELOW: msg = _("got below block quota"); 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 %llu 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_sock *nsock) { int ret; while (1) { ret = nl_recvmsgs_default(nsock); if (ret < 0) errstr(_("Failed to read or parse quota netlink" " message: %s\n"), strerror(-ret)); } } /* Build file name (absolute path) to PID file of this daemon. * The returned name is allocated on heap. */ static char *build_pid_file_name(void) { char *pid_name = NULL; if (!progname) { errstr(_("Undefined program name.\n")); return NULL; } pid_name = malloc(strlen(PID_DIR) + 1 + strlen(progname) + 4 + 1); if (!pid_name) { errstr(_("Not enough memory to build PID file name.\n")); return NULL; } sprintf(pid_name, "%s/%s.pid", PID_DIR, progname); return pid_name; } /* Store daemon's PID to file */ static int store_pid(pid_t pid) { FILE *pid_file; char *pid_name; pid_name = build_pid_file_name(); if (!pid_name) return -1; pid_file = fopen(pid_name, "w"); if (!pid_file) { errstr(_("Could not open PID file '%s': %s\n"), pid_name, strerror(errno)); free(pid_name); return -1; } if (fprintf(pid_file, "%d\n", (int)pid) < 0) { errstr(_("Could not write daemon's PID into '%s'.\n"), pid_name); fclose(pid_file); free(pid_name); return -1; } if (fclose(pid_file)) { errstr(_("Could not close PID file '%s'.\n"), pid_name); free(pid_name); return -1; } free(pid_name); return 0; } /* Handler for SIGTERM to remove PID file */ static void remove_pid(int signal) { char *pid_name; pid_name = build_pid_file_name(); if (pid_name) { unlink(pid_name); free(pid_name); } exit(EXIT_SUCCESS); } /* Register daemon's PID file removal on SIGTERM */ static void setup_sigterm_handler(void) { struct sigaction term_action; term_action.sa_handler = remove_pid; term_action.sa_flags = 0; if (sigemptyset(&term_action.sa_mask) || sigaction(SIGTERM, &term_action, NULL)) errstr(_("Could not register PID file removal on SIGTERM.\n")); } static void fork_daemon(void) { pid_t pid = fork(); if (pid < 0) { errstr(_("Failed to daemonize: fork: %s\n"), strerror(errno)); exit(1); } else if (pid != 0) { if (store_pid(pid)) { errstr(_("Could not store my PID %d.\n"), (int)pid); kill(pid, SIGKILL); } exit(0); } setup_sigterm_handler(); if (setsid() < 0) { errstr(_("Failed to daemonize: setsid: %s\n"), strerror(errno)); exit(1); } if (chdir("/") < 0) errstr(_("Failed to chdir in daemonize\n")); int fd = open("/dev/null", O_RDWR, 0); if (fd >= 0) { dup2(fd, STDIN_FILENO); dup2(fd, STDOUT_FILENO); dup2(fd, STDERR_FILENO); close(fd); } } int main(int argc, char **argv) { struct nl_sock *nsock; gettexton(); progname = basename(argv[0]); parse_options(argc, argv); nsock = init_netlink(); if (!(flags & FL_NODBUS)) dhandle = init_dbus(); if (!(flags & FL_NODAEMON)) { use_syslog(); fork_daemon(); } run(nsock); return 0; }