From 3ea36b63cda3076fcf15ca8df692a7fb5b3c6bc8 Mon Sep 17 00:00:00 2001 From: Milan Crha Date: Fri, 24 Apr 2009 18:37:46 +0200 Subject: [PATCH] Allow getting system timezone information in libecal ** Part of fix for bug #381132 * libecal/Makefile.am: * libecal/e-cal-system-timezone.h: * libecal/e-cal-system-timezone.c: New files for obtaining system timezone location, based on gnome-panel's clock-applet system-timezone.c. * libecal/e-cal-util.h: (e_cal_util_get_system_timezone_location), (e_cal_util_get_system_timezone): * libecal/e-cal-util.c: (): (e_cal_util_get_system_timezone_location), (e_cal_util_get_system_timezone): New API functions to obtain system timezone. --- calendar/ChangeLog | 15 ++ calendar/libecal/Makefile.am | 2 + calendar/libecal/e-cal-system-timezone.c | 428 +++++++++++++++++++++++++++++++ calendar/libecal/e-cal-system-timezone.h | 27 ++ calendar/libecal/e-cal-util.c | 35 +++ calendar/libecal/e-cal-util.h | 4 +- 6 files changed, 510 insertions(+), 1 deletion(-) create mode 100644 calendar/libecal/e-cal-system-timezone.c create mode 100644 calendar/libecal/e-cal-system-timezone.h diff --git a/calendar/ChangeLog b/calendar/ChangeLog index d11c391..6394d6b 100644 --- a/calendar/ChangeLog +++ b/calendar/ChangeLog @@ -1,5 +1,20 @@ 2009-04-24 Milan Crha + ** Part of fix for bug #381132 + + * libecal/Makefile.am: + * libecal/e-cal-system-timezone.h: + * libecal/e-cal-system-timezone.c: + New files for obtaining system timezone location, + based on gnome-panel's clock-applet system-timezone.c. + * libecal/e-cal-util.h: (e_cal_util_get_system_timezone_location), + (e_cal_util_get_system_timezone): + * libecal/e-cal-util.c: (): (e_cal_util_get_system_timezone_location), + (e_cal_util_get_system_timezone): + New API functions to obtain system timezone. + +2009-04-24 Milan Crha + ** Part of fix for bug #478692 * backends/caldav/create-account.c: Removed unused file. diff --git a/calendar/libecal/Makefile.am b/calendar/libecal/Makefile.am index 9addcf6..8e2255c 100644 --- a/calendar/libecal/Makefile.am +++ b/calendar/libecal/Makefile.am @@ -46,6 +46,8 @@ libecal_1_2_la_SOURCES = \ e-cal-recur.c \ e-cal-time-util.c \ e-cal-check-timezones.c \ + e-cal-system-timezone.c \ + e-cal-system-timezone.h \ e-cal-util.c \ e-cal-view.c \ e-cal-view-listener.c \ diff --git a/calendar/libecal/e-cal-system-timezone.c b/calendar/libecal/e-cal-system-timezone.c new file mode 100644 index 0000000..cfee41f --- /dev/null +++ b/calendar/libecal/e-cal-system-timezone.c @@ -0,0 +1,428 @@ +/* Evolution calendar system timezone functions + * Based on gnome-panel's clock-applet system-timezone.c file. + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#include +#include +#include +#include +#include "e-cal-system-timezone.h" + +#ifdef HAVE_SOLARIS +#define SYSTEM_ZONEINFODIR "/usr/share/lib/zoneinfo/tab" +#else +#define SYSTEM_ZONEINFODIR "/usr/share/zoneinfo" +#endif + +#define ETC_TIMEZONE "/etc/timezone" +#define ETC_TIMEZONE_MAJ "/etc/TIMEZONE" +#define ETC_RC_CONF "/etc/rc.conf" +#define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock" +#define ETC_CONF_D_CLOCK "/etc/conf.d/clock" +#define ETC_LOCALTIME "/etc/localtime" + +#define TZ_MAGIC "TZif" + +static char * +system_timezone_strip_path_if_valid (const char *filename) +{ + int skip; + + if (!filename || !g_str_has_prefix (filename, SYSTEM_ZONEINFODIR"/")) + return NULL; + + /* Timezone data files also live under posix/ and right/ for some + * reason. + * FIXME: make sure accepting those files is valid. I think "posix" is + * okay, not sure about "right" */ + if (g_str_has_prefix (filename, SYSTEM_ZONEINFODIR"/posix/")) + skip = strlen (SYSTEM_ZONEINFODIR"/posix/"); + else if (g_str_has_prefix (filename, SYSTEM_ZONEINFODIR"/right/")) + skip = strlen (SYSTEM_ZONEINFODIR"/right/"); + else + skip = strlen (SYSTEM_ZONEINFODIR"/"); + + return g_strdup (filename + skip); +} + +/* Read the soft symlink from /etc/localtime */ +static char * +system_timezone_read_etc_localtime_softlink (void) +{ + char *file; + char *tz; + + if (!g_file_test (ETC_LOCALTIME, G_FILE_TEST_IS_SYMLINK)) + return NULL; + + file = g_file_read_link (ETC_LOCALTIME, NULL); + tz = system_timezone_strip_path_if_valid (file); + g_free (file); + + return tz; +} + +static char * +system_timezone_read_etc_timezone (void) +{ + FILE *etc_timezone; + GString *reading; + int c; + + etc_timezone = g_fopen (ETC_TIMEZONE, "r"); + if (!etc_timezone) + return NULL; + + reading = g_string_new (""); + + c = fgetc (etc_timezone); + /* only get the first line, we'll validate the value later */ + while (c != EOF && !g_ascii_isspace (c)) { + reading = g_string_append_c (reading, c); + c = fgetc (etc_timezone); + } + + fclose (etc_timezone); + + if (reading->str && reading->str[0] != '\0') + return g_string_free (reading, FALSE); + else + g_string_free (reading, TRUE); + + return NULL; +} + +/* Read a file that looks like a key-file (but there's no need for groups) + * and get the last value for a specific key */ +static char * +system_timezone_read_key_file (const char *filename, + const char *key) +{ + GIOChannel *channel; + char *key_eq; + char *line; + char *retval; + + if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR)) + return NULL; + + channel = g_io_channel_new_file (filename, "r", NULL); + if (!channel) + return NULL; + + key_eq = g_strdup_printf ("%s=", key); + retval = NULL; + + while (g_io_channel_read_line (channel, &line, NULL, + NULL, NULL) == G_IO_STATUS_NORMAL) { + if (g_str_has_prefix (line, key_eq)) { + char *value; + int len; + + value = line + strlen (key_eq); + g_strstrip (value); + + len = strlen (value); + + if (value[0] == '\"') { + if (value[len - 1] == '\"') { + if (retval) + g_free (retval); + + retval = g_strndup (value + 1, + len - 2); + } + } else { + if (retval) + g_free (retval); + + retval = g_strdup (line + strlen (key_eq)); + } + + g_strstrip (retval); + } + + g_free (line); + } + + g_free (key_eq); + g_io_channel_unref (channel); + + return retval; +} + +/* This works for Fedora and Mandriva */ +static char * +system_timezone_read_etc_sysconfig_clock (void) +{ + return system_timezone_read_key_file (ETC_SYSCONFIG_CLOCK, + "ZONE"); +} + +/* This works for openSUSE */ +static char * +system_timezone_read_etc_sysconfig_clock_alt (void) +{ + return system_timezone_read_key_file (ETC_SYSCONFIG_CLOCK, + "TIMEZONE"); +} + +/* This works for Solaris/OpenSolaris */ +static char * +system_timezone_read_etc_TIMEZONE (void) +{ + return system_timezone_read_key_file (ETC_TIMEZONE_MAJ, + "TZ"); +} + +/* This works for Arch Linux */ +static char * +system_timezone_read_etc_rc_conf (void) +{ + return system_timezone_read_key_file (ETC_RC_CONF, + "TIMEZONE"); +} + +/* This works for old Gentoo */ +static char * +system_timezone_read_etc_conf_d_clock (void) +{ + return system_timezone_read_key_file (ETC_CONF_D_CLOCK, + "TIMEZONE"); +} + +typedef gboolean (*CompareFiles) (struct stat *a_stat, + struct stat *b_stat, + const char *a_content, + gsize a_content_len, + const char *b_filename); + +static char * +recursive_compare (struct stat *localtime_stat, + const char *localtime_content, + gsize localtime_content_len, + char *file, + CompareFiles compare_func) +{ + struct stat file_stat; + + if (g_stat (file, &file_stat) != 0) + return NULL; + + if (S_ISREG (file_stat.st_mode)) { + if (compare_func (localtime_stat, + &file_stat, + localtime_content, + localtime_content_len, + file)) + return system_timezone_strip_path_if_valid (file); + else + return NULL; + } else if (S_ISDIR (file_stat.st_mode)) { + GDir *dir = NULL; + char *ret = NULL; + const char *subfile = NULL; + char *subpath = NULL; + + dir = g_dir_open (file, 0, NULL); + if (dir == NULL) + return NULL; + + while ((subfile = g_dir_read_name (dir)) != NULL) { + subpath = g_build_filename (file, subfile, NULL); + + ret = recursive_compare (localtime_stat, + localtime_content, + localtime_content_len, + subpath, + compare_func); + + g_free (subpath); + + if (ret != NULL) + break; + } + + g_dir_close (dir); + + return ret; + } + + return NULL; +} + + +static gboolean +files_are_identical_inode (struct stat *a_stat, + struct stat *b_stat, + const char *a_content, + gsize a_content_len, + const char *b_filename) +{ + return (a_stat->st_ino == b_stat->st_ino); +} + + +/* Determine if /etc/localtime is a hard link to some file, by looking at + * the inodes */ +static char * +system_timezone_read_etc_localtime_hardlink (void) +{ + struct stat stat_localtime; + + if (g_stat (ETC_LOCALTIME, &stat_localtime) != 0) + return NULL; + + if (!S_ISREG (stat_localtime.st_mode)) + return NULL; + + return recursive_compare (&stat_localtime, + NULL, + 0, + SYSTEM_ZONEINFODIR, + files_are_identical_inode); +} + +static gboolean +files_are_identical_content (struct stat *a_stat, + struct stat *b_stat, + const char *a_content, + gsize a_content_len, + const char *b_filename) +{ + char *b_content = NULL; + gsize b_content_len = -1; + int cmp; + + if (a_stat->st_size != b_stat->st_size) + return FALSE; + + if (!g_file_get_contents (b_filename, + &b_content, &b_content_len, NULL)) + return FALSE; + + if (a_content_len != b_content_len) { + g_free (b_content); + return FALSE; + } + + cmp = memcmp (a_content, b_content, a_content_len); + g_free (b_content); + + return (cmp == 0); +} + +/* Determine if /etc/localtime is a copy of a timezone file */ +static char * +system_timezone_read_etc_localtime_content (void) +{ + struct stat stat_localtime; + char *localtime_content = NULL; + gsize localtime_content_len = -1; + char *retval; + + if (g_stat (ETC_LOCALTIME, &stat_localtime) != 0) + return NULL; + + if (!S_ISREG (stat_localtime.st_mode)) + return NULL; + + if (!g_file_get_contents (ETC_LOCALTIME, + &localtime_content, + &localtime_content_len, + NULL)) + return NULL; + + retval = recursive_compare (&stat_localtime, + localtime_content, + localtime_content_len, + SYSTEM_ZONEINFODIR, + files_are_identical_content); + + g_free (localtime_content); + + return retval; +} + +typedef char * (*GetSystemTimezone) (void); +/* The order of the functions here define the priority of the methods used + * to find the timezone. First method has higher priority. */ +static GetSystemTimezone get_system_timezone_methods[] = { + /* cheap and "more correct" than data from a config file */ + system_timezone_read_etc_localtime_softlink, + /* reading various config files */ + system_timezone_read_etc_timezone, + system_timezone_read_etc_sysconfig_clock, + system_timezone_read_etc_sysconfig_clock_alt, + system_timezone_read_etc_TIMEZONE, + system_timezone_read_etc_rc_conf, + /* reading deprecated config files */ + system_timezone_read_etc_conf_d_clock, + /* reading /etc/timezone directly. Expensive since we have to stat + * many files */ + system_timezone_read_etc_localtime_hardlink, + system_timezone_read_etc_localtime_content, + NULL +}; + +static gboolean +system_timezone_is_valid (const char *tz) +{ + const char *c; + + if (!tz) + return FALSE; + + for (c = tz; *c != '\0'; c++) { + if (!(g_ascii_isalnum (*c) || + *c == '/' || *c == '-' || *c == '_')) + return FALSE; + } + + return TRUE; +} + +static char * +system_timezone_find (void) +{ + char *tz; + int i; + + for (i = 0; get_system_timezone_methods[i] != NULL; i++) { + tz = get_system_timezone_methods[i] (); + + if (system_timezone_is_valid (tz)) + return tz; + + g_free (tz); + } + + return NULL; +} + +/** + * e_cal_system_timezone_get_location: + * + * Returns system timezone location string, NULL on an error. + * Returned pointer should be freed with g_free(). + **/ +char * +e_cal_system_timezone_get_location (void) +{ + return system_timezone_find (); +} diff --git a/calendar/libecal/e-cal-system-timezone.h b/calendar/libecal/e-cal-system-timezone.h new file mode 100644 index 0000000..ea286a9 --- /dev/null +++ b/calendar/libecal/e-cal-system-timezone.h @@ -0,0 +1,27 @@ +/* Evolution calendar system timezone functions + * Based on gnome-panel's clock-applet system-timezone.c file. + * + * Copyright (C) 1999-2008 Novell, Inc. (www.novell.com) + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of version 2 of the GNU Lesser General Public + * License as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ + +#ifndef E_CAL_SYSTEM_TIMEZONE_H +#define E_CAL_SYSTEM_TIMEZONE_H + +#include + +char *e_cal_system_timezone_get_location (void); + +#endif diff --git a/calendar/libecal/e-cal-util.c b/calendar/libecal/e-cal-util.c index f4f03c5..a80c1f5 100644 --- a/calendar/libecal/e-cal-util.c +++ b/calendar/libecal/e-cal-util.c @@ -26,6 +26,7 @@ #include #include "libedataserver/e-data-server-util.h" #include "e-cal-util.h" +#include "e-cal-system-timezone.h" @@ -1139,3 +1140,37 @@ e_cal_util_remove_instances (icalcomponent *icalcomp, icalrecur_iterator_free (iter); } } + +/** + * e_cal_util_get_system_timezone_location: + * + * Returns system timezone location string, NULL on an error. + * Returned pointer should be freed with g_free(). + **/ +char * +e_cal_util_get_system_timezone_location (void) +{ + return e_cal_system_timezone_get_location (); +} + +/** + * e_cal_util_get_system_timezone: + * + * Returns icaltimezone object of the system timezone. NULL on an error. + * Returned pointer is part of the built-in timezones, thus do not free it. + **/ +icaltimezone * +e_cal_util_get_system_timezone (void) +{ + char *location; + icaltimezone *zone; + + location = e_cal_system_timezone_get_location (); + g_return_val_if_fail (location != NULL, NULL); + + zone = icaltimezone_get_builtin_timezone (location); + + g_free (location); + + return zone; +} diff --git a/calendar/libecal/e-cal-util.h b/calendar/libecal/e-cal-util.h index a369b59..8bd5ee1 100644 --- a/calendar/libecal/e-cal-util.h +++ b/calendar/libecal/e-cal-util.h @@ -135,7 +135,9 @@ void e_cal_util_remove_instances (icalcomponent *icalcomp, struct icaltimetype rid, CalObjModType mod); +char *e_cal_util_get_system_timezone_location (void); +icaltimezone *e_cal_util_get_system_timezone (void); + G_END_DECLS #endif - -- 2.7.4