1 /* System timezone handling
2 *
3 * Copyright (C) 2008 Novell, Inc.
4 *
5 * Authors: Vincent Untz <vuntz@gnome.org>
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program 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
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA.
20 *
21 * Some code is based on previous code in clock-location.c and on code from
22 * tz.c (shipped with version <= 2.22.0). Those files were under the same
23 * license, with those authors and copyrights:
24 *
25 * clock-location.c:
26 * ================
27 * No header, but most of the work was done (AFAIK) by
28 * Federico Mena Quintero <federico@novell.com>
29 * Matthias Clasen <mclasen@redhat.com>
30 *
31 * tz.c:
32 * ====
33 * Copyright (C) 2000-2001 Ximian, Inc.
34 * Copyright (C) 2004 Sun Microsystems, Inc.
35 *
36 * Authors: Hans Petter Jansson <hpj@ximian.com>
37 * additional functions by Erwann Chenede <erwann.chenede@sun.com>
38 * reworked by Vincent Untz <vuntz@gnome.org>
39 *
40 * Largely based on Michael Fulbright's work on Anaconda.
41 */
42
43 /* FIXME: it'd be nice to filter out the timezones that we might get when
44 * parsing config files that are not in zone.tab. Note that it's also wrong
45 * in some cases: eg, in tzdata2008b, Asia/Calcutta got renamed to
46 * Asia/Kolkata and the old name is not in zone.tab. */
47
48 #include <string.h>
49 #include <unistd.h>
50
51 #include <glib.h>
52 #include <glib/gstdio.h>
53 #include <gio/gio.h>
54
55 #include "system-timezone.h"
56
57 /* Files that we look at */
58 #define ETC_TIMEZONE "/etc/timezone"
59 #define ETC_TIMEZONE_MAJ "/etc/TIMEZONE"
60 #define ETC_RC_CONF "/etc/rc.conf"
61 #define ETC_SYSCONFIG_CLOCK "/etc/sysconfig/clock"
62 #define ETC_CONF_D_CLOCK "/etc/conf.d/clock"
63 #define ETC_LOCALTIME "/etc/localtime"
64
65 /* The first 4 characters in a timezone file, from tzfile.h */
66 #define TZ_MAGIC "TZif"
67
68 static GObject *systz_singleton = NULL;
69
70 G_DEFINE_TYPE (SystemTimezone, system_timezone, G_TYPE_OBJECT)
71
72 typedef struct {
73 char *tz;
74 char *env_tz;
75 } SystemTimezonePrivate;
76
77 static GObject *system_timezone_constructor (GType type,
78 guint n_construct_properties,
79 GObjectConstructParam *construct_properties);
80 static void system_timezone_finalize (GObject *obj);
81
82 #define PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), SYSTEM_TIMEZONE_TYPE, SystemTimezonePrivate))
83
84 SystemTimezone *
system_timezone_new(void)85 system_timezone_new (void)
86 {
87 return g_object_new (SYSTEM_TIMEZONE_TYPE, NULL);
88 }
89
90 const char *
system_timezone_get(SystemTimezone * systz)91 system_timezone_get (SystemTimezone *systz)
92 {
93 SystemTimezonePrivate *priv;
94
95 g_return_val_if_fail (IS_SYSTEM_TIMEZONE (systz), NULL);
96
97 priv = PRIVATE (systz);
98 return priv->tz;
99 }
100
101 const char *
system_timezone_get_env(SystemTimezone * systz)102 system_timezone_get_env (SystemTimezone *systz)
103 {
104 SystemTimezonePrivate *priv;
105
106 g_return_val_if_fail (IS_SYSTEM_TIMEZONE (systz), NULL);
107
108 priv = PRIVATE (systz);
109 return priv->env_tz;
110 }
111
112 static void
system_timezone_class_init(SystemTimezoneClass * class)113 system_timezone_class_init (SystemTimezoneClass *class)
114 {
115 GObjectClass *g_obj_class = G_OBJECT_CLASS (class);
116
117 g_obj_class->constructor = system_timezone_constructor;
118 g_obj_class->finalize = system_timezone_finalize;
119
120 g_type_class_add_private (class, sizeof (SystemTimezonePrivate));
121 }
122
123 static void
system_timezone_init(SystemTimezone * systz)124 system_timezone_init (SystemTimezone *systz)
125 {
126 SystemTimezonePrivate *priv = PRIVATE (systz);
127
128 priv->tz = NULL;
129 priv->env_tz = NULL;
130 }
131
132 static GObject *
system_timezone_constructor(GType type,guint n_construct_properties,GObjectConstructParam * construct_properties)133 system_timezone_constructor (GType type,
134 guint n_construct_properties,
135 GObjectConstructParam *construct_properties)
136 {
137 GObject *obj;
138 SystemTimezonePrivate *priv;
139
140 /* This is a singleton, we don't need to have it per-applet */
141 if (systz_singleton)
142 return g_object_ref (systz_singleton);
143
144 obj = G_OBJECT_CLASS (system_timezone_parent_class)->constructor (
145 type,
146 n_construct_properties,
147 construct_properties);
148
149 priv = PRIVATE (obj);
150
151 priv->tz = system_timezone_find ();
152
153 priv->env_tz = g_strdup (g_getenv ("TZ"));
154
155 systz_singleton = obj;
156
157 return systz_singleton;
158 }
159
160 static void
system_timezone_finalize(GObject * obj)161 system_timezone_finalize (GObject *obj)
162 {
163 SystemTimezonePrivate *priv = PRIVATE (obj);
164
165 if (priv->tz) {
166 g_free (priv->tz);
167 priv->tz = NULL;
168 }
169
170 if (priv->env_tz) {
171 g_free (priv->env_tz);
172 priv->env_tz = NULL;
173 }
174
175 G_OBJECT_CLASS (system_timezone_parent_class)->finalize (obj);
176
177 g_assert (obj == systz_singleton);
178
179 systz_singleton = NULL;
180 }
181
182 /*
183 * Code to deal with the system timezone on all distros.
184 * There's no dependency on the SystemTimezone GObject here.
185 *
186 * Here's what we know:
187 *
188 * + /etc/localtime contains the binary data of the timezone.
189 * It can be a symlink to the actual data file, a hard link to the data
190 * file, or just a copy. So we can determine the timezone with this
191 * (reading the symlink, comparing inodes, or comparing content).
192 *
193 * + However, most distributions also have the timezone setting
194 * configured somewhere else. This might be better to read it from there.
195 *
196 * Debian/Ubuntu/Gentoo (new): content of /etc/timezone
197 * Fedora/Mandriva: the ZONE key in /etc/sysconfig/clock
198 * openSUSE: the TIMEZONE key in /etc/sysconfig/clock
199 * Solaris/OpenSolaris: the TZ key in /etc/TIMEZONE
200 * Arch Linux: the TIMEZONE key in /etc/rc.conf
201 * Gentoo (old): the ZONE key in /etc/conf.d/clock
202 *
203 * FIXME: reading the system-tools-backends, it seems there's this too:
204 * Solaris: the TZ key in /etc/default/init
205 * /etc/TIMEZONE seems to be a link to /etc/default/init
206 *
207 * First, some functions to handle those system config files.
208 *
209 */
210
211 /* This works for Debian and derivatives (including Ubuntu), and new Gentoo */
212 static char *
system_timezone_read_etc_timezone(void)213 system_timezone_read_etc_timezone (void)
214 {
215 FILE *etc_timezone;
216 GString *reading;
217 int c;
218
219 etc_timezone = g_fopen (ETC_TIMEZONE, "r");
220 if (!etc_timezone)
221 return NULL;
222
223 reading = g_string_new ("");
224
225 c = fgetc (etc_timezone);
226 /* only get the first line, we'll validate the value later */
227 while (c != EOF && !g_ascii_isspace (c)) {
228 reading = g_string_append_c (reading, c);
229 c = fgetc (etc_timezone);
230 }
231
232 fclose (etc_timezone);
233
234 if (reading->str && reading->str[0] != '\0')
235 return g_string_free (reading, FALSE);
236 else
237 g_string_free (reading, TRUE);
238
239 return NULL;
240 }
241
242 static gboolean
system_timezone_write_etc_timezone(const char * tz,GError ** error)243 system_timezone_write_etc_timezone (const char *tz,
244 GError **error)
245 {
246 char *content;
247 GError *our_error;
248 gboolean retval;
249
250 if (!g_file_test (ETC_TIMEZONE, G_FILE_TEST_IS_REGULAR))
251 return TRUE;
252
253 content = g_strdup_printf ("%s\n", tz);
254
255 our_error = NULL;
256 retval = g_file_set_contents (ETC_TIMEZONE, content, -1, &our_error);
257 g_free (content);
258
259 if (!retval) {
260 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
261 SYSTEM_TIMEZONE_ERROR_GENERAL,
262 ETC_TIMEZONE" cannot be overwritten: %s",
263 our_error->message);
264 g_error_free (our_error);
265 }
266
267 return retval;
268 }
269
270
271 /* Read a file that looks like a key-file (but there's no need for groups)
272 * and get the last value for a specific key */
273 static char *
system_timezone_read_key_file(const char * filename,const char * key)274 system_timezone_read_key_file (const char *filename,
275 const char *key)
276 {
277 GIOChannel *channel;
278 char *key_eq;
279 char *line;
280 char *retval;
281
282 if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
283 return NULL;
284
285 channel = g_io_channel_new_file (filename, "r", NULL);
286 if (!channel)
287 return NULL;
288
289 key_eq = g_strdup_printf ("%s=", key);
290 retval = NULL;
291
292 while (g_io_channel_read_line (channel, &line, NULL,
293 NULL, NULL) == G_IO_STATUS_NORMAL) {
294 if (g_str_has_prefix (line, key_eq)) {
295 char *value;
296 int len;
297
298 value = line + strlen (key_eq);
299 g_strstrip (value);
300
301 len = strlen (value);
302
303 if (value[0] == '\"') {
304 if (value[len - 1] == '\"') {
305 if (retval)
306 g_free (retval);
307
308 retval = g_strndup (value + 1,
309 len - 2);
310 }
311 } else {
312 if (retval)
313 g_free (retval);
314
315 retval = g_strdup (line + strlen (key_eq));
316 }
317
318 g_strstrip (retval);
319 }
320
321 g_free (line);
322 }
323
324 g_free (key_eq);
325 g_io_channel_unref (channel);
326
327 return retval;
328 }
329
330 static gboolean
system_timezone_write_key_file(const char * filename,const char * key,const char * value,GError ** error)331 system_timezone_write_key_file (const char *filename,
332 const char *key,
333 const char *value,
334 GError **error)
335 {
336 GError *our_error;
337 char *content;
338 gsize len;
339 char *key_eq;
340 char **lines;
341 gboolean replaced;
342 gboolean retval;
343 int n;
344
345 if (!g_file_test (filename, G_FILE_TEST_IS_REGULAR))
346 return TRUE;
347
348 our_error = NULL;
349
350 if (!g_file_get_contents (filename, &content, &len, &our_error)) {
351 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
352 SYSTEM_TIMEZONE_ERROR_GENERAL,
353 "%s cannot be read: %s",
354 filename, our_error->message);
355 g_error_free (our_error);
356 return FALSE;
357 }
358
359 lines = g_strsplit (content, "\n", 0);
360 g_free (content);
361
362 key_eq = g_strdup_printf ("%s=", key);
363 replaced = FALSE;
364
365 for (n = 0; lines[n] != NULL; n++) {
366 if (g_str_has_prefix (lines[n], key_eq)) {
367 char *old_value;
368 gboolean use_quotes;
369
370 old_value = lines[n] + strlen (key_eq);
371 g_strstrip (old_value);
372 use_quotes = old_value[0] == '\"';
373
374 g_free (lines[n]);
375
376 if (use_quotes)
377 lines[n] = g_strdup_printf ("%s\"%s\"",
378 key_eq, value);
379 else
380 lines[n] = g_strdup_printf ("%s%s",
381 key_eq, value);
382
383 replaced = TRUE;
384 }
385 }
386
387 g_free (key_eq);
388
389 if (!replaced) {
390 g_strfreev (lines);
391 return TRUE;
392 }
393
394 content = g_strjoinv ("\n", lines);
395 g_strfreev (lines);
396
397 retval = g_file_set_contents (filename, content, -1, &our_error);
398 g_free (content);
399
400 if (!retval) {
401 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
402 SYSTEM_TIMEZONE_ERROR_GENERAL,
403 "%s cannot be overwritten: %s",
404 filename, our_error->message);
405 g_error_free (our_error);
406 }
407
408 return retval;
409 }
410
411 /* This works for Solaris/OpenSolaris */
412 static char *
system_timezone_read_etc_TIMEZONE(void)413 system_timezone_read_etc_TIMEZONE (void)
414 {
415 return system_timezone_read_key_file (ETC_TIMEZONE_MAJ,
416 "TZ");
417 }
418
419 static gboolean
system_timezone_write_etc_TIMEZONE(const char * tz,GError ** error)420 system_timezone_write_etc_TIMEZONE (const char *tz,
421 GError **error)
422 {
423 return system_timezone_write_key_file (ETC_TIMEZONE_MAJ,
424 "TZ", tz, error);
425 }
426
427 /* This works for Fedora and Mandriva */
428 static char *
system_timezone_read_etc_sysconfig_clock(void)429 system_timezone_read_etc_sysconfig_clock (void)
430 {
431 return system_timezone_read_key_file (ETC_SYSCONFIG_CLOCK,
432 "ZONE");
433 }
434
435 static gboolean
system_timezone_write_etc_sysconfig_clock(const char * tz,GError ** error)436 system_timezone_write_etc_sysconfig_clock (const char *tz,
437 GError **error)
438 {
439 return system_timezone_write_key_file (ETC_SYSCONFIG_CLOCK,
440 "ZONE", tz, error);
441 }
442
443 /* This works for openSUSE */
444 static char *
system_timezone_read_etc_sysconfig_clock_alt(void)445 system_timezone_read_etc_sysconfig_clock_alt (void)
446 {
447 return system_timezone_read_key_file (ETC_SYSCONFIG_CLOCK,
448 "TIMEZONE");
449 }
450
451 static gboolean
system_timezone_write_etc_sysconfig_clock_alt(const char * tz,GError ** error)452 system_timezone_write_etc_sysconfig_clock_alt (const char *tz,
453 GError **error)
454 {
455 return system_timezone_write_key_file (ETC_SYSCONFIG_CLOCK,
456 "TIMEZONE", tz, error);
457 }
458
459 /* This works for old Gentoo */
460 static char *
system_timezone_read_etc_conf_d_clock(void)461 system_timezone_read_etc_conf_d_clock (void)
462 {
463 return system_timezone_read_key_file (ETC_CONF_D_CLOCK,
464 "TIMEZONE");
465 }
466
467 static gboolean
system_timezone_write_etc_conf_d_clock(const char * tz,GError ** error)468 system_timezone_write_etc_conf_d_clock (const char *tz,
469 GError **error)
470 {
471 return system_timezone_write_key_file (ETC_CONF_D_CLOCK,
472 "TIMEZONE", tz, error);
473 }
474
475 /* This works for Arch Linux */
476 static char *
system_timezone_read_etc_rc_conf(void)477 system_timezone_read_etc_rc_conf (void)
478 {
479 return system_timezone_read_key_file (ETC_RC_CONF,
480 "TIMEZONE");
481 }
482
483 static gboolean
system_timezone_write_etc_rc_conf(const char * tz,GError ** error)484 system_timezone_write_etc_rc_conf (const char *tz,
485 GError **error)
486 {
487 return system_timezone_write_key_file (ETC_RC_CONF,
488 "TIMEZONE", tz, error);
489 }
490
491 /*
492 *
493 * First, getting the timezone.
494 *
495 */
496
497 static char *
system_timezone_strip_path_if_valid(const char * filename)498 system_timezone_strip_path_if_valid (const char *filename)
499 {
500 int skip;
501
502 if (!filename || !g_str_has_prefix (filename, SYSTEM_ZONEINFODIR"/"))
503 return NULL;
504
505 /* Timezone data files also live under posix/ and right/ for some
506 * reason.
507 * FIXME: make sure accepting those files is valid. I think "posix" is
508 * okay, not sure about "right" */
509 if (g_str_has_prefix (filename, SYSTEM_ZONEINFODIR"/posix/"))
510 skip = strlen (SYSTEM_ZONEINFODIR"/posix/");
511 else if (g_str_has_prefix (filename, SYSTEM_ZONEINFODIR"/right/"))
512 skip = strlen (SYSTEM_ZONEINFODIR"/right/");
513 else
514 skip = strlen (SYSTEM_ZONEINFODIR"/");
515
516 return g_strdup (filename + skip);
517 }
518
519 /* Read the soft symlink from /etc/localtime */
520 static char *
system_timezone_read_etc_localtime_softlink(void)521 system_timezone_read_etc_localtime_softlink (void)
522 {
523 char *file;
524 char *tz;
525
526 if (!g_file_test (ETC_LOCALTIME, G_FILE_TEST_IS_SYMLINK))
527 return NULL;
528
529 file = g_file_read_link (ETC_LOCALTIME, NULL);
530 tz = system_timezone_strip_path_if_valid (file);
531 g_free (file);
532
533 return tz;
534 }
535
536 typedef gboolean (*CompareFiles) (struct stat *a_stat,
537 struct stat *b_stat,
538 const char *a_content,
539 gsize a_content_len,
540 const char *b_filename);
541
542 static char *
recursive_compare(struct stat * localtime_stat,const char * localtime_content,gsize localtime_content_len,char * file,CompareFiles compare_func)543 recursive_compare (struct stat *localtime_stat,
544 const char *localtime_content,
545 gsize localtime_content_len,
546 char *file,
547 CompareFiles compare_func)
548 {
549 struct stat file_stat;
550
551 if (g_stat (file, &file_stat) != 0)
552 return NULL;
553
554 if (S_ISREG (file_stat.st_mode)) {
555 if (compare_func (localtime_stat,
556 &file_stat,
557 localtime_content,
558 localtime_content_len,
559 file))
560 return system_timezone_strip_path_if_valid (file);
561 else
562 return NULL;
563 } else if (S_ISDIR (file_stat.st_mode)) {
564 GDir *dir = NULL;
565 char *ret = NULL;
566 const char *subfile = NULL;
567 char *subpath = NULL;
568
569 dir = g_dir_open (file, 0, NULL);
570 if (dir == NULL)
571 return NULL;
572
573 while ((subfile = g_dir_read_name (dir)) != NULL) {
574 subpath = g_build_filename (file, subfile, NULL);
575
576 ret = recursive_compare (localtime_stat,
577 localtime_content,
578 localtime_content_len,
579 subpath,
580 compare_func);
581
582 g_free (subpath);
583
584 if (ret != NULL)
585 break;
586 }
587
588 g_dir_close (dir);
589
590 return ret;
591 }
592
593 return NULL;
594 }
595
596
597 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)598 files_are_identical_inode (struct stat *a_stat,
599 struct stat *b_stat,
600 const char *a_content,
601 gsize a_content_len,
602 const char *b_filename)
603 {
604 return (a_stat->st_ino == b_stat->st_ino);
605 }
606
607
608 /* Determine if /etc/localtime is a hard link to some file, by looking at
609 * the inodes */
610 static char *
system_timezone_read_etc_localtime_hardlink(void)611 system_timezone_read_etc_localtime_hardlink (void)
612 {
613 struct stat stat_localtime;
614
615 if (g_stat (ETC_LOCALTIME, &stat_localtime) != 0)
616 return NULL;
617
618 if (!S_ISREG (stat_localtime.st_mode))
619 return NULL;
620
621 return recursive_compare (&stat_localtime,
622 NULL,
623 0,
624 SYSTEM_ZONEINFODIR,
625 files_are_identical_inode);
626 }
627
628 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)629 files_are_identical_content (struct stat *a_stat,
630 struct stat *b_stat,
631 const char *a_content,
632 gsize a_content_len,
633 const char *b_filename)
634 {
635 char *b_content = NULL;
636 gsize b_content_len = -1;
637 int cmp;
638
639 if (a_stat->st_size != b_stat->st_size)
640 return FALSE;
641
642 if (!g_file_get_contents (b_filename,
643 &b_content, &b_content_len, NULL))
644 return FALSE;
645
646 if (a_content_len != b_content_len) {
647 g_free (b_content);
648 return FALSE;
649 }
650
651 cmp = memcmp (a_content, b_content, a_content_len);
652 g_free (b_content);
653
654 return (cmp == 0);
655 }
656
657 /* Determine if /etc/localtime is a copy of a timezone file */
658 static char *
system_timezone_read_etc_localtime_content(void)659 system_timezone_read_etc_localtime_content (void)
660 {
661 struct stat stat_localtime;
662 char *localtime_content = NULL;
663 gsize localtime_content_len = -1;
664 char *retval;
665
666 if (g_stat (ETC_LOCALTIME, &stat_localtime) != 0)
667 return NULL;
668
669 if (!S_ISREG (stat_localtime.st_mode))
670 return NULL;
671
672 if (!g_file_get_contents (ETC_LOCALTIME,
673 &localtime_content,
674 &localtime_content_len,
675 NULL))
676 return NULL;
677
678 retval = recursive_compare (&stat_localtime,
679 localtime_content,
680 localtime_content_len,
681 SYSTEM_ZONEINFODIR,
682 files_are_identical_content);
683
684 g_free (localtime_content);
685
686 return retval;
687 }
688
689 typedef char * (*GetSystemTimezone) (void);
690 /* The order of the functions here define the priority of the methods used
691 * to find the timezone. First method has higher priority. */
692 static GetSystemTimezone get_system_timezone_methods[] = {
693 /* cheap and "more correct" than data from a config file */
694 system_timezone_read_etc_localtime_softlink,
695 /* reading various config files */
696 system_timezone_read_etc_timezone,
697 system_timezone_read_etc_sysconfig_clock,
698 system_timezone_read_etc_sysconfig_clock_alt,
699 system_timezone_read_etc_TIMEZONE,
700 system_timezone_read_etc_rc_conf,
701 /* reading deprecated config files */
702 system_timezone_read_etc_conf_d_clock,
703 /* reading /etc/timezone directly. Expensive since we have to stat
704 * many files */
705 system_timezone_read_etc_localtime_hardlink,
706 system_timezone_read_etc_localtime_content,
707 NULL
708 };
709
710 static gboolean
system_timezone_is_valid(const char * tz)711 system_timezone_is_valid (const char *tz)
712 {
713 const char *c;
714
715 if (!tz)
716 return FALSE;
717
718 for (c = tz; *c != '\0'; c++) {
719 if (!(g_ascii_isalnum (*c) ||
720 *c == '/' || *c == '-' || *c == '_'))
721 return FALSE;
722 }
723
724 return TRUE;
725 }
726
727 char *
system_timezone_find(void)728 system_timezone_find (void)
729 {
730 char *tz;
731 int i;
732
733 for (i = 0; get_system_timezone_methods[i] != NULL; i++) {
734 tz = get_system_timezone_methods[i] ();
735
736 if (system_timezone_is_valid (tz))
737 return tz;
738
739 g_free (tz);
740 }
741
742 return g_strdup ("UTC");
743 }
744
745 /*
746 *
747 * Now, setting the timezone.
748 *
749 */
750
751 static gboolean
system_timezone_is_zone_file_valid(const char * zone_file,GError ** error)752 system_timezone_is_zone_file_valid (const char *zone_file,
753 GError **error)
754 {
755 GError *our_error;
756 GIOChannel *channel;
757 char buffer[strlen (TZ_MAGIC)];
758 gsize read;
759
760 /* First, check the zone_file is properly rooted */
761 if (!g_str_has_prefix (zone_file, SYSTEM_ZONEINFODIR"/")) {
762 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
763 SYSTEM_TIMEZONE_ERROR_INVALID_TIMEZONE_FILE,
764 "Timezone file needs to be under "SYSTEM_ZONEINFODIR);
765 return FALSE;
766 }
767
768 /* Second, check it's a regular file that exists */
769 if (!g_file_test (zone_file, G_FILE_TEST_IS_REGULAR)) {
770 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
771 SYSTEM_TIMEZONE_ERROR_INVALID_TIMEZONE_FILE,
772 "No such timezone file %s", zone_file);
773 return FALSE;
774 }
775
776 /* Third, check that it's a tzfile (see tzfile(5)). The file has a 4
777 * bytes header which is TZ_MAGIC.
778 *
779 * TODO: is there glibc API for this? */
780 our_error = NULL;
781 channel = g_io_channel_new_file (zone_file, "r", &our_error);
782 if (!our_error)
783 g_io_channel_read_chars (channel,
784 buffer, strlen (TZ_MAGIC),
785 &read, &our_error);
786 if (channel)
787 g_io_channel_unref (channel);
788
789 if (our_error) {
790 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
791 SYSTEM_TIMEZONE_ERROR_INVALID_TIMEZONE_FILE,
792 "Timezone file %s cannot be read: %s",
793 zone_file, our_error->message);
794 g_error_free (our_error);
795 return FALSE;
796 }
797
798 if (read != strlen (TZ_MAGIC) || strncmp (buffer, TZ_MAGIC, strlen (TZ_MAGIC)) != 0) {
799 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
800 SYSTEM_TIMEZONE_ERROR_INVALID_TIMEZONE_FILE,
801 "%s is not a timezone file",
802 zone_file);
803 return FALSE;
804 }
805
806 return TRUE;
807 }
808
809 static gboolean
system_timezone_set_etc_timezone(const char * zone_file,GError ** error)810 system_timezone_set_etc_timezone (const char *zone_file,
811 GError **error)
812 {
813 GError *our_error;
814 char *content;
815 gsize len;
816
817 if (!system_timezone_is_zone_file_valid (zone_file, error))
818 return FALSE;
819
820 /* If /etc/localtime is a symlink, write a symlink */
821 if (g_file_test (ETC_LOCALTIME, G_FILE_TEST_IS_SYMLINK)) {
822 if (g_unlink (ETC_LOCALTIME) == 0 &&
823 symlink (zone_file, ETC_LOCALTIME) == 0)
824 return TRUE;
825
826 /* If we couldn't symlink the file, we'll just fallback on
827 * copying it */
828 }
829
830 /* Else copy the file to /etc/localtime. We explicitly avoid doing
831 * hard links since they break with different partitions */
832 our_error = NULL;
833 if (!g_file_get_contents (zone_file, &content, &len, &our_error)) {
834 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
835 SYSTEM_TIMEZONE_ERROR_GENERAL,
836 "Timezone file %s cannot be read: %s",
837 zone_file, our_error->message);
838 g_error_free (our_error);
839 return FALSE;
840 }
841
842 if (!g_file_set_contents (ETC_LOCALTIME, content, len, &our_error)) {
843 g_set_error (error, SYSTEM_TIMEZONE_ERROR,
844 SYSTEM_TIMEZONE_ERROR_GENERAL,
845 ETC_LOCALTIME" cannot be overwritten: %s",
846 our_error->message);
847 g_error_free (our_error);
848 g_free (content);
849 return FALSE;
850 }
851
852 g_free (content);
853
854 return TRUE;
855 }
856
857 typedef gboolean (*SetSystemTimezone) (const char *tz,
858 GError **error);
859 /* The order here does not matter too much: we'll try to change all files
860 * that already have a timezone configured. It matters in case of error,
861 * since the process will be stopped and the last methods won't be called.
862 * So we use the same order as in get_system_timezone_methods */
863 static SetSystemTimezone set_system_timezone_methods[] = {
864 /* writing various config files if they exist and have the
865 * setting already present */
866 system_timezone_write_etc_timezone,
867 system_timezone_write_etc_sysconfig_clock,
868 system_timezone_write_etc_sysconfig_clock_alt,
869 system_timezone_write_etc_TIMEZONE,
870 system_timezone_write_etc_rc_conf,
871 /* writing deprecated config files if they exist and have the
872 * setting already present */
873 system_timezone_write_etc_conf_d_clock,
874 NULL
875 };
876
877 static gboolean
system_timezone_update_config(const char * tz,GError ** error)878 system_timezone_update_config (const char *tz,
879 GError **error)
880 {
881 int i;
882
883 for (i = 0; set_system_timezone_methods[i] != NULL; i++) {
884 if (!set_system_timezone_methods[i] (tz, error))
885 return FALSE;
886 /* FIXME: maybe continue to change all config files if
887 * possible? */
888 }
889
890 return TRUE;
891 }
892
893 gboolean
system_timezone_set(const char * tz,GError ** error)894 system_timezone_set (const char *tz,
895 GError **error)
896 {
897 char *zone_file;
898 gboolean retval;
899
900 g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
901
902 zone_file = g_build_filename (SYSTEM_ZONEINFODIR, tz, NULL);
903
904 /* FIXME: is it right to return FALSE even when /etc/localtime was
905 * changed but not the config files? */
906 retval = system_timezone_set_etc_timezone (zone_file, error) &&
907 system_timezone_update_config (tz, error);
908
909 g_free (zone_file);
910
911 return retval;
912 }
913
914 GQuark
system_timezone_error_quark(void)915 system_timezone_error_quark (void)
916 {
917 static GQuark ret = 0;
918
919 if (ret == 0) {
920 ret = g_quark_from_static_string ("system-timezone-error");
921 }
922
923 return ret;
924 }
925