syncevolution/src/syncevo/icaltz-util.c
Patrick Ohly 426ec3543a syncevolution.org: compile on Ubuntu Trusty, libical v1/v2 compatibility
syncevolution.org binaries are now getting compiled on Ubuntu Trusty and thus
no longer support distros with older EDS. The code should still compile
against older EDS (for example, for Maemo), but that is not getting tested
anymore.

This allows removing the dynamic linker hacks related to older libraries,
which was only used in those binaries. Instead, backends using libical or EDS
get compiled on Ubuntu Trusty and then the soname of those libs get patched to
make the backend module usable in combination with a different set of
libs. That patching is part of a script maintained in the syncevolution.org
build infrastructure.

This approach was already used before to generate different EDS backends
for EDS versions with the newer EClient API, because that turned out to be
easier than the dynamic loading approach. It works because none of the methods
used by SyncEvolution changed their ABI, only some other parts of the
libraries did. Should there ever be a situation again that cannot be handled
like this, then backends might also get compiled on different distros than
Ubuntu Trusty (however, that may lead to problems due to the libstdc++ ABI
changes - to be decided...).

libical still requires one special hack: system time zone loading in
libical v1 (and only in that version, v2 has builtin support again) must
be overridden such that time zones are generated with rules instead
of transitions because that is more compatible with the peers that
SyncEvolution exchanges data with.

That hack now relies on overriding the two relevant functions inside the main
binaries (has to be there, otherwise libical still ends up calling its own
internal implementation). The overriding code is in
libsyncevo-icaltz-util.so.0 and depends on libical.so.1. If
libsyncevo-icaltz-util.so.0 can be loaded, the wrappers in the main binary use
it, otherwise they fall through to the code from the current libical.so, which
then should be libical.so.2 or more recent.

This hack is active by default when libical v1 is detected during configuration.
2016-09-26 12:58:26 +02:00

656 lines
16 KiB
C

