1 /*
2 |  Copyright (C) 2002-2007 Jorg Schuler <jcsjcs at users sourceforge net>
3 |  Copyright (C) 2009 Christophe Fergeau <cfergeau at mandriva com>
4 |  Part of the gtkpod project.
5 |
6 |  URL: http://www.gtkpod.org/
7 |  URL: http://gtkpod.sourceforge.net/
8 |
9 |  The code contained in this file is free software; you can redistribute
10 |  it and/or modify it under the terms of the GNU Lesser General Public
11 |  License as published by the Free Software Foundation; either version
12 |  2.1 of the License, or (at your option) any later version.
13 |
14 |  This file is distributed in the hope that it will be useful,
15 |  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 |  Lesser General Public License for more details.
18 |
19 |  You should have received a copy of the GNU Lesser General Public
20 |  License along with this code; if not, write to the Free Software
21 |  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
22 |  USA
23 |
24 |  iTunes and iPod are trademarks of Apple
25 |
26 |  This product is not supported/written/published by Apple!
27 |
28 |  $Id$
29 */
30 #include <config.h>
31 
32 #include "itdb.h"
33 #include "itdb_device.h"
34 #include "itdb_private.h"
35 #include "itdb_tzinfo_data.h"
36 
37 #include <stdio.h>
38 #include <string.h>
39 #include <time.h>
40 
41 #include <glib/gstdio.h>
42 
43 #ifdef __CYGWIN__
44     extern __IMPORT long _timezone;
45 #endif
46 
47 static gboolean parse_tzdata (const char *tzname, time_t start, time_t end,
48 			      int *offset, gboolean *has_dst, int *dst_offset);
49 
50 /* Mac epoch is 1st January 1904 00:00 in local time */
device_time_mac_to_time_t(Itdb_Device * device,guint64 mactime)51 G_GNUC_INTERNAL time_t device_time_mac_to_time_t (Itdb_Device *device, guint64 mactime)
52 {
53     g_return_val_if_fail (device, 0);
54     if (mactime != 0)  return (time_t)(mactime - 2082844800 - device->timezone_shift);
55     else               return (time_t)mactime;
56 }
57 
device_time_time_t_to_mac(Itdb_Device * device,time_t timet)58 G_GNUC_INTERNAL guint64 device_time_time_t_to_mac (Itdb_Device *device, time_t timet)
59 {
60     g_return_val_if_fail (device, 0);
61     if (timet != 0)
62 	return ((guint64)timet) + 2082844800 + device->timezone_shift;
63     else return 0;
64 }
65 
66 static char *
get_preferences_path(const Itdb_Device * device)67 get_preferences_path (const Itdb_Device *device)
68 {
69 
70     const gchar *p_preferences[] = {"Preferences", NULL};
71     char *dev_path;
72     char *prefs_filename;
73 
74     if (device->mountpoint == NULL) {
75         return NULL;
76     }
77 
78     dev_path = itdb_get_device_dir (device->mountpoint);
79 
80     if (dev_path == NULL) {
81         return NULL;
82     }
83 
84     prefs_filename = itdb_resolve_path (dev_path, p_preferences);
85     g_free (dev_path);
86 
87     return prefs_filename;
88 }
89 
itdb_device_read_raw_timezone(const char * prefs_path,glong offset,gint16 * timezone)90 static gboolean itdb_device_read_raw_timezone (const char *prefs_path,
91                                                glong offset,
92                                                gint16 *timezone)
93 {
94     FILE *f;
95     int result;
96 
97     if (timezone == NULL) {
98         return FALSE;
99     }
100 
101     f = fopen (prefs_path, "r");
102     if (f == NULL) {
103         return FALSE;
104     }
105 
106     result = fseek (f, offset, SEEK_SET);
107     if (result != 0) {
108         fclose (f);
109         return FALSE;
110     }
111 
112     result = fread (timezone, sizeof (*timezone), 1, f);
113     if (result != 1) {
114         fclose (f);
115         return FALSE;
116     }
117 
118     fclose (f);
119 
120     *timezone = GINT16_FROM_LE (*timezone);
121 
122     return TRUE;
123 }
124 
raw_timezone_to_utc_shift_4g(gint16 raw_timezone,gint * utc_shift)125 static gboolean raw_timezone_to_utc_shift_4g (gint16 raw_timezone,
126                                               gint *utc_shift)
127 {
128     const int GMT_OFFSET = 0x19;
129 
130     if (utc_shift == NULL) {
131         return FALSE;
132     }
133 
134     if ((raw_timezone < 0) || (raw_timezone > (2*12) << 1)) {
135         /* invalid timezone */
136         return FALSE;
137     }
138 
139     raw_timezone -= GMT_OFFSET;
140 
141     *utc_shift = (raw_timezone >> 1) * 3600;
142     if (raw_timezone & 1) {
143         /* Adjust for DST */
144         *utc_shift += 3600;
145     }
146 
147     return TRUE;
148 }
149 
raw_timezone_to_utc_shift_5g(gint16 raw_timezone,gint * utc_shift)150 static gboolean raw_timezone_to_utc_shift_5g (gint16 raw_timezone,
151                                               gint *utc_shift)
152 {
153     const int TZ_SHIFT = 8;
154 
155     if (utc_shift == NULL) {
156         return FALSE;
157     }
158     /* The iPod stores the timezone information as a number of minutes
159      * from Tokyo timezone which increases when going eastward (ie
160      * going from Tokyo to LA and then to Europe).
161      * The calculation below shifts the origin so that 0 corresponds
162      * to UTC-12 and the max is 24*60 and corresponds to UTC+12
163      * Finally, we substract 12*60 to that value to get a signed number
164      * giving the timezone relative to UTC.
165      */
166     *utc_shift = raw_timezone*60 - TZ_SHIFT*3600;
167 
168     return TRUE;
169 }
170 
raw_timezone_to_utc_shift_6g(gint16 city_id,gint * utc_shift)171 static gboolean raw_timezone_to_utc_shift_6g (gint16 city_id,
172                                               gint *utc_shift)
173 {
174     const ItdbTimezone *it;
175 
176     for (it = timezones; it->city_name != NULL; it++) {
177 	if (it->city_index == city_id) {
178 	    int offset;
179 	    gboolean unused1;
180 	    int unused2;
181 	    gboolean got_tzinfo;
182 
183 	    got_tzinfo = parse_tzdata (it->tz_name, time (NULL), time (NULL),
184 				       &offset, &unused1, &unused2);
185 	    if (!got_tzinfo) {
186 		return FALSE;
187 	    }
188 	    *utc_shift = offset*60;
189 
190 	    return TRUE;
191 	}
192     }
193 
194     /* unknown city ID */
195     return FALSE;
196 }
197 
get_local_timezone(void)198 static gint get_local_timezone (void)
199 {
200 #ifdef HAVE_STRUCT_TM_TM_GMTOFF
201     /*
202      * http://www.gnu.org/software/libc/manual/html_node/Time-Zone-Functions.html
203      *
204      * Variable: long int timezone
205      *
206      * This contains the difference between UTC and the latest local
207      * standard time, in seconds west of UTC. For example, in the
208      * U.S. Eastern time zone, the value is 5*60*60. Unlike the
209      * tm_gmtoff member of the broken-down time structure, this value is
210      * not adjusted for daylight saving, and its sign is reversed. In
211      * GNU programs it is better to use tm_gmtoff, since it contains the
212      * correct offset even when it is not the latest one.
213      */
214     time_t t = time(NULL);
215     glong seconds_east_utc;
216 #   ifdef HAVE_LOCALTIME_R
217     {
218         struct tm tmb;
219         localtime_r(&t, &tmb);
220         seconds_east_utc = tmb.tm_gmtoff;
221     }
222 #   else /* !HAVE_LOCALTIME_R */
223     {
224         struct tm* tp;
225         tp = localtime(&t);
226         seconds_east_utc = tp->tm_gmtoff;
227     }
228 #   endif /* !HAVE_LOCALTIME_R */
229     return seconds_east_utc; /* mimic the old behaviour when global variable 'timezone' from the 'time.h' header was returned */
230 #elif __CYGWIN__   /* !HAVE_STRUCT_TM_TM_GMTOFF */
231     return (gint) _timezone * -1; /* global variable defined by time.h, see man tzset */
232 #else /* !HAVE_STRUCT_TM_TM_GMTOFF && !__CYGWIN__ */
233     return timezone * -1; /* global variable defined by time.h, see man tzset */
234 #endif
235 }
236 
237 /* This function reads the timezone information from the iPod and sets it in
238  * the Itdb_Device structure. If an error occurs, the function returns silently
239  * and the timezone shift is set to 0
240  */
itdb_device_set_timezone_info(Itdb_Device * device)241 G_GNUC_INTERNAL void itdb_device_set_timezone_info (Itdb_Device *device)
242 {
243     gint16 raw_timezone;
244     gint timezone = 0;
245     gboolean result;
246     struct stat stat_buf;
247     int status;
248     char *prefs_path;
249     guint offset;
250     gboolean (*raw_timezone_converter) (gint16 raw_timezone, gint *utc_shift);
251 
252     device->timezone_shift = get_local_timezone ();
253 
254     prefs_path = get_preferences_path (device);
255 
256     if (!prefs_path) {
257 	return;
258     }
259 
260     status = g_stat (prefs_path, &stat_buf);
261     if (status != 0) {
262 	g_free (prefs_path);
263 	return;
264     }
265     switch (stat_buf.st_size) {
266 	case 2892: /* seen on iPod 4g */
267 	    offset = 0xb10;
268 	    raw_timezone_converter = raw_timezone_to_utc_shift_4g;
269 	    break;
270 	case 2924: /* seen on iPod video */
271 	    offset = 0xb22;
272 	    raw_timezone_converter = raw_timezone_to_utc_shift_5g;
273 	    break;
274 	case 2952: /* seen on nano 3g and iPod classic */
275 	case 2956: /* seen on iPod classic */
276 	case 2960: /* seen on nano 4g */
277 	    offset = 0xb70;
278 	    raw_timezone_converter = raw_timezone_to_utc_shift_6g;
279 	    break;
280 	default:
281 	    /* We don't know how to get the timezone of this ipod model,
282 	     * assume the computer timezone and the ipod timezone match
283 	     */
284 	    g_free (prefs_path);
285 	    return;
286     }
287 
288     result = itdb_device_read_raw_timezone (prefs_path, offset,
289 					    &raw_timezone);
290     g_free (prefs_path);
291     if (!result) {
292 	return;
293     }
294     result = raw_timezone_converter (raw_timezone, &timezone);
295     if (!result) {
296 	return;
297     }
298 
299     if ((timezone < -12*3600) || (timezone > 12 * 3600)) {
300         return;
301     }
302 
303     device->timezone_shift = timezone;
304 }
305 
306 /*
307  * The following function was copied from libgweather/gweather-timezone.c
308  *
309  * Copyright 2008, Red Hat, Inc.
310  *
311  * This library is free software; you can redistribute it and/or
312  * modify it under the terms of the GNU Lesser General Public License
313  * as published by the Free Software Foundation; either version 2.1 of
314  * the License, or (at your option) any later version.
315  *
316  * libgweather uses it for a different purpose than libgpod's, namely
317  * finding if a given world location uses DST (not 'now' but at some point
318  * during the year) and getting the offset.
319  * libgpod only needs to know the offset from UTC, and if start == end when
320  * calling this function, this is exactly what we get!
321  */
322 #define ZONEINFO_DIR "/usr/share/zoneinfo"
323 
324 #define TZ_MAGIC "TZif"
325 #define TZ_HEADER_SIZE 44
326 #define TZ_TIMECNT_OFFSET 32
327 #define TZ_TRANSITIONS_OFFSET 44
328 
329 #define TZ_TTINFO_SIZE 6
330 #define TZ_TTINFO_GMTOFF_OFFSET 0
331 #define TZ_TTINFO_ISDST_OFFSET 4
332 
333 static gboolean
parse_tzdata(const char * tzname,time_t start,time_t end,int * offset,gboolean * has_dst,int * dst_offset)334 parse_tzdata (const char *tzname, time_t start, time_t end,
335 	      int *offset, gboolean *has_dst, int *dst_offset)
336 {
337     char *filename, *contents;
338     gsize length;
339     int timecnt, transitions_size, ttinfo_map_size;
340     int initial_transition = -1, second_transition = -1;
341     gint32 *transitions;
342     char *ttinfo_map, *ttinfos;
343     gint32 initial_offset, second_offset;
344     char initial_isdst, second_isdst;
345     int i;
346 
347     filename = g_build_filename (ZONEINFO_DIR, tzname, NULL);
348     if (!g_file_get_contents (filename, &contents, &length, NULL)) {
349 	g_free (filename);
350 	return FALSE;
351     }
352     g_free (filename);
353 
354     if (length < TZ_HEADER_SIZE ||
355 	strncmp (contents, TZ_MAGIC, strlen (TZ_MAGIC)) != 0) {
356 	g_free (contents);
357 	return FALSE;
358     }
359 
360     timecnt = GUINT32_FROM_BE (*(guint32 *)(contents + TZ_TIMECNT_OFFSET));
361     transitions = (void *)(contents + TZ_TRANSITIONS_OFFSET);
362     transitions_size = timecnt * sizeof (*transitions);
363     ttinfo_map = (void *)(contents + TZ_TRANSITIONS_OFFSET + transitions_size);
364     ttinfo_map_size = timecnt;
365     ttinfos = (void *)(ttinfo_map + ttinfo_map_size);
366 
367     /* @transitions is an array of @timecnt time_t values. We need to
368      * find the transition into the current offset, which is the last
369      * transition before @start. If the following transition is before
370      * @end, then note that one too, since it presumably means we're
371      * doing DST.
372      */
373     for (i = 1; i < timecnt && initial_transition == -1; i++) {
374 	if (GINT32_FROM_BE (transitions[i]) > start) {
375 	    initial_transition = ttinfo_map[i - 1];
376 	    if (GINT32_FROM_BE (transitions[i]) < end)
377 		second_transition = ttinfo_map[i];
378 	}
379     }
380     if (initial_transition == -1) {
381 	if (timecnt)
382 	    initial_transition = ttinfo_map[timecnt - 1];
383 	else
384 	    initial_transition = 0;
385     }
386 
387     /* Copy the data out of the corresponding ttinfo structs */
388     initial_offset = *(gint32 *)(ttinfos +
389 				 initial_transition * TZ_TTINFO_SIZE +
390 				 TZ_TTINFO_GMTOFF_OFFSET);
391     initial_offset = GINT32_FROM_BE (initial_offset);
392     initial_isdst = *(ttinfos +
393 		      initial_transition * TZ_TTINFO_SIZE +
394 		      TZ_TTINFO_ISDST_OFFSET);
395 
396     if (second_transition != -1) {
397 	second_offset = *(gint32 *)(ttinfos +
398 				    second_transition * TZ_TTINFO_SIZE +
399 				    TZ_TTINFO_GMTOFF_OFFSET);
400 	second_offset = GINT32_FROM_BE (second_offset);
401 	second_isdst = *(ttinfos +
402 			 second_transition * TZ_TTINFO_SIZE +
403 			 TZ_TTINFO_ISDST_OFFSET);
404 
405 	*has_dst = (initial_isdst != second_isdst);
406     } else
407 	*has_dst = FALSE;
408 
409     if (!*has_dst)
410 	*offset = initial_offset / 60;
411     else {
412 	if (initial_isdst) {
413 	    *offset = second_offset / 60;
414 	    *dst_offset = initial_offset / 60;
415 	} else {
416 	    *offset = initial_offset / 60;
417 	    *dst_offset = second_offset / 60;
418 	}
419     }
420 
421     g_free (contents);
422     return TRUE;
423 }
424