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