Initial commit to Gerrit
[profile/ivi/quota.git] / quota_nld.c
1 /*
2  *  A deamon to read quota warning messages from the kernel netlink socket
3  *  and either pipe them to the system DBUS or write them to user's console
4  *
5  *  Copyright (c) 2007 SUSE CR, All Rights Reserved.
6  *
7  * This program is free software; you can redistribute it and/or modify it
8  * under the terms of version 2 of the GNU General Public License as
9  * published by the Free Software Foundation.
10  *
11  * This program is distributed in the hope that it would be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
14  * more details.
15  *
16  */
17
18 #include "config.h"
19
20 #include <stdio.h>
21 #include <unistd.h>
22 #include <getopt.h>
23 #include <utmp.h>
24 #include <errno.h>
25 #include <string.h>
26 #include <fcntl.h>
27 #include <limits.h>
28
29 #include <netlink/genl/genl.h>
30 #include <netlink/genl/ctrl.h>
31
32 #include <dbus/dbus.h>
33
34 #include "pot.h"
35 #include "common.h"
36 #include "quotasys.h"
37 #include "quota.h"
38
39 char *progname;
40
41 struct quota_warning {
42         uint32_t qtype;
43         uint64_t excess_id;
44         uint32_t warntype;
45         uint32_t dev_major;
46         uint32_t dev_minor;
47         uint64_t caused_id;
48 };
49
50 static struct nla_policy quota_nl_warn_cmd_policy[QUOTA_NL_A_MAX+1] = {
51         [QUOTA_NL_A_QTYPE] = { .type = NLA_U32 },
52         [QUOTA_NL_A_EXCESS_ID] = { .type = NLA_U64 },
53         [QUOTA_NL_A_WARNING] = { .type = NLA_U32 },
54         [QUOTA_NL_A_DEV_MAJOR] = { .type = NLA_U32 },
55         [QUOTA_NL_A_DEV_MINOR] = { .type = NLA_U32 },
56         [QUOTA_NL_A_CAUSED_ID] = { .type = NLA_U64 },
57 };
58
59 /* User options */
60 #define FL_NODBUS 1
61 #define FL_NOCONSOLE 2
62 #define FL_NODAEMON 4
63 #define FL_PRINTBELOW 8
64
65 int flags;
66 DBusConnection *dhandle;
67
68 static const struct option options[] = {
69         { "version", 0, NULL, 'V' },
70         { "help", 0, NULL, 'h' },
71         { "no-dbus", 0, NULL, 'D' },
72         { "no-console", 0, NULL, 'C' },
73         { "foreground", 0, NULL, 'F' },
74         { "print-below", 0, NULL, 'b' },
75         { NULL, 0, NULL, 0 }
76 };
77
78 void show_help(void)
79 {
80         errstr(_("Usage: %s [options]\nOptions are:\n\
81  -h --help         shows this text\n\
82  -V --version      shows version information\n\
83  -C --no-console   do not try to write messages to console\n\
84  -b --print-below  write to console also information about getting below hard/soft limits\n\
85  -D --no-dbus      do not try to write messages to DBUS\n\
86  -F --foreground   run daemon in foreground\n"), progname);
87 }
88
89 static void parse_options(int argc, char **argv)
90 {
91         int opt;
92
93         while ((opt = getopt_long(argc, argv, "VhDCFb", options, NULL)) >= 0) {
94                 switch (opt) {
95                         case 'V':
96                                 version();
97                                 exit(0);
98                         case 'h':
99                                 show_help();
100                                 exit(0);
101                         case 'D':
102                                 flags |= FL_NODBUS;
103                                 break;
104                         case 'C':
105                                 flags |= FL_NOCONSOLE;
106                                 break;
107                         case 'F':
108                                 flags |= FL_NODAEMON;
109                                 break;
110                         case 'b':
111                                 flags |= FL_PRINTBELOW;
112                                 break;
113                         default:
114                                 errstr(_("Unknown option '%c'.\n"), opt);
115                                 show_help();
116                                 exit(1);
117                 }
118         }
119         if (flags & FL_NODBUS && flags & FL_NOCONSOLE) {
120                 errstr(_("No possible destination for messages. Nothing to do.\n"));
121                 exit(0);
122         }
123 }
124
125 static void write_console_warning(struct quota_warning *warn);
126 static void write_dbus_warning(struct DBusConnection *dhandle, struct quota_warning *warn);
127
128 /* Parse netlink message and process it. */
129 static int quota_nl_parser(struct nl_msg *msg, void *arg)
130 {
131         struct nlmsghdr *nlh = nlmsg_hdr(msg);
132         struct genlmsghdr *ghdr;
133         struct nlattr *attrs[QUOTA_NL_A_MAX+1];
134         struct quota_warning warn;
135         int ret;
136
137         if (!genlmsg_valid_hdr(nlh, 0))
138                 return 0;
139         ghdr = nlmsg_data(nlh);
140         /* Unknown message? Ignore... */
141         if (ghdr->cmd != QUOTA_NL_C_WARNING)
142                 return 0;
143
144         ret = genlmsg_parse(nlh, 0, attrs, QUOTA_NL_A_MAX, quota_nl_warn_cmd_policy);
145         if (ret < 0) {
146                 errstr(_("Error parsing netlink message.\n"));
147                 return ret;
148         }
149         if (!attrs[QUOTA_NL_A_QTYPE] || !attrs[QUOTA_NL_A_EXCESS_ID] ||
150             !attrs[QUOTA_NL_A_WARNING] || !attrs[QUOTA_NL_A_DEV_MAJOR] ||
151             !attrs[QUOTA_NL_A_DEV_MAJOR] || !attrs[QUOTA_NL_A_DEV_MINOR] ||
152             !attrs[QUOTA_NL_A_CAUSED_ID]) {
153                 errstr(_("Unknown format of kernel netlink message!\nMaybe your quota tools are too old?\n"));
154                 return -EINVAL;
155         }
156         warn.qtype = nla_get_u32(attrs[QUOTA_NL_A_QTYPE]);
157         warn.excess_id = nla_get_u64(attrs[QUOTA_NL_A_EXCESS_ID]);
158         warn.warntype = nla_get_u32(attrs[QUOTA_NL_A_WARNING]);
159         warn.dev_major = nla_get_u32(attrs[QUOTA_NL_A_DEV_MAJOR]);
160         warn.dev_minor = nla_get_u32(attrs[QUOTA_NL_A_DEV_MINOR]);
161         warn.caused_id = nla_get_u64(attrs[QUOTA_NL_A_CAUSED_ID]);
162
163         if (!(flags & FL_NOCONSOLE))
164                 write_console_warning(&warn);
165         if (!(flags & FL_NODBUS))
166                 write_dbus_warning(dhandle, &warn);
167         return 0;
168 }
169
170 static struct nl_handle *init_netlink(void)
171 {
172         struct nl_handle *handle;
173         int ret, family;
174
175         handle = nl_handle_alloc();
176         if (!handle)
177                 die(2, _("Cannot allocate netlink handle!\n"));
178         nl_disable_sequence_check(handle);
179         ret = genl_connect(handle);
180         if (ret < 0)
181                 die(2, _("Cannot connect to netlink socket: %s\n"), strerror(-ret));
182         family = genl_ctrl_resolve(handle, "VFS_DQUOT");
183         if (ret < 0)
184                 die(2, _("Cannot resolve quota netlink name: %s\n"), strerror(-ret));
185
186         ret = nl_socket_add_membership(handle, family);
187         if (ret < 0)
188                 die(2, _("Cannot join quota multicast group: %s\n"), strerror(-ret));
189
190         ret = nl_socket_modify_cb(handle, NL_CB_VALID, NL_CB_CUSTOM,
191                         quota_nl_parser, NULL);
192         if (ret < 0)
193                 die(2, _("Cannot register callback for"
194                          " netlink messages: %s\n"), strerror(-ret));
195
196         return handle;
197 }
198
199 static DBusConnection *init_dbus(void)
200 {
201         DBusConnection *handle;
202         DBusError err;
203
204         dbus_error_init(&err);
205         handle = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
206         if (dbus_error_is_set(&err))
207                 die(2, _("Cannot connect to system DBUS: %s\n"), err.message);
208
209         dbus_connection_set_exit_on_disconnect(handle, FALSE);
210         return handle;
211 }
212
213 static int write_all(int fd, char *buf, int len)
214 {
215         int ret;
216
217         while (len) {
218                 ret = write(fd, buf, len);
219                 if (ret < 0)
220                         return -1;
221                 buf += ret;
222                 len -= ret;
223         }
224         return 0;
225 }
226
227 #define WARN_BUF_SIZE 512
228
229 /* Scan through utmp, find latest used controlling tty and write to it */
230 static void write_console_warning(struct quota_warning *warn)
231 {
232         struct utmp *uent;
233         char user[MAXNAMELEN];
234         struct stat st;
235         char dev[PATH_MAX];
236         time_t max_atime = 0;
237         char max_dev[PATH_MAX];
238         int fd;
239         char warnbuf[WARN_BUF_SIZE];
240         char *level, *msg;
241
242         if ((warn->warntype == QUOTA_NL_IHARDBELOW ||
243             warn->warntype == QUOTA_NL_ISOFTBELOW ||
244             warn->warntype == QUOTA_NL_BHARDBELOW ||
245             warn->warntype == QUOTA_NL_BSOFTBELOW) && !(flags & FL_PRINTBELOW))
246                 return;
247         uid2user(warn->caused_id, user);
248         strcpy(dev, "/dev/");
249
250         setutent();
251         endutent();
252         while ((uent = getutent())) {
253                 if (uent->ut_type != USER_PROCESS)
254                         continue;
255                 /* Entry for a different user? */
256                 if (strcmp(user, uent->ut_user))
257                         continue;
258                 sstrncpy(dev+5, uent->ut_line, PATH_MAX-5);
259                 if (stat(dev, &st) < 0)
260                         continue;       /* Failed to stat - not a good candidate for warning... */
261                 if (max_atime < st.st_atime) {
262                         max_atime = st.st_atime;
263                         strcpy(max_dev, dev);
264                 }
265         }
266         if (!max_atime) {
267                 errstr(_("Failed to find tty of user %llu to report warning to.\n"), (unsigned long long)warn->caused_id);
268                 return;
269         }
270         fd = open(max_dev, O_WRONLY);
271         if (fd < 0) {
272                 errstr(_("Failed to open tty %s of user %llu to report warning.\n"), dev, (unsigned long long)warn->caused_id);
273                 return;
274         }
275         id2name(warn->excess_id, warn->qtype, user);
276         if (warn->warntype == QUOTA_NL_ISOFTWARN ||
277             warn->warntype == QUOTA_NL_BSOFTWARN)
278                 level = _("Warning");
279         else if (warn->warntype == QUOTA_NL_IHARDWARN ||
280                  warn->warntype == QUOTA_NL_BHARDWARN)
281                 level = _("Error");
282         else
283                 level = _("Info");
284         switch (warn->warntype) {
285                 case QUOTA_NL_IHARDWARN:
286                         msg = _("file limit reached");
287                         break;
288                 case QUOTA_NL_ISOFTLONGWARN:
289                         msg = _("file quota exceeded too long");
290                         break;
291                 case QUOTA_NL_ISOFTWARN:
292                         msg = _("file quota exceeded");
293                         break;
294                 case QUOTA_NL_BHARDWARN:
295                         msg = _("block limit reached");
296                         break;
297                 case QUOTA_NL_BSOFTLONGWARN:
298                         msg = _("block quota exceeded too long");
299                         break;
300                 case QUOTA_NL_BSOFTWARN:
301                         msg = _("block quota exceeded");
302                         break;
303                 case QUOTA_NL_IHARDBELOW:
304                         msg = _("got below file limit");
305                         break;
306                 case QUOTA_NL_ISOFTBELOW:
307                         msg = _("got below file quota");
308                         break;
309                 case QUOTA_NL_BHARDBELOW:
310                         msg = _("got below block limit");
311                         break;
312                 case QUOTA_NL_BSOFTBELOW:
313                         msg = _("got below block quota");
314                         break;
315                 default:
316                         msg = _("unknown quota warning");
317         }
318         sprintf(warnbuf, "%s: %s %s %s.\r\n", level, type2name(warn->qtype), user, msg);
319         if (write_all(fd, warnbuf, strlen(warnbuf)) < 0)
320                 errstr(_("Failed to write quota message for user %llu to %s: %s\n"), (unsigned long long)warn->caused_id, dev, strerror(errno));
321         close(fd);
322 }
323
324 /* Send warning through DBUS */
325 static void write_dbus_warning(struct DBusConnection *dhandle, struct quota_warning *warn)
326 {
327         DBusMessage* msg;
328         DBusMessageIter args;
329
330         msg = dbus_message_new_signal("/", "com.system.quota.warning", "warning");
331         if (!msg) {
332 no_mem:
333                 errstr(_("Cannot create DBUS message: No enough memory.\n"));
334                 goto out;
335         }
336         dbus_message_iter_init_append(msg, &args);
337         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->qtype))
338                 goto no_mem;
339         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT64, &warn->excess_id))
340                 goto no_mem;
341         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->warntype))
342                 goto no_mem;
343         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->dev_major))
344                 goto no_mem;
345         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->dev_minor))
346                 goto no_mem;
347         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT64, &warn->caused_id))
348                 goto no_mem;
349
350         if (!dbus_connection_send(dhandle, msg, NULL)) {
351                 errstr(_("Failed to write message to dbus: No enough memory.\n"));
352                 goto out;
353         }
354         dbus_connection_flush(dhandle);
355 out:
356         if (msg)
357                 dbus_message_unref(msg);
358 }
359
360 static void run(struct nl_handle *nhandle)
361 {
362         int ret;
363
364         while (1) {
365                 ret = nl_recvmsgs_default(nhandle);
366                 if (ret < 0)
367                         errstr(_("Failed to read or parse quota netlink"
368                                 " message: %s\n"), strerror(-ret));
369         }
370 }
371
372 int main(int argc, char **argv)
373 {
374         struct nl_handle *nhandle;
375
376         gettexton();
377         progname = basename(argv[0]);
378         parse_options(argc, argv);
379
380         nhandle = init_netlink();
381         if (!(flags & FL_NODBUS))
382                 dhandle = init_dbus();
383         if (!(flags & FL_NODAEMON)) {
384                 use_syslog();
385                 daemon(0, 0);
386         }
387         run(nhandle);
388         return 0;
389 }