• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //
2 //  Copyright (c) 2009-2011 Artyom Beilis (Tonkikh)
3 //
4 //  Distributed under the Boost Software License, Version 1.0. (See
5 //  accompanying file LICENSE_1_0.txt or copy at
6 //  http://www.boost.org/LICENSE_1_0.txt)
7 //
8 #define BOOST_LOCALE_SOURCE
9 #include "time_zone.hpp"
10 
11 //
12 // Bug - when ICU tries to find a file that is equivalent to /etc/localtime it finds /usr/share/zoneinfo/localtime
13 // that is just a symbolic link to /etc/localtime.
14 //
15 // It started in 4.0 and was fixed in version 4.6, also the fix was backported to the 4.4 branch so it should be
16 // available from 4.4.3... So we test if the workaround is required
17 //
18 // It is also relevant only for Linux, BSD and Apple (as I see in ICU code)
19 //
20 
21 #if U_ICU_VERSION_MAJOR_NUM == 4 && (U_ICU_VERSION_MINOR_NUM * 100 + U_ICU_VERSION_PATCHLEVEL_NUM) <= 402
22 # if defined(__linux) || defined(__FreeBSD__) || defined(__APPLE__)
23 #   define BOOST_LOCALE_WORKAROUND_ICU_BUG
24 # endif
25 #endif
26 
27 #ifdef BOOST_LOCALE_WORKAROUND_ICU_BUG
28 #include <dirent.h>
29 #include <sys/types.h>
30 #include <sys/stat.h>
31 #include <unistd.h>
32 #include <fstream>
33 #include <pthread.h>
34 #include <string.h>
35 #include <memory>
36 #endif
37 
38 #include <boost/locale/hold_ptr.hpp>
39 
40 namespace boost {
41     namespace locale {
42         namespace impl_icu {
43 
44             #ifndef BOOST_LOCALE_WORKAROUND_ICU_BUG
45 
46             // This is normal behavior
47 
get_time_zone(std::string const & time_zone)48             icu::TimeZone *get_time_zone(std::string const &time_zone)
49             {
50 
51                 if(time_zone.empty()) {
52                     return icu::TimeZone::createDefault();
53                 }
54                 else {
55                     return icu::TimeZone::createTimeZone(time_zone.c_str());
56                 }
57             }
58 
59             #else
60 
61             //
62             // This is a workaround for an ICU timezone detection bug.
63             // It is \b very ICU specific and should not be used
64             // in general. It is also designed to work only on
65             // specific patforms: Linux, BSD and Apple, where this bug may actually
66             // occur
67             //
68             namespace {
69 
70                 // Under BSD, Linux and Mac OS X dirent has normal size
71                 // so no issues with readdir_r
72 
73                 class directory {
74                 public:
75                     directory(char const *name) : d(0),read_result(0)
76                     {
77                         d=opendir(name);
78                         if(!d)
79                             return;
80                     }
81                     ~directory()
82                     {
83                         if(d)
84                             closedir(d);
85                     }
86                     bool is_open()
87                     {
88                         return d;
89                     }
90                     char const *next()
91                     {
92                         if(d && readdir_r(d,&de,&read_result)==0 && read_result!=0)
93                             return de.d_name;
94                         return 0;
95                     }
96                 private:
97                     DIR *d;
98                     struct dirent de;
99                     struct dirent *read_result;
100                 };
101 
102                 bool files_equal(std::string const &left,std::string const &right)
103                 {
104                     char l[256],r[256];
105                     std::ifstream ls(left.c_str());
106                     if(!ls)
107                         return false;
108                     std::ifstream rs(right.c_str());
109                     if(!rs)
110                         return false;
111                     do {
112                         ls.read(l,sizeof(l));
113                         rs.read(r,sizeof(r));
114                         size_t n;
115                         if((n=ls.gcount())!=size_t(rs.gcount()))
116                             return false;
117                         if(memcmp(l,r,n)!=0)
118                             return false;
119                     }while(!ls.eof() || !rs.eof());
120                     if(bool(ls.eof())!=bool(rs.eof()))
121                         return false;
122                     return true;
123                 }
124 
125                 std::string find_file_in(std::string const &ref,size_t size,std::string const &dir)
126                 {
127                     directory d(dir.c_str());
128                     if(!d.is_open())
129                         return std::string();
130 
131                     char const *name=0;
132                     while((name=d.next())!=0) {
133                         std::string file_name = name;
134                         if( file_name == "."
135                             || file_name ==".."
136                             || file_name=="posixrules"
137                             || file_name=="localtime")
138                         {
139                             continue;
140                         }
141                         struct stat st;
142                         std::string path = dir+"/"+file_name;
143                         if(stat(path.c_str(),&st)==0) {
144                             if(S_ISDIR(st.st_mode)) {
145                                 std::string res = find_file_in(ref,size,path);
146                                 if(!res.empty())
147                                     return file_name + "/" + res;
148                             }
149                             else {
150                                 if(size_t(st.st_size) == size && files_equal(path,ref)) {
151                                     return file_name;
152                                 }
153                             }
154                         }
155                     }
156                     return std::string();
157                 }
158 
159                 // This actually emulates ICU's search
160                 // algorithm... just it ignores localtime
161                 std::string detect_correct_time_zone()
162                 {
163 
164                     char const *tz_dir = "/usr/share/zoneinfo";
165                     char const *tz_file = "/etc/localtime";
166 
167                     struct stat st;
168                     if(::stat(tz_file,&st)!=0)
169                         return std::string();
170                     size_t size = st.st_size;
171                     std::string r = find_file_in(tz_file,size,tz_dir);
172                     if(r.empty())
173                         return r;
174                     if(r.compare(0,6,"posix/")==0 || r.compare(0,6,"right/",6)==0)
175                         return r.substr(6);
176                     return r;
177                 }
178 
179 
180                 //
181                 // Using pthread as:
182                 // - This bug is relevant for only Linux, BSD, Mac OS X and
183                 //  pthreads are native threading API
184                 // - The dependency on boost.thread may be removed when using
185                 //   more recent ICU versions (so TLS would not be needed)
186                 //
187                 // This the dependency on Boost.Thread is eliminated
188                 //
189 
190                 pthread_once_t init_tz = PTHREAD_ONCE_INIT;
191                 std::string default_time_zone_name;
192 
193                 extern "C" {
194                     static void init_tz_proc()
195                     {
196                         try {
197                             default_time_zone_name = detect_correct_time_zone();
198                         }
199                         catch(...){}
200                     }
201                 }
202 
203                 std::string get_time_zone_name()
204                 {
205                     pthread_once(&init_tz,init_tz_proc);
206                     return default_time_zone_name;
207                 }
208 
209 
210             } // namespace
211 
212             icu::TimeZone *get_time_zone(std::string const &time_zone)
213             {
214 
215                 if(!time_zone.empty()) {
216                     return icu::TimeZone::createTimeZone(time_zone.c_str());
217                 }
218                 hold_ptr<icu::TimeZone> tz(icu::TimeZone::createDefault());
219                 icu::UnicodeString id;
220                 tz->getID(id);
221                 // Check if there is a bug?
222                 if(id != icu::UnicodeString("localtime"))
223                     return tz.release();
224                 // Now let's deal with the bug and run the fixed
225                 // search loop as that of ICU
226                 std::string real_id = get_time_zone_name();
227                 if(real_id.empty()) {
228                     // if we failed fallback to ICU's time zone
229                     return tz.release();
230                 }
231                 return icu::TimeZone::createTimeZone(real_id.c_str());
232             }
233             #endif // bug workaround
234 
235         }
236     }
237 }
238 
239 // vim: tabstop=4 expandtab shiftwidth=4 softtabstop=4
240