/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
/*
* Authors :
* Chenthill Palanisamy <pchenthill@novell.com>
*
* Copyright 2007, Novell, Inc.
*
* 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 library; if not, write to the
* Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
/*
* Compile with
* gcc -c -Wall -DDISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY -DICALTZ_UTIL_MAIN -DHAVE_UNISTD_H -DHAVE_ENDIAN_H -DHAVE_BYTESWAP_H $(pkg-config --cflags --libs libical) -o icaltz-util icaltz-util.c
* to get an utility which will print the VTIMEZONE definition of
* a certain location, for example "Europe/Berlin".
*
* With -DDISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY the
* icaltzutil_get_zone_directory() from libical will be used.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include <string.h>
#ifdef ICALTZ_UTIL_MAIN
#include <stdio.h>
#endif
#ifdef EVOLUTION_ICAL_COMPATIBILITY
# include "eds_abi_wrapper.h"
# else
# include <libical/icaltimezone.h>
#endif
#ifdef HAVE_STDINT_H
#include <stdint.h>
#endif
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#if defined(sun) && defined(__SVR4)
#include <sys/types.h>
#include <sys/byteorder.h>
#else
# ifdef HAVE_BYTESWAP_H
# include <byteswap.h>
# endif
# ifdef HAVE_ENDIAN_H
# include <endian.h>
# else
# ifdef HAVE_SYS_ENDIAN_H
# include <sys/endian.h>
# ifdef bswap32
# define bswap_32 bswap32
# else
# define bswap_32 swap32
# endif
# endif
# endif
#endif
#ifdef _MSC_VER
#if !defined(HAVE_BYTESWAP_H) && !defined(HAVE_SYS_ENDIAN_H) && !defined(HAVE_ENDIAN_H)
#define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff)
#define bswap_32(x) (((x) << 24) & 0xff000000) \
| (((x) << 8) & 0xff0000) \
| (((x) >> 8) & 0xff00) \
| (((x) >> 24) & 0xff )
#define bswap_64(x) ((((x) & 0xff00000000000000ull) >> 56) \
| (((x) & 0x00ff000000000000ull) >> 40) \
| (((x) & 0x0000ff0000000000ull) >> 24) \
| (((x) & 0x000000ff00000000ull) >> 8) \
| (((x) & 0x00000000ff000000ull) << 8) \
| (((x) & 0x0000000000ff0000ull) << 24) \
| (((x) & 0x000000000000ff00ull) << 40) \
| (((x) & 0x00000000000000ffull) << 56))
#endif
#include <io.h>
#endif
#if defined(__APPLE__)
#define bswap_16(x) (((x) << 8) & 0xff00) | (((x) >> 8 ) & 0xff)
#define bswap_32 __builtin_bswap32
#define bswap_64 __builtin_bswap64
#endif
#ifndef PATH_MAX
#define PATH_MAX 512
#endif
#ifndef F_OK
#define F_OK 0
#endif
#ifndef R_OK
#define R_OK 4
#endif
#include <limits.h>
#include <time.h>
#include <stdlib.h>
#ifdef HAVE_UNISTD_H
#include <unistd.h>
#endif
#include <libical/icalerror.h>
#include "icaltz-util.h"
typedef struct
{
char ttisgmtcnt [4];
char ttisstdcnt[4];
char leapcnt[4];
char timecnt[4];
char typecnt[4];
char charcnt[4];
} tzinfo;
static int r_pos [] = {1, 2, 3, -2, -1};
#ifndef DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY
static char *search_paths [] = {"/usr/share/zoneinfo","/usr/lib/zoneinfo","/etc/zoneinfo","/usr/share/lib/zoneinfo"};
static char *zdir = NULL;
#endif
#define NUM_SEARCH_PATHS (sizeof (search_paths)/ sizeof (search_paths [0]))
#define EFREAD(buf,size,num,fs) \
if (fread (buf, size, num, fs) == 0 && ferror (fs)) {\
icalerror_set_errno (ICAL_FILE_ERROR); \
goto error; \
} \
typedef struct
{
long int gmtoff;
unsigned char isdst;
unsigned int abbr;
unsigned char isstd;
unsigned char isgmt;
char *zname;
} ttinfo;
typedef struct
{
time_t transition;
long int change;
} leap;
#ifndef EVOLUTION_ICAL_COMPATIBILITY
extern const char *ical_tzid_prefix;
#endif
static int
decode (const void *ptr)
{
#if defined(sun) && defined(__SVR4)
if (sizeof (int) == 4)
#ifdef _BIG_ENDIAN
return *(const int *) ptr;
#else
return BSWAP_32 (*(const int *) ptr);
#endif
#else
if ((BYTE_ORDER == BIG_ENDIAN) && sizeof (int) == 4)
return *(const int *) ptr;
else if (BYTE_ORDER == LITTLE_ENDIAN && sizeof (int) == 4)
return bswap_32 (*(const int *) ptr);
#endif
else
{
const unsigned char *p = ptr;
int result = *p & (1 << (CHAR_BIT - 1)) ? ~0 : 0;
result = (result << 8) | *p++;
result = (result << 8) | *p++;
result = (result << 8) | *p++;
result = (result << 8) | *p++;
return result;
}
}
static char *
zname_from_stridx (char *str, long int idx)
{
int i = 0;
char *ret;
size_t size;
i = idx;
while (str [i] != '\0')
i++;
size = i - idx;
str += idx;
ret = (char *) malloc (size + 1);
ret = strncpy (ret, str, size);
ret [size] = '\0';
return ret;
}
static void
find_transidx (time_t *transitions, ttinfo *types, int *trans_idx, long int num_trans, int *stdidx, int *dstidx)
{
time_t now, year_start;
int i, found = 0;
struct icaltimetype itime;
now = time (NULL);
itime = icaltime_from_timet (now, 0);
itime.month = itime.day = 1;
itime.hour = itime.minute = itime.second = 0;
year_start = icaltime_as_timet(itime);
/* Set this by default */
*stdidx = (num_trans - 1);
for (i = (num_trans - 1); i >= 0; --i)
if (year_start < transitions [i]) {
int idx;
found = 1;
idx = trans_idx [i];
(types [idx].isdst) ? (*dstidx = i) : (*stdidx = i);
}
/* If the transition found is the last among the list, prepare to use the last two transtions.
* Using this will most likely throw the DTSTART of the resulting component off by 1 or 2 days
* but it would set right by the adjustment made.
* NOTE: We need to use the last two transitions only because there is no data for the future
* transitions.
*/
if (found && (*dstidx == -1)) {
*dstidx = ((*stdidx) - 1);
}
return;
}
#ifndef DISABLE_ICALTZUTIL_GET_ZONE_DIRECTORY
static void
set_zonedir (void)
{
char file_path[PATH_MAX];
const char *fname = ZONES_TAB_SYSTEM_FILENAME;
int i;
for (i = 0;i < NUM_SEARCH_PATHS; i++) {
sprintf (file_path, "%s/%s", search_paths [i], fname);
if (!access (file_path, F_OK|R_OK)) {
zdir = search_paths [i];
break;
}
}
}
const char *
icaltzutil_get_zone_directory (void)
{
if (!zdir)
set_zonedir ();
return zdir;
}
#endif
/* Calculate the relative position of the week in a month from a date */
static int
calculate_pos (icaltimetype icaltime)
{
int pos;
pos = (icaltime.day -1) / 7;
/* Check if pos 3 is the last occurence of the week day in the month */
if (pos == 3 && ((icaltime.day + 7) > icaltime_days_in_month (icaltime.month, icaltime.year)))
pos = 4;
return r_pos [pos];
}
static void
adjust_dtstart_day_to_rrule (icalcomponent *comp, struct icalrecurrencetype rule)
{
time_t now, year_start;
struct icaltimetype start, comp_start, iter_start, itime;
icalrecur_iterator *iter;
now = time (NULL);
itime = icaltime_from_timet (now, 0);
itime.month = itime.day = 1;
itime.hour = itime.minute = itime.second = 0;
year_start = icaltime_as_timet(itime);
comp_start = icalcomponent_get_dtstart (comp);
start = icaltime_from_timet (year_start, 0);
iter = icalrecur_iterator_new (rule, start);
iter_start = icalrecur_iterator_next (iter);
icalrecur_iterator_free (iter);
if (iter_start.day != comp_start.day) {
comp_start.day = iter_start.day;
icalcomponent_set_dtstart (comp, comp_start);
}
}
icalcomponent*
icaltzutil_fetch_timezone (const char *location)
{
int ret = 0;
FILE *f;
tzinfo type_cnts;
unsigned int i, num_trans, num_types = 0, num_chars, num_leaps, num_isstd, num_isgmt;
time_t *transitions = NULL;
time_t trans;
int *trans_idx = NULL, dstidx = -1, stdidx = -1, pos, sign, zidx, zp_idx;
ttinfo *types = NULL;
char *znames = NULL, *full_path, *tzid, *r_trans, *temp = NULL;
leap *leaps = NULL;
icalcomponent *tz_comp = NULL, *dst_comp = NULL, *std_comp = NULL;
icalproperty *icalprop;
icaltimetype dtstart, icaltime;
struct icalrecurrencetype ical_recur;
const char *basedir;
/* if (!location) return NULL; */
basedir = icaltzutil_get_zone_directory();
if (!basedir) {
icalerror_set_errno (ICAL_FILE_ERROR);
return NULL;
}
full_path = (char *) malloc (strlen (basedir) + strlen (location) + 2);
sprintf (full_path,"%s/%s",basedir, location);
if ((f = fopen (full_path, "rb")) == 0) {
icalerror_set_errno (ICAL_FILE_ERROR);
free (full_path);
return NULL;
}
if ((ret = fseek (f, 20, SEEK_SET)) != 0) {
icalerror_set_errno (ICAL_FILE_ERROR);
goto error;
}
EFREAD(&type_cnts, 24, 1, f);
num_isgmt = decode (type_cnts.ttisgmtcnt);
num_leaps = decode (type_cnts.leapcnt);
num_chars = decode (type_cnts.charcnt);
num_trans = decode (type_cnts.timecnt);
num_isstd = decode (type_cnts.ttisstdcnt);
num_types = decode (type_cnts.typecnt);
transitions = calloc (num_trans, sizeof (time_t));
temp = calloc (num_trans, 4);
r_trans = temp;
EFREAD(r_trans, 4, num_trans, f);
if (num_trans) {
trans_idx = calloc (num_trans, sizeof (int));
for (i = 0; i < num_trans; i++) {
trans_idx [i] = fgetc (f);
transitions [i] = decode (r_trans);
r_trans += 4;
}
}
free (temp);
temp = NULL;
types = calloc (num_types, sizeof (ttinfo));
for (i = 0; i < num_types; i++) {
unsigned char a [4];
int c;
EFREAD(a, 4, 1, f);
c = fgetc (f);
types [i].isdst = c;
if((c = fgetc (f)) < 0) {
break;
}
types [i].abbr = c;
types [i].gmtoff = decode (a);
}
znames = (char *) malloc (num_chars);
EFREAD(znames, num_chars, 1, f);
/* We got all the information which we need */
leaps = calloc (num_leaps, sizeof (leap));
for (i = 0; i < num_leaps; i++) {
char c [4];
EFREAD (c, 4, 1, f);
leaps [i].transition = decode (c);
EFREAD (c, 4, 1, f);
leaps [i].change = decode (c);
}
for (i = 0; i < num_isstd; ++i) {
int c = getc (f);
types [i].isstd = c != 0;
}
while (i < num_types)
types [i++].isstd = 0;
for (i = 0; i < num_isgmt; ++i) {
int c = getc (f);
types [i].isgmt = c != 0;
}
while (i < num_types)
types [i++].isgmt = 0;
/* Read all the contents now */
for (i = 0; i < num_types; i++)
types [i].zname = zname_from_stridx (znames, types [i].abbr);
if (num_trans != 0)
find_transidx (transitions, types, trans_idx, num_trans, &stdidx, &dstidx);
else
stdidx = 0;
tz_comp = icalcomponent_new (ICAL_VTIMEZONE_COMPONENT);
/* Add tzid property */
tzid = (char *) malloc (strlen (ical_tzid_prefix) + strlen (location) + 8);
sprintf (tzid, "%sTzfile/%s", ical_tzid_prefix, location);
icalprop = icalproperty_new_tzid (tzid);
icalcomponent_add_property (tz_comp, icalprop);
free (tzid);
icalprop = icalproperty_new_x (location);
icalproperty_set_x_name (icalprop, "X-LIC-LOCATION");
icalcomponent_add_property (tz_comp, icalprop);
if (stdidx != -1) {
if (num_trans != 0)
zidx = trans_idx [stdidx];
else
zidx = 0;
std_comp = icalcomponent_new (ICAL_XSTANDARD_COMPONENT);
icalprop = icalproperty_new_tzname (types [zidx].zname);
icalcomponent_add_property (std_comp, icalprop);
if (dstidx != -1)
zp_idx = trans_idx [stdidx-1];
else
zp_idx = zidx;
/* DTSTART localtime uses TZOFFSETFROM UTC offset */
if (num_trans != 0)
trans = transitions [stdidx] + types [zp_idx].gmtoff;
else
trans = types [zp_idx].gmtoff;
icaltime = icaltime_from_timet (trans, 0);
dtstart = icaltime;
dtstart.year = 1970;
dtstart.minute = dtstart.second = 0;
icalprop = icalproperty_new_dtstart (dtstart);
icalcomponent_add_property (std_comp, icalprop);
/* If DST changes are present use RRULE */
if (dstidx != -1) {
icalrecurrencetype_clear (&ical_recur);
ical_recur.freq = ICAL_YEARLY_RECURRENCE;
ical_recur.by_month [0] = icaltime.month;
pos = calculate_pos (icaltime);
pos < 0 ? (sign = -1): (sign = 1);
ical_recur.by_day [0] = sign * ((abs (pos) * 8) + icaltime_day_of_week (icaltime));
icalprop = icalproperty_new_rrule (ical_recur);
icalcomponent_add_property (std_comp, icalprop);
adjust_dtstart_day_to_rrule (std_comp, ical_recur);
}
icalprop = icalproperty_new_tzoffsetfrom (types [zp_idx].gmtoff);
icalcomponent_add_property (std_comp, icalprop);
icalprop = icalproperty_new_tzoffsetto (types [zidx].gmtoff);
icalcomponent_add_property (std_comp, icalprop);
icalcomponent_add_component (tz_comp, std_comp);
} else
icalerror_set_errno (ICAL_MALFORMEDDATA_ERROR);
if (dstidx != -1) {
zidx = trans_idx [dstidx];
zp_idx = trans_idx [dstidx-1];
dst_comp = icalcomponent_new (ICAL_XDAYLIGHT_COMPONENT);
icalprop = icalproperty_new_tzname (types [zidx].zname);
icalcomponent_add_property (dst_comp, icalprop);
/* DTSTART localtime uses TZOFFSETFROM UTC offset */
if (num_trans != 0)
trans = transitions [dstidx] + types [zp_idx].gmtoff;
else
trans = types [zp_idx].gmtoff;
icaltime = icaltime_from_timet (trans, 0);
dtstart = icaltime;
dtstart.year = 1970;
dtstart.minute = dtstart.second = 0;
icalprop = icalproperty_new_dtstart (dtstart);
icalcomponent_add_property (dst_comp, icalprop);
icalrecurrencetype_clear (&ical_recur);
ical_recur.freq = ICAL_YEARLY_RECURRENCE;
ical_recur.by_month [0] = icaltime.month;
pos = calculate_pos (icaltime);
pos < 0 ? (sign = -1): (sign = 1);
ical_recur.by_day [0] = sign * ((abs (pos) * 8) + icaltime_day_of_week (icaltime));
icalprop = icalproperty_new_rrule (ical_recur);
icalcomponent_add_property (dst_comp, icalprop);
adjust_dtstart_day_to_rrule (dst_comp, ical_recur);
icalprop = icalproperty_new_tzoffsetfrom (types [zp_idx].gmtoff);
icalcomponent_add_property (dst_comp, icalprop);
icalprop = icalproperty_new_tzoffsetto (types [zidx].gmtoff);
icalcomponent_add_property (dst_comp, icalprop);
icalcomponent_add_component (tz_comp, dst_comp);
}
error:
if (f)
fclose (f);
if (transitions)
free (transitions);
if (trans_idx)
free (trans_idx);
if (types) {
for (i = 0; i < num_types; i++)
if (types [i].zname)
free (types [i].zname);
free (types);
}
if (znames)
free (znames);
free (full_path);
if (leaps)
free (leaps);
if (temp)
free (temp);
return tz_comp;
}
/*
* What follows is copied and slightly simplified (not thread-safe!)
* code from icaltimezone.c.
*
* This is necessary because otherwise, when libsynthesis.so calls
* icaltimezone_get_component(), libical.1.so uses its own builtin
* icaltzutil_fetch_timezone(). Apparently it exports that without
* looking it up via the dynamic linker itself. Therefore we have to
* redirect the original icaltimezone_get_component() call.
*/
static void
icaltimezone_load_builtin_timezone (icaltimezone *zone)
{
icalcomponent *subcomp;
const char *location;
/* If the location isn't set, it isn't a builtin timezone. */
location = icaltimezone_get_location(zone);
if (!location || !location[0])
return;
subcomp = icaltzutil_fetch_timezone (location);
if (!subcomp) {
icalerror_set_errno(ICAL_PARSE_ERROR);
return;
}
icaltimezone_set_component(zone, subcomp);
}
#undef icaltimezone_get_component
icalcomponent *icaltimezone_get_component(icaltimezone *zone)
{
icalcomponent *comp;
/* If this is a floating time, without a timezone, return NULL. */
if (!zone)
return NULL;
/*
* Without this check, icaltimezone_set_component() in
* icaltimezone_load_builtin_timezone() will discard the
* already loaded component of builtin timezones and replace
* it with the new one, so there is no leak. It's just
* inefficient.
*
* However, this method also gets called for non-internal
* timezones which were created from a VTIMEZONE and in
* that case not using the existing component is wrong.
*
* Hack: duplicate the internal _icaltimezone struct (from
* icaltimezoneimpl.h).
*/
struct _my_icaltimezone {
char *tzid;
char *location;
char *tznames;
double latitude;
double longitude;
icalcomponent *component;
};
comp = ((struct _my_icaltimezone *)zone)->component;
if (!comp) {
icaltimezone_load_builtin_timezone (zone);
comp = ((struct _my_icaltimezone *)zone)->component;
}
return comp;
}
#ifdef ICALTZ_UTIL_MAIN
int
main (int argc, char *argv [])
{
icalcomponent *comp = icaltzutil_fetch_timezone (argv [1]);
const char *str = comp ? icalcomponent_as_ical_string(comp) : "no such zone";
puts(str);
return 0;
}
#endif