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