blob: ea541e01e4a9f215ef8249dff4a5afc79ee7dd5f [file] [log] [blame]
/*
* 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 <stdio.h>
#include <unistd.h>
#include <getopt.h>
#include <utmp.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <limits.h>
#include <signal.h>
#include <sys/stat.h>
#include <asm/types.h>
#include <linux/netlink.h>
#include <netlink/socket.h>
#include <netlink/genl/genl.h>
#include <netlink/genl/ctrl.h>
#include <dbus/dbus.h>
#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);
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) {
/*
* 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(9 + strlen(progname) + 4 + 1);
if (!pid_name) {
errstr(_("Not enough memory to build PID file name.\n"));
return NULL;
}
sprintf(pid_name, "/var/run/%s.pid", progname);
return pid_name;
}
/* Store daemon's PID to file */
static int store_pid(void)
{
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, "%jd\n", (intmax_t)getpid()) < 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);
}
/* Store daemon's PID into file and register its removal on SIGTERM */
static void use_pid_file(void)
{
struct sigaction term_action;
term_action.sa_handler = remove_pid;
term_action.sa_flags = 0;
if (sigaction(SIGTERM, &term_action, NULL))
errstr(_("Could not register PID file removal on SIGTERM.\n"));
if (store_pid())
errstr(_("Could not store my PID %jd.\n"), (intmax_t )getpid());
}
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();
daemon(0, 0);
use_pid_file();
}
run(nsock);
return 0;
}