Update to 4.01.
[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 #include <signal.h>
29
30 #include <netlink/genl/genl.h>
31 #include <netlink/genl/ctrl.h>
32
33 #include <dbus/dbus.h>
34
35 #include "pot.h"
36 #include "common.h"
37 #include "quotasys.h"
38 #include "quota.h"
39
40 char *progname;
41
42 struct quota_warning {
43         uint32_t qtype;
44         uint64_t excess_id;
45         uint32_t warntype;
46         uint32_t dev_major;
47         uint32_t dev_minor;
48         uint64_t caused_id;
49 };
50
51 static struct nla_policy quota_nl_warn_cmd_policy[QUOTA_NL_A_MAX+1] = {
52         [QUOTA_NL_A_QTYPE] = { .type = NLA_U32 },
53         [QUOTA_NL_A_EXCESS_ID] = { .type = NLA_U64 },
54         [QUOTA_NL_A_WARNING] = { .type = NLA_U32 },
55         [QUOTA_NL_A_DEV_MAJOR] = { .type = NLA_U32 },
56         [QUOTA_NL_A_DEV_MINOR] = { .type = NLA_U32 },
57         [QUOTA_NL_A_CAUSED_ID] = { .type = NLA_U64 },
58 };
59
60 /* User options */
61 #define FL_NODBUS 1
62 #define FL_NOCONSOLE 2
63 #define FL_NODAEMON 4
64 #define FL_PRINTBELOW 8
65
66 static int flags;
67 static DBusConnection *dhandle;
68
69 static const struct option options[] = {
70         { "version", 0, NULL, 'V' },
71         { "help", 0, NULL, 'h' },
72         { "no-dbus", 0, NULL, 'D' },
73         { "no-console", 0, NULL, 'C' },
74         { "foreground", 0, NULL, 'F' },
75         { "print-below", 0, NULL, 'b' },
76         { NULL, 0, NULL, 0 }
77 };
78
79 static void show_help(void)
80 {
81         errstr(_("Usage: %s [options]\nOptions are:\n\
82  -h --help         shows this text\n\
83  -V --version      shows version information\n\
84  -C --no-console   do not try to write messages to console\n\
85  -b --print-below  write to console also information about getting below hard/soft limits\n\
86  -D --no-dbus      do not try to write messages to DBUS\n\
87  -F --foreground   run daemon in foreground\n"), progname);
88 }
89
90 static void parse_options(int argc, char **argv)
91 {
92         int opt;
93
94         while ((opt = getopt_long(argc, argv, "VhDCFb", options, NULL)) >= 0) {
95                 switch (opt) {
96                         case 'V':
97                                 version();
98                                 exit(0);
99                         case 'h':
100                                 show_help();
101                                 exit(0);
102                         case 'D':
103                                 flags |= FL_NODBUS;
104                                 break;
105                         case 'C':
106                                 flags |= FL_NOCONSOLE;
107                                 break;
108                         case 'F':
109                                 flags |= FL_NODAEMON;
110                                 break;
111                         case 'b':
112                                 flags |= FL_PRINTBELOW;
113                                 break;
114                         default:
115                                 errstr(_("Unknown option '%c'.\n"), opt);
116                                 show_help();
117                                 exit(1);
118                 }
119         }
120         if (flags & FL_NODBUS && flags & FL_NOCONSOLE) {
121                 errstr(_("No possible destination for messages. Nothing to do.\n"));
122                 exit(0);
123         }
124 }
125
126 static void write_console_warning(struct quota_warning *warn);
127 static void write_dbus_warning(struct DBusConnection *dhandle, struct quota_warning *warn);
128
129 /* Parse netlink message and process it. */
130 static int quota_nl_parser(struct nl_msg *msg, void *arg)
131 {
132         struct nlmsghdr *nlh = nlmsg_hdr(msg);
133         struct genlmsghdr *ghdr;
134         struct nlattr *attrs[QUOTA_NL_A_MAX+1];
135         struct quota_warning warn;
136         int ret;
137
138         if (!genlmsg_valid_hdr(nlh, 0))
139                 return 0;
140         ghdr = nlmsg_data(nlh);
141         /* Unknown message? Ignore... */
142         if (ghdr->cmd != QUOTA_NL_C_WARNING)
143                 return 0;
144
145         ret = genlmsg_parse(nlh, 0, attrs, QUOTA_NL_A_MAX, quota_nl_warn_cmd_policy);
146         if (ret < 0) {
147                 errstr(_("Error parsing netlink message.\n"));
148                 return ret;
149         }
150         if (!attrs[QUOTA_NL_A_QTYPE] || !attrs[QUOTA_NL_A_EXCESS_ID] ||
151             !attrs[QUOTA_NL_A_WARNING] || !attrs[QUOTA_NL_A_DEV_MAJOR] ||
152             !attrs[QUOTA_NL_A_DEV_MAJOR] || !attrs[QUOTA_NL_A_DEV_MINOR] ||
153             !attrs[QUOTA_NL_A_CAUSED_ID]) {
154                 errstr(_("Unknown format of kernel netlink message!\nMaybe your quota tools are too old?\n"));
155                 return -EINVAL;
156         }
157         warn.qtype = nla_get_u32(attrs[QUOTA_NL_A_QTYPE]);
158         warn.excess_id = nla_get_u64(attrs[QUOTA_NL_A_EXCESS_ID]);
159         warn.warntype = nla_get_u32(attrs[QUOTA_NL_A_WARNING]);
160         warn.dev_major = nla_get_u32(attrs[QUOTA_NL_A_DEV_MAJOR]);
161         warn.dev_minor = nla_get_u32(attrs[QUOTA_NL_A_DEV_MINOR]);
162         warn.caused_id = nla_get_u64(attrs[QUOTA_NL_A_CAUSED_ID]);
163
164         if (!(flags & FL_NOCONSOLE))
165                 write_console_warning(&warn);
166         if (!(flags & FL_NODBUS))
167                 write_dbus_warning(dhandle, &warn);
168         return 0;
169 }
170
171 static struct nl_handle *init_netlink(void)
172 {
173         struct nl_handle *handle;
174         int ret, family;
175
176         handle = nl_handle_alloc();
177         if (!handle)
178                 die(2, _("Cannot allocate netlink handle!\n"));
179         nl_disable_sequence_check(handle);
180         ret = genl_connect(handle);
181         if (ret < 0)
182                 die(2, _("Cannot connect to netlink socket: %s\n"), strerror(-ret));
183         family = genl_ctrl_resolve(handle, "VFS_DQUOT");
184         if (ret < 0)
185                 die(2, _("Cannot resolve quota netlink name: %s\n"), strerror(-ret));
186
187         ret = nl_socket_add_membership(handle, family);
188         if (ret < 0)
189                 die(2, _("Cannot join quota multicast group: %s\n"), strerror(-ret));
190
191         ret = nl_socket_modify_cb(handle, NL_CB_VALID, NL_CB_CUSTOM,
192                         quota_nl_parser, NULL);
193         if (ret < 0)
194                 die(2, _("Cannot register callback for"
195                          " netlink messages: %s\n"), strerror(-ret));
196
197         return handle;
198 }
199
200 static DBusConnection *init_dbus(void)
201 {
202         DBusConnection *handle;
203         DBusError err;
204
205         dbus_error_init(&err);
206         handle = dbus_bus_get(DBUS_BUS_SYSTEM, &err);
207         if (dbus_error_is_set(&err))
208                 die(2, _("Cannot connect to system DBUS: %s\n"), err.message);
209
210         dbus_connection_set_exit_on_disconnect(handle, FALSE);
211         return handle;
212 }
213
214 static int write_all(int fd, char *buf, int len)
215 {
216         int ret;
217
218         while (len) {
219                 ret = write(fd, buf, len);
220                 if (ret < 0)
221                         return -1;
222                 buf += ret;
223                 len -= ret;
224         }
225         return 0;
226 }
227
228 #define WARN_BUF_SIZE 512
229
230 /* Scan through utmp, find latest used controlling tty and write to it */
231 static void write_console_warning(struct quota_warning *warn)
232 {
233         struct utmp *uent;
234         char user[MAXNAMELEN];
235         struct stat st;
236         char dev[PATH_MAX];
237         time_t max_atime = 0;
238         char max_dev[PATH_MAX];
239         int fd;
240         char warnbuf[WARN_BUF_SIZE];
241         char *level, *msg;
242
243         if ((warn->warntype == QUOTA_NL_IHARDBELOW ||
244             warn->warntype == QUOTA_NL_ISOFTBELOW ||
245             warn->warntype == QUOTA_NL_BHARDBELOW ||
246             warn->warntype == QUOTA_NL_BSOFTBELOW) && !(flags & FL_PRINTBELOW))
247                 return;
248         uid2user(warn->caused_id, user);
249         strcpy(dev, "/dev/");
250
251         setutent();
252         endutent();
253         while ((uent = getutent())) {
254                 if (uent->ut_type != USER_PROCESS)
255                         continue;
256                 /* Entry for a different user? */
257                 if (strcmp(user, uent->ut_user))
258                         continue;
259                 sstrncpy(dev+5, uent->ut_line, PATH_MAX-5);
260                 if (stat(dev, &st) < 0)
261                         continue;       /* Failed to stat - not a good candidate for warning... */
262                 if (max_atime < st.st_atime) {
263                         max_atime = st.st_atime;
264                         strcpy(max_dev, dev);
265                 }
266         }
267         if (!max_atime) {
268                 /*
269                  * This can happen quite easily so don't spam syslog with
270                  * the error
271                  */
272                 if (flags & FL_NODAEMON)
273                         errstr(_("Failed to find tty of user %llu to report warning to.\n"), (unsigned long long)warn->caused_id);
274                 return;
275         }
276         fd = open(max_dev, O_WRONLY);
277         if (fd < 0) {
278                 errstr(_("Failed to open tty %s of user %llu to report warning.\n"), dev, (unsigned long long)warn->caused_id);
279                 return;
280         }
281         id2name(warn->excess_id, warn->qtype, user);
282         if (warn->warntype == QUOTA_NL_ISOFTWARN ||
283             warn->warntype == QUOTA_NL_BSOFTWARN)
284                 level = _("Warning");
285         else if (warn->warntype == QUOTA_NL_IHARDWARN ||
286                  warn->warntype == QUOTA_NL_BHARDWARN)
287                 level = _("Error");
288         else
289                 level = _("Info");
290         switch (warn->warntype) {
291                 case QUOTA_NL_IHARDWARN:
292                         msg = _("file limit reached");
293                         break;
294                 case QUOTA_NL_ISOFTLONGWARN:
295                         msg = _("file quota exceeded too long");
296                         break;
297                 case QUOTA_NL_ISOFTWARN:
298                         msg = _("file quota exceeded");
299                         break;
300                 case QUOTA_NL_BHARDWARN:
301                         msg = _("block limit reached");
302                         break;
303                 case QUOTA_NL_BSOFTLONGWARN:
304                         msg = _("block quota exceeded too long");
305                         break;
306                 case QUOTA_NL_BSOFTWARN:
307                         msg = _("block quota exceeded");
308                         break;
309                 case QUOTA_NL_IHARDBELOW:
310                         msg = _("got below file limit");
311                         break;
312                 case QUOTA_NL_ISOFTBELOW:
313                         msg = _("got below file quota");
314                         break;
315                 case QUOTA_NL_BHARDBELOW:
316                         msg = _("got below block limit");
317                         break;
318                 case QUOTA_NL_BSOFTBELOW:
319                         msg = _("got below block quota");
320                         break;
321                 default:
322                         msg = _("unknown quota warning");
323         }
324         sprintf(warnbuf, "%s: %s %s %s.\r\n", level, type2name(warn->qtype), user, msg);
325         if (write_all(fd, warnbuf, strlen(warnbuf)) < 0)
326                 errstr(_("Failed to write quota message for user %llu to %s: %s\n"), (unsigned long long)warn->caused_id, dev, strerror(errno));
327         close(fd);
328 }
329
330 /* Send warning through DBUS */
331 static void write_dbus_warning(struct DBusConnection *dhandle, struct quota_warning *warn)
332 {
333         DBusMessage* msg;
334         DBusMessageIter args;
335
336         msg = dbus_message_new_signal("/", "com.system.quota.warning", "warning");
337         if (!msg) {
338 no_mem:
339                 errstr(_("Cannot create DBUS message: No enough memory.\n"));
340                 goto out;
341         }
342         dbus_message_iter_init_append(msg, &args);
343         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->qtype))
344                 goto no_mem;
345         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT64, &warn->excess_id))
346                 goto no_mem;
347         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->warntype))
348                 goto no_mem;
349         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->dev_major))
350                 goto no_mem;
351         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT32, &warn->dev_minor))
352                 goto no_mem;
353         if (!dbus_message_iter_append_basic(&args, DBUS_TYPE_UINT64, &warn->caused_id))
354                 goto no_mem;
355
356         if (!dbus_connection_send(dhandle, msg, NULL)) {
357                 errstr(_("Failed to write message to dbus: No enough memory.\n"));
358                 goto out;
359         }
360         dbus_connection_flush(dhandle);
361 out:
362         if (msg)
363                 dbus_message_unref(msg);
364 }
365
366 static void run(struct nl_handle *nhandle)
367 {
368         int ret;
369
370         while (1) {
371                 ret = nl_recvmsgs_default(nhandle);
372                 if (ret < 0)
373                         errstr(_("Failed to read or parse quota netlink"
374                                 " message: %s\n"), strerror(-ret));
375         }
376 }
377
378 /* Build file name (absolute path) to PID file of this daemon.
379  * The returned name is allocated on heap. */
380 static char *build_pid_file_name(void)
381 {
382         char *pid_name = NULL;
383         if (!progname) {
384                 errstr(_("Undefined program name.\n"));
385                 return NULL;
386         }
387         pid_name = malloc(9 + strlen(progname) + 4 + 1);
388         if (!pid_name) {
389                 errstr(_("Not enough memory to build PID file name.\n"));
390                 return NULL;
391         }
392         sprintf(pid_name, "/var/run/%s.pid", progname);
393         return pid_name;
394 }
395
396 /* Store daemon's PID to file */
397 static int store_pid(void)
398 {
399         FILE *pid_file;
400         char *pid_name;
401
402         pid_name = build_pid_file_name();
403         if (!pid_name)
404                 return -1;
405
406         pid_file = fopen(pid_name, "w");
407         if (!pid_file) {
408                 errstr(_("Could not open PID file '%s': %s\n"),
409                         pid_name, strerror(errno));
410                 free(pid_name);
411                 return -1;
412         }
413         if (fprintf(pid_file, "%jd\n", (intmax_t)getpid()) < 0) {
414                 errstr(_("Could not write daemon's PID into '%s'.\n"),
415                         pid_name);
416                 fclose(pid_file);
417                 free(pid_name);
418                 return -1;
419         }
420         if (fclose(pid_file)) {
421                 errstr(_("Could not close PID file '%s'.\n"), pid_name);
422                 free(pid_name);
423                 return -1;
424         }
425
426         free(pid_name);
427         return 0;
428 }
429
430 /* Handler for SIGTERM to remove PID file */
431 static void remove_pid(int signal)
432 {
433         char *pid_name;
434
435         pid_name = build_pid_file_name();
436         if (pid_name) {
437                 unlink(pid_name);
438                 free(pid_name);
439         }
440         exit(EXIT_SUCCESS);
441 }
442
443 /* Store daemon's PID into file and register its removal on SIGTERM */
444 static void use_pid_file(void)
445 {
446         struct sigaction term_action;
447
448         term_action.sa_handler = remove_pid;
449         term_action.sa_flags = 0;
450         if (sigaction(SIGTERM, &term_action, NULL))
451                 errstr(_("Could not register PID file removal on SIGTERM.\n"));
452         if (store_pid())
453                 errstr(_("Could not store my PID %jd.\n"), (intmax_t )getpid());
454 }
455
456 int main(int argc, char **argv)
457 {
458         struct nl_handle *nhandle;
459
460         gettexton();
461         progname = basename(argv[0]);
462         parse_options(argc, argv);
463
464         nhandle = init_netlink();
465         if (!(flags & FL_NODBUS))
466                 dhandle = init_dbus();
467         if (!(flags & FL_NODAEMON)) {
468                 use_syslog();
469                 daemon(0, 0);
470                 use_pid_file();
471         }
472         run(nhandle);
473         return 0;
474 }