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