2
* Copyright (C) 2004 Lukas Lipka <lukas@pmad.net>
3
* (C) 2004 Michael Vogt <mvo@debian.org>
4
* (C) 2004 Michiel Sikkes <michiel@eyesopened.nl>
5
* (C) 2004-2009 Canonical
7
* This library is free software; you can redistribute it and/or
8
* modify it under the terms of the GNU Lesser General Public
9
* License as published by the Free Software Foundation; either
10
* version 2 of the License, or (at your option) any later version.
12
* This library is distributed in the hope that it will be useful,
13
* but WITHOUT ANY WARRANTY; without even the implied warranty of
14
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15
* Lesser General Public License for more details.
17
* You should have received a copy of the GNU Lesser General Public
18
* License along with this library; if not, write to the
19
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor
20
* Boston, MA 02110-1301 USA.
28
#include <sys/types.h>
37
#include <glib/gstdio.h>
38
#include <glib-unix.h>
43
#include "update-notifier.h"
44
#include "livepatch-tray.h"
50
#include "trayappletui.h"
53
gboolean update_timer_finished(gpointer data);
55
// the time when we check for fam events, in seconds
56
#define TIMEOUT_FAM 180
58
// the timeout (in sec) when a further activity from dpkg/apt
59
// causes the applet to "ungray"
60
#define TIMEOUT_APT_RUN 600 /* 600 sec */
62
// force startup even if the user is not in the sudo/admin group
63
static gboolean FORCE_START=FALSE;
65
// force pkexec for all menu actions
66
static gboolean FORCE_PKEXEC=FALSE;
68
// delay on startup (to make session startup faster)
69
static int STARTUP_DELAY=1;
71
// make the release upgrader check for a devel release
72
gboolean DEVEL_RELEASE=FALSE;
74
// force release check
75
static gboolean FORCE_RELEASE_CHECK=FALSE;
77
// global debug options
78
static gboolean HOOK_DEBUG = FALSE;
79
static gboolean UPDATE_DEBUG = FALSE;
80
static gboolean INOTIFY_DEBUG = FALSE;
81
static gboolean UEVENT_DEBUG = FALSE;
82
static gboolean RELEASE_DEBUG = FALSE;
83
static gboolean MISC_DEBUG = FALSE;
86
static void debug_log_handler (const gchar *log_domain,
87
GLogLevelFlags log_level,
91
if (log_domain && strcmp(log_domain, "hooks") == 0) {
93
g_log_default_handler (log_domain, log_level, message, user_data);
95
else if (log_domain && strcmp(log_domain, "update") == 0) {
97
g_log_default_handler (log_domain, log_level, message, user_data);
99
else if (log_domain && strcmp(log_domain, "inotify") == 0) {
101
g_log_default_handler (log_domain, log_level, message, user_data);
103
else if (log_domain && strcmp(log_domain, "uevent") == 0) {
105
g_log_default_handler (log_domain, log_level, message, user_data);
107
else if (log_domain && strcmp(log_domain, "release") == 0) {
109
g_log_default_handler (log_domain, log_level, message, user_data);
112
g_log_default_handler (log_domain, log_level, message, user_data);
116
g_debug_inotify(const char *msg, ...)
120
g_logv("inotify",G_LOG_LEVEL_DEBUG, msg, va);
124
// launch async scripts one after another
125
static void start_next_script(GPid pid,
127
GSList *remaining_scripts_to_run)
129
if(remaining_scripts_to_run) {
130
gboolean ret = False;
131
gchar* full_path = remaining_scripts_to_run->data;
132
gchar *argv[] = { full_path, NULL };
134
g_debug_inotify ("executing: %s", full_path);
135
if (g_file_test(full_path, G_FILE_TEST_IS_EXECUTABLE ))
136
ret = g_spawn_async("/", argv, NULL, G_SPAWN_DO_NOT_REAP_CHILD, NULL, NULL, &pid, NULL);
137
remaining_scripts_to_run = g_slist_remove(remaining_scripts_to_run, full_path);
141
g_warning("script execution failed or not executable");
142
start_next_script(0, 0, remaining_scripts_to_run);
145
g_child_watch_add(pid, (GChildWatchFunc)start_next_script, remaining_scripts_to_run);
150
void invoke(const gchar *cmd, const gchar *desktop, gboolean with_pkexec)
152
GdkAppLaunchContext *context;
154
GError *error = NULL;
155
static GtkWidget *w = NULL;
158
if(with_pkexec || FORCE_PKEXEC) {
159
invoke_with_pkexec(cmd);
163
// fake window to get the current server time *urgh*
165
w = gtk_window_new(GTK_WINDOW_TOPLEVEL);
166
gtk_widget_realize (w);
170
context = gdk_display_get_app_launch_context (gdk_display_get_default ());
171
guint32 timestamp = gdk_x11_get_server_time (gtk_widget_get_window (w));
172
appinfo = g_app_info_create_from_commandline(cmd,
174
G_APP_INFO_CREATE_NONE,
176
gdk_app_launch_context_set_timestamp (context, timestamp);
177
if (!g_app_info_launch (appinfo, NULL, (GAppLaunchContext*)context, &error))
178
g_warning ("Launching failed: %s", error->message);
179
g_object_unref (context);
180
g_object_unref (appinfo);
185
invoke_with_pkexec(const gchar *cmd)
187
g_debug("invoke_with_pkexec ()");
189
argv[0] = "/usr/bin/pkexec";
190
argv[1] = (gchar*)cmd;
192
g_spawn_async (NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL);
198
g_debug("avahi disabled ()");
200
argv[0] = "/usr/lib/update-notifier/local-avahi-notification";
202
g_spawn_async (NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL);
205
void livepatch_check()
207
g_debug("livepatch_check ()");
209
argv[0] = "/usr/lib/update-notifier/livepatch-notification";
211
g_spawn_async (NULL, argv, NULL, G_SPAWN_DEFAULT, NULL, NULL, NULL, NULL);
215
trayapplet_create (TrayApplet *ta, UpgradeNotifier *un, char *name)
217
g_debug("trayapplet_create()");
226
the following files change:
230
- /var/lib/update-notifier/dpkg-run-stamp
232
- /var/lib/apt/lists/lock
233
- /var/lib/apt/lists/ *
235
- /var/lib/apt/periodic/update-success-stamp
238
monitor_cb(GFileMonitor *handle,
240
GFile *other_monitor_f,
241
GFileMonitorEvent event_type,
244
UpgradeNotifier *un = (UpgradeNotifier*)user_data;
246
gchar *info_uri = g_file_get_path(monitor_f);
248
g_debug_inotify("info_uri: %s", info_uri);
249
g_debug_inotify("event_type: %i",event_type);
252
// we ignore lock file events because we can only get
253
// when a lock was taken, but not when it was removed
254
if(g_str_has_suffix(info_uri, "lock"))
257
// look for apt-get install/update
258
if(g_str_has_prefix(info_uri,"/var/lib/apt/")
259
|| g_str_has_prefix(info_uri,"/var/cache/apt/")
260
|| strcmp(info_uri,"/var/lib/dpkg/status") == 0) {
261
g_debug_inotify("apt_get_running=TRUE");
262
un->apt_get_running=TRUE;
264
if(strstr(info_uri, "/var/lib/update-notifier/dpkg-run-stamp") ||
265
strstr(info_uri, "/var/lib/apt/periodic/update-success-stamp")) {
266
g_debug_inotify("dpkg_was_run=TRUE");
267
un->dpkg_was_run = TRUE;
269
if(strstr(info_uri, HOOKS_DIR)) {
270
g_debug_inotify("new hook!");
271
un->hook_pending = TRUE;
273
if(strstr(info_uri, CRASHREPORT_DIR)) {
274
g_debug_inotify("crashreport found");
275
un->crashreport_pending = TRUE;
277
if(strstr(info_uri, UNICAST_LOCAL_AVAHI_FILE)) {
278
g_debug_inotify("avahi disabled due to unicast .local domain");
279
un->unicast_local_avahi_pending = TRUE;
281
if(strstr(info_uri, LIVEPATCH_FILE)) {
282
g_debug_inotify("livepatch status changed");
283
un->livepatch_pending = TRUE;
288
* We periodically check here what actions happened in this "time-slice".
290
* - dpkg_was_run=TRUE: set when apt wrote the "dpkg-run-stamp" file
291
* - apt_get_running: set when apt/dpkg activity is detected (in the
292
* lists-dir, archive-cache, or /var/lib/dpkg/status)
293
* - hook_pending: we have new upgrade hook information
294
* - crashreport_pending: we have a new crashreport
295
* - unicast_local_avahi_pending: avahi got disabled due to a unicast .local domain
296
* - livepatch_pending: livepatch status changed
300
file_monitor_periodic_check(gpointer data)
302
UpgradeNotifier *un = (UpgradeNotifier *)data;
304
// we are not ready yet, wait for the next timeslice
305
if(un->crashreport == NULL)
308
// DPkg::Post-Invoke has written a stamp file, that means an install/remove
309
// operation finished, we can show hooks/reboot notifications then
310
if(un->dpkg_was_run) {
313
update_check(un->update);
315
// run cache-changed plugin scripts
316
GDir *dir = g_dir_open(CACHE_CHANGED_PLUGIN_DIR, 0, NULL);
317
const gchar *fname, *full_path;
318
GSList *cache_changed_scripts = NULL;
319
while ( (fname = g_dir_read_name(dir)) != NULL ) {
320
full_path = g_build_filename(CACHE_CHANGED_PLUGIN_DIR, fname, NULL);
321
cache_changed_scripts = g_slist_insert_sorted(cache_changed_scripts,
322
(gpointer)full_path, (GCompareFunc)g_strcmp0);
325
// launch first script and queue others
326
start_next_script(0, 0, cache_changed_scripts);
328
// any apt-get update must be finished, otherwise
329
// apt-get install wouldn't be finished
330
update_apt_is_running(un->update, FALSE);
331
if(un->update_finished_timer > 0)
332
g_source_remove(un->update_finished_timer);
334
// apt must be finished when a new stamp file was written, so we
335
// reset the apt_get_running time-slice field because its not
336
// important anymore (it finished running)
338
// This may leave a 5s race condition when a apt-get install finished
339
// and something new (apt-get) was started
340
un->apt_get_running = FALSE;
341
un->last_apt_action = 0;
344
// show pending hooks
345
if(un->hook_pending) {
346
//g_print("checking hooks now\n");
347
check_update_hooks(un->hook);
348
un->hook_pending = FALSE;
351
// apt-get update/install or dpkg is running (and updates files in
352
// its list/cache dir) or in /var/lib/dpkg/status
353
if(un->apt_get_running)
354
update_apt_is_running(un->update, TRUE);
356
// update time information for apt/dpkg
357
time_t now = time(NULL);
358
if(un->apt_get_running)
359
un->last_apt_action = now;
361
// no apt operation for a long time
362
if(un->last_apt_action > 0 &&
363
(now - un->last_apt_action) > TIMEOUT_APT_RUN) {
364
update_apt_is_running(un->update, FALSE);
365
update_check(un->update);
366
un->last_apt_action = 0;
369
if(un->crashreport_pending) {
370
g_debug("checking for valid crashreport now");
371
crashreport_check (un->crashreport);
372
un->crashreport_pending = FALSE;
375
if(un->unicast_local_avahi_pending) {
376
g_debug("checking for disabled avahi due to unicast .local domain now");
378
un->unicast_local_avahi_pending = FALSE;
381
if(un->livepatch_pending) {
382
g_debug("checking for livepatch status now");
384
un->livepatch_pending = FALSE;
387
// reset the bitfields (for the next "time-slice")
388
un->dpkg_was_run = FALSE;
389
un->apt_get_running = FALSE;
397
// FIXME: get the apt directories with apt-config or something
399
monitor_init(UpgradeNotifier *un)
402
GFileMonitor *monitor_handle;
404
// monitor these dirs
405
static const char *monitor_dirs[] = {
406
"/var/lib/apt/lists/", "/var/lib/apt/lists/partial/",
407
"/var/cache/apt/archives/", "/var/cache/apt/archives/partial/",
411
for(i=0;monitor_dirs[i] != NULL;i++) {
412
if (getenv("UPSTART_SESSION") && monitor_dirs[i] == CRASHREPORT_DIR) {
415
GError *error = NULL;
416
GFile *gf = g_file_new_for_path(monitor_dirs[i]);
417
monitor_handle = g_file_monitor_directory(gf, 0, NULL, &error);
419
g_signal_connect(monitor_handle, "changed", (GCallback)monitor_cb, un);
421
g_warning("can not add '%s'", monitor_dirs[i]);
425
static const char *monitor_files[] = {
426
"/var/lib/dpkg/status",
427
"/var/lib/update-notifier/dpkg-run-stamp",
428
"/var/lib/apt/periodic/update-success-stamp",
429
UNICAST_LOCAL_AVAHI_FILE,
432
for(i=0;monitor_files[i] != NULL;i++) {
433
if (getenv("UPSTART_SESSION") && monitor_files[i] == UNICAST_LOCAL_AVAHI_FILE) {
436
GError *error = NULL;
437
GFile *gf = g_file_new_for_path(monitor_files[i]);
438
monitor_handle = g_file_monitor_file(gf, 0, NULL, &error);
440
g_signal_connect(monitor_handle, "changed", (GCallback)monitor_cb, un);
442
g_warning("can not add '%s'", monitor_dirs[i]);
444
g_timeout_add_seconds (TIMEOUT_FAM,
445
(GSourceFunc)file_monitor_periodic_check, un);
455
tray_icons_init(UpgradeNotifier *un)
457
//g_debug_inotify("tray_icons_init");
459
/* new updates tray icon */
460
un->update = g_new0 (TrayApplet, 1);
462
// check if the updates icon should be displayed
463
if (in_admin_group() || FORCE_START) {
464
trayapplet_create(un->update, un, "software-update-available");
465
update_tray_icon_init(un->update);
469
/* update hook icon*/
470
un->hook = g_new0 (TrayApplet, 1);
471
trayapplet_create(un->hook, un, "hook-notifier");
472
hook_tray_icon_init(un->hook);
474
/* crashreport detected icon */
475
un->crashreport = g_new0 (TrayApplet, 1);
476
trayapplet_create(un->crashreport, un, "apport");
477
crashreport_tray_icon_init(un->crashreport);
480
un->livepatch = g_new0(TrayApplet, 1);
481
trayapplet_create(un->livepatch, un, "livepatch");
482
livepatch_tray_icon_init(un->livepatch);
484
return FALSE; // for the tray_destroyed_cb
488
system_user(UpgradeNotifier *un)
490
/* do not start for system users, e.g. the guest session
491
* (see /usr/share/gdm/guest-session/guest-session-setup.sh)
493
int end_system_uid = 500;
494
int i = g_settings_get_int(un->settings, SETTINGS_KEY_END_SYSTEM_UIDS);
498
uid_t uid = getuid();
499
if (uid < end_system_uid)
504
// this function checks if the user is in the given group; return 1 if user is
505
// a member, 0 if not; return 2 if group does not exist.
507
user_in_group(const char* grpname)
510
gid_t *groups = NULL;
512
struct group *grp = getgrnam(grpname);
516
ng = getgroups (0, NULL);
517
groups = (gid_t *) malloc (ng * sizeof (gid_t));
519
i = getgroups (ng, groups);
526
if(groups[i] == grp->gr_gid) {
541
int is_admin, is_sudo;
543
// we consider the user an admin if the user is in the "sudo" or "admin"
544
// group or neither of these groups exist
545
is_admin = user_in_group("admin");
546
is_sudo = user_in_group("sudo");
547
return is_admin == 1 || is_sudo == 1 || (is_admin + is_sudo == 4);
552
sigint_cb (gpointer user_data __attribute__ ((__unused__)))
559
static GOptionEntry entries[] =
561
{ "debug-hooks", 0, 0, G_OPTION_ARG_NONE, &HOOK_DEBUG, "Enable hooks debugging"},
562
{ "debug-updates", 0, 0, G_OPTION_ARG_NONE, &UPDATE_DEBUG, "Enable updates/autolaunch debugging"},
563
{ "debug-inotify", 0, 0, G_OPTION_ARG_NONE, &INOTIFY_DEBUG, "Enable inotify debugging"},
564
{ "debug-uevent", 0, 0, G_OPTION_ARG_NONE, &UEVENT_DEBUG, "Enable uevent debugging"},
565
{ "debug-new-release-check", 0, 0, G_OPTION_ARG_NONE, &RELEASE_DEBUG, "Enable new release check debugging"},
566
{ "debug-misc", 0, 0, G_OPTION_ARG_NONE, &MISC_DEBUG, "Enable uncategorized debug prints"},
567
{ "force", 0, 0, G_OPTION_ARG_NONE, &FORCE_START, "Force start even if the user is not in the admin group"},
568
{ "force-use-pkexec", 0, 0, G_OPTION_ARG_NONE, &FORCE_PKEXEC, "Force running all commands (update-manager, synaptic) with pkexec" },
569
{ "startup-delay", 0, 0, G_OPTION_ARG_INT, &STARTUP_DELAY, "Delay startup by given amount of seconds" },
570
{ "devel-release", 0, 0, G_OPTION_ARG_NONE, &DEVEL_RELEASE, "Make the release checker check for a new development release" },
571
{ "force-release-check", 0, 0, G_OPTION_ARG_NONE, &FORCE_RELEASE_CHECK, "Force release check" },
576
main (int argc, char **argv)
579
GError *error = NULL;
584
if(!gtk_init_with_args (&argc, &argv,
585
_("- inform about updates"), entries,
586
"update-notifier", &error) ) {
587
fprintf(stderr, _("Failed to init the UI: %s\n"),
588
error ? error->message : _("unknown error"));
592
notify_init("update-notifier");
593
bindtextdomain(GETTEXT_PACKAGE, PACKAGE_LOCALE_DIR);
594
bind_textdomain_codeset(GETTEXT_PACKAGE, "UTF-8");
595
textdomain(GETTEXT_PACKAGE);
597
if (HOOK_DEBUG || UPDATE_DEBUG || INOTIFY_DEBUG ||
598
UEVENT_DEBUG || RELEASE_DEBUG || MISC_DEBUG)
599
g_setenv ("G_MESSAGES_DEBUG", "all", TRUE);
601
// setup a custom debug log handler
602
g_log_set_handler ("inotify", G_LOG_LEVEL_DEBUG,
603
debug_log_handler, NULL);
604
g_log_set_handler ("hooks", G_LOG_LEVEL_DEBUG,
605
debug_log_handler, NULL);
606
g_log_set_handler ("update", G_LOG_LEVEL_DEBUG,
607
debug_log_handler, NULL);
608
g_log_set_handler ("uevent", G_LOG_LEVEL_DEBUG,
609
debug_log_handler, NULL);
610
g_log_set_handler ("release", G_LOG_LEVEL_DEBUG,
611
debug_log_handler, NULL);
612
g_log_set_handler (NULL, G_LOG_LEVEL_DEBUG,
613
debug_log_handler, NULL);
615
g_set_application_name (_("update-notifier"));
616
gtk_window_set_default_icon_name ("update-notifier");
618
//g_print("starting update-notifier\n");
620
/* Create the UpgradeNotifier object */
621
un = g_new0 (UpgradeNotifier, 1);
622
un->settings = g_settings_new(SETTINGS_SCHEMA);
624
// do not run as system user (e.g. guest user)
625
if (system_user(un) && !FORCE_START) {
626
g_warning("not starting for system user");
630
// do not run as system user (e.g. guest user)
631
if (FORCE_RELEASE_CHECK)
632
g_settings_reset(un->settings, SETTINGS_KEY_LAST_RELEASE_CHECK);
634
// check if it is running already
635
lockfn = g_build_filename (g_get_user_runtime_dir (), "update-notifier.pid", NULL);
636
pid_file = open (lockfn, O_CREAT | O_RDWR, 0600);
638
rc = flock(pid_file, LOCK_EX | LOCK_NB);
640
g_warning ("already running?");
644
// check for update-notifier dir and create if needed
645
gchar *dirname = g_strdup_printf("%s/update-notifier",
646
g_get_user_config_dir());
647
if(!g_file_test(dirname, G_FILE_TEST_IS_DIR))
648
g_mkdir(dirname, 0700);
651
// delay icon creation for 30s so that the desktop
652
// login is not slowed down by update-notifier
653
g_timeout_add_seconds(STARTUP_DELAY,
654
(GSourceFunc)(tray_icons_init), un);
656
// init release checker
657
release_checker_init(un);
659
// init uevent monitoring (printer support, etc.)
664
/* check if livepatch patches has been applied during boot or before
665
update-notifier has started (see LP: #1800862) */
668
// init gio file monitoring
671
/* Start the main gtk loop */
672
g_unix_signal_add_full (G_PRIORITY_DEFAULT, SIGINT, sigint_cb,