├── CREDITS ├── ip2country.def ├── makedb ├── Makefile └── makedb.c ├── config.m4 ├── .gitignore ├── ip2country.ini ├── php_ip2country.h ├── db.h ├── ip2country.php ├── MaxMind-LICENSE.txt ├── README.markdown └── ip2country.c /CREDITS: -------------------------------------------------------------------------------- 1 | Erik Dubbelboer 2 | -------------------------------------------------------------------------------- /ip2country.def: -------------------------------------------------------------------------------- 1 | mixed ip2country(mixed ip [, bool full = false]) 2 | string code2country(string code) 3 | -------------------------------------------------------------------------------- /makedb/Makefile: -------------------------------------------------------------------------------- 1 | 2 | makedb: makedb.c ../db.h 3 | gcc -o makedb makedb.c 4 | 5 | all: makedb 6 | 7 | clean: 8 | rm makedb 9 | 10 | -------------------------------------------------------------------------------- /config.m4: -------------------------------------------------------------------------------- 1 | dnl $Id$ 2 | dnl config.m4 for extension ip2country 3 | 4 | PHP_ARG_ENABLE(ip2country, whether to enable ip2country support, 5 | [ --enable-ip2country Enable ip2country support]) 6 | 7 | if test "$PHP_IP2COUNTRY" != "no"; then 8 | CFLAGS="$CFLAGS -O3" 9 | PHP_NEW_EXTENSION(ip2country, ip2country.c, $ext_shared) 10 | fi 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .deps 2 | .libs 3 | Makefile 4 | Makefile.fragments 5 | Makefile.global 6 | Makefile.objects 7 | acinclude.m4 8 | aclocal.m4 9 | autom4te.cache 10 | build 11 | *.in 12 | config.guess 13 | config.h 14 | config.log 15 | config.nice 16 | config.status 17 | config.sub 18 | configure 19 | install-sh 20 | ip2country.la 21 | ip2country.lo 22 | libtool 23 | ltmain.sh 24 | missing 25 | mkinstalldirs 26 | modules 27 | run-tests.php 28 | makedb/GeoIP* 29 | makedb/makedb 30 | -------------------------------------------------------------------------------- /ip2country.ini: -------------------------------------------------------------------------------- 1 | ; load the extension 2 | ; 3 | ; *** CHANGE HERE 4 | ; Change this to the correct path for your installation. Leaving this as it is will work on most systems. 5 | ; *** CHANGE HERE 6 | extension = ip2country.so 7 | 8 | ; point to the pre-processed db file. 9 | ; default: /var/www/geoip.db 10 | ; 11 | ; *** CHANGE HERE - change this to point to your db file - CHANGE HERE *** 12 | ip2country.db = /var/www/geoip.db 13 | 14 | ; enable (1) or disable (0) keeping stats. 15 | ; stats will only require a bit of shared memory. 16 | ; keeping stats doesn't really slow things down (on average only cpu 2 instructions more per lookup). 17 | ; default: 1 18 | ;ip2country.stat = 0 19 | 20 | -------------------------------------------------------------------------------- /php_ip2country.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef PHP_IP2COUNTRY_H 3 | #define PHP_IP2COUNTRY_H 4 | 5 | extern zend_module_entry ip2country_module_entry; 6 | #define phpext_ip2country_ptr &ip2country_module_entry 7 | 8 | #ifdef PHP_WIN32 9 | #define PHP_IP2COUNTRY_API __declspec(dllexport) 10 | #else 11 | #define PHP_IP2COUNTRY_API 12 | #endif 13 | 14 | #ifdef ZTS 15 | #include "TSRM.h" 16 | #endif 17 | 18 | /* init and shutdown functions */ 19 | PHP_MINIT_FUNCTION(ip2country); 20 | PHP_MSHUTDOWN_FUNCTION(ip2country); 21 | PHP_RINIT_FUNCTION(ip2country); 22 | PHP_RSHUTDOWN_FUNCTION(ip2country); 23 | PHP_MINFO_FUNCTION(ip2country); 24 | 25 | /* exported functions that can be used inside php */ 26 | PHP_FUNCTION(ip2country); 27 | PHP_FUNCTION(code2country); 28 | PHP_FUNCTION(ip2country_stat); 29 | 30 | 31 | #ifdef ZTS 32 | #define IP2COUNTRY_G(v) TSRMG(ip2country_globals_id, zend_ip2country_globals *, v) 33 | #else 34 | #define IP2COUNTRY_G(v) (ip2country_globals.v) 35 | #endif 36 | 37 | #endif /* PHP_IP2COUNTRY_H */ 38 | 39 | -------------------------------------------------------------------------------- /db.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _db_h_ 3 | #define _db_h_ 4 | 5 | 6 | 7 | #ifdef __cplusplus 8 | extern "C" { 9 | #endif /* __cplusplus */ 10 | 11 | 12 | 13 | /* flags used while traversing the tree */ 14 | #define done_left (1<<0) 15 | #define done_this (1<<1) 16 | #define done_right (1<<2) 17 | 18 | 19 | 20 | 21 | typedef struct country_s { 22 | char code[4]; 23 | char name[60]; 24 | } country_t; 25 | 26 | 27 | /* this struct is only used to build the list */ 28 | typedef struct country_list_s { 29 | country_t data; 30 | 31 | struct country_list_s* next; 32 | unsigned int id; 33 | } country_list_t; 34 | 35 | 36 | 37 | 38 | typedef struct range_s { 39 | /* numbers of the childs in the tree */ 40 | unsigned int left; 41 | unsigned int right; 42 | 43 | unsigned int start; 44 | unsigned int end; 45 | 46 | unsigned short country; 47 | } range_t; 48 | 49 | 50 | /* this version is only used to build the tree */ 51 | typedef struct range_tree_s { 52 | struct range_tree_s* left; 53 | struct range_tree_s* right; 54 | 55 | unsigned int start; 56 | unsigned int end; 57 | country_list_t* country; 58 | 59 | int depth; 60 | struct range_tree_s* parent; 61 | unsigned int id; 62 | } range_tree_t; 63 | 64 | 65 | 66 | 67 | #ifdef __cplusplus 68 | } 69 | #endif /* __cplusplus */ 70 | 71 | 72 | 73 | 74 | #endif /* _db_h_ */ 75 | 76 | -------------------------------------------------------------------------------- /ip2country.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | ip2country 6 | 7 | 8 | Statistics"; 14 | 15 | /* ip2country_stat will return NULL when statistics are disabled */ 16 | if (!is_array($stat)) { 17 | echo '

statistics are disabled in the ip2country.ini file (setting name is ip2country.stat).

'; 18 | } else { 19 | /* convert the last misses from numers to ip address strings */ 20 | foreach ($stat['lastmisses'] as &$ip) { 21 | $ip = long2ip($ip); 22 | } 23 | unset($ip); /* always unset the iterator after an & loop */ 24 | 25 | $total = $stat['hits'] + $stat['misses']; 26 | 27 | /* no division by zero kk tnx */ 28 | if ($total > 0) { 29 | $stat['misspercentage'] = number_format($stat['misses'] / $total * 100, 2).'%'; 30 | } 31 | 32 | echo '
';
33 |     print_r($stat);
34 |     echo '
'; 35 | } 36 | 37 | 38 | $ip = $_SERVER['REMOTE_ADDR']; 39 | if (isset($_GET['ip'])) { 40 | $ip = $_GET['ip']; 41 | } 42 | 43 | echo '

Looking up ',$ip,'

'; 44 | 45 | echo '
';
46 |   var_dump(ip2country($ip, true));
47 |   echo '
'; 48 | 49 | ?> 50 |
51 |

52 | 53 |

54 |

55 | 56 |

57 |
58 | the ip2country extension is not loaded.

'; 61 | } 62 | ?> 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /MaxMind-LICENSE.txt: -------------------------------------------------------------------------------- 1 | OPEN DATA LICENSE (GeoLite Country and GeoLite City databases) 2 | 3 | Copyright (c) 2007 MaxMind LLC. All Rights Reserved. 4 | 5 | All advertising materials and documentation mentioning features or use of 6 | this database must display the following acknowledgment: 7 | "This product includes GeoLite data created by MaxMind, available from 8 | http://maxmind.com/" 9 | 10 | Redistribution and use with or without modification, are permitted provided 11 | that the following conditions are met: 12 | 1. Redistributions must retain the above copyright notice, this list of 13 | conditions and the following disclaimer in the documentation and/or other 14 | materials provided with the distribution. 15 | 2. All advertising materials and documentation mentioning features or use of 16 | this database must display the following acknowledgement: 17 | "This product includes GeoLite data created by MaxMind, available from 18 | http://maxmind.com/" 19 | 3. "MaxMind" may not be used to endorse or promote products derived from this 20 | database without specific prior written permission. 21 | 22 | THIS DATABASE IS PROVIDED BY MAXMIND.COM ``AS IS'' AND ANY 23 | EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 24 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 25 | DISCLAIMED. IN NO EVENT SHALL MAXMIND.COM BE LIABLE FOR ANY 26 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 27 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 28 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 29 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 31 | DATABASE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 | 33 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | 2 | ip2country 3 | ========== 4 | 5 | ip2country module for PHP. 6 | 7 | You can send comments, patches, questions [here on github](https://github.com/ErikDubbelboer/ip2country/issues) or to erik@mininova.org. 8 | 9 | 10 | Installing/Configuring 11 | ---------------------- 12 | 13 | To build the database, run: 14 | 15 |
16 | cd makedb
17 | make
18 | wget http://www.maxmind.com/download/geoip/database/GeoIPCountryCSV.zip
19 | unzip GeoIPCountryCSV.zip
20 | ./makedb GeoIPCountryWhois.csv /var/www/geoip.db
21 | 
22 | 23 | Where `/var/www/geoip.db` is the location where you want to store your database file. 24 | 25 | 26 | To install the module, run: 27 | 28 |
29 | phpize
30 | ./configure
31 | make
32 | sudo make install
33 | 
34 | 35 | You will need to edit the `ip2country.ini` file to point to your extension and database file. You can find your extension directory using: 36 | 37 |
38 | php-config --extension-dir
39 | 
40 | 41 | Now copy `ip2country.ini` to your php install's configuration directory. On Gentoo this will usually be `/etc/php/cgi-php5/ext-active/`. On Ubuntu this will be `/etc/php5/conf.d` 42 | 43 |
44 | cp ip2country.ini /etc/php5/conf.d/
45 | 
46 | 47 | After restarting php the extension should be working. 48 | 49 | 50 | Usage 51 | ----- 52 | 53 | The module introduces 3 new functions to php. 54 | 55 |
56 | mixed ip2country(mixed ip [, bool fullname])
57 | string code2country(string code)
58 | array ip2country_stat()
59 | 
60 | 61 | You can use the ip2country function to get the country code for a specific ip address. If fullname is set to true an array containing both the code and name is returned. 62 | 63 |
64 | $code = ip2country($_SERVER['REMOTE_ADDR']);
65 | 
66 | 67 | The code2country function can be used to look up the full name of a country. 68 | 69 |
70 | $name = code2country($code);
71 | 
72 | 73 | -------------------------------------------------------------------------------- /ip2country.c: -------------------------------------------------------------------------------- 1 | 2 | #ifdef HAVE_CONFIG_H 3 | #include "config.h" 4 | #endif 5 | 6 | #include "php.h" 7 | #include "php_ini.h" 8 | #include "ext/standard/info.h" 9 | #include "php_ip2country.h" 10 | 11 | #include 12 | #include 13 | 14 | #include "db.h" 15 | 16 | 17 | typedef struct ip2country_stat_s { 18 | unsigned long hits; 19 | unsigned long misses; 20 | unsigned long lastmisses[10]; 21 | } ip2country_stat_t; 22 | 23 | 24 | /* exported functions */ 25 | zend_function_entry ip2country_functions[] = { 26 | PHP_FE(ip2country, NULL) 27 | PHP_FE(code2country, NULL) 28 | PHP_FE(ip2country_stat, NULL) 29 | {NULL, NULL, NULL} 30 | }; 31 | 32 | zend_module_entry ip2country_module_entry = { 33 | #if ZEND_MODULE_API_NO >= 20010901 34 | STANDARD_MODULE_HEADER, 35 | #endif 36 | "ip2country", 37 | ip2country_functions, 38 | PHP_MINIT(ip2country), 39 | PHP_MSHUTDOWN(ip2country), 40 | NULL, 41 | NULL, 42 | PHP_MINFO(ip2country), 43 | #if ZEND_MODULE_API_NO >= 20010901 44 | "1.0", 45 | #endif 46 | STANDARD_MODULE_PROPERTIES 47 | }; 48 | 49 | #ifdef COMPILE_DL_IP2COUNTRY 50 | ZEND_GET_MODULE(ip2country) 51 | #endif 52 | 53 | 54 | 55 | /* array of all ranges */ 56 | static range_t* ranges = NULL; 57 | static range_t* root = NULL; /* the root of the tree (last node of the ranges array) */ 58 | 59 | /* array of all countries */ 60 | static country_t* countries = NULL; 61 | static unsigned int num_countries = 0; 62 | 63 | 64 | 65 | /* shared memory id, and pointer to the stat structure that's placed in it */ 66 | static int shmid; 67 | static ip2country_stat_t* stats = NULL; 68 | 69 | 70 | /* php.ini entries 71 | * PHP_INI_SYSTEM means they can only be changed in the .ini file 72 | */ 73 | PHP_INI_BEGIN() 74 | PHP_INI_ENTRY("ip2country.db" , "/var/www/geoip.db", PHP_INI_SYSTEM, NULL) 75 | PHP_INI_ENTRY("ip2country.stat", "1" , PHP_INI_SYSTEM, NULL) 76 | PHP_INI_END() 77 | 78 | 79 | /* simple O(n) lookup base on the country code. */ 80 | static country_t* country_get(char* code) { 81 | unsigned int i; 82 | 83 | for (i = 0; i < num_countries; ++i) { 84 | if (strcmp(countries[i].code, code) == 0) { 85 | return &countries[i]; 86 | } 87 | } 88 | 89 | return NULL; 90 | } 91 | 92 | 93 | 94 | /* return the left node if we didn't visit it already */ 95 | static range_t* tree_left(range_t* node) { 96 | if (node->left == UINT_MAX ) { 97 | return NULL; 98 | } else { 99 | return &ranges[node->left]; 100 | } 101 | } 102 | 103 | 104 | /* return the right node if we didn't visit it already */ 105 | static range_t* tree_right(range_t* node) { 106 | if (node->right == UINT_MAX) { 107 | return NULL; 108 | } else { 109 | return &ranges[node->right]; 110 | } 111 | } 112 | 113 | 114 | /* find the range in which val belongs */ 115 | static country_t* tree_find(unsigned long val) { 116 | range_t* node = root; 117 | range_t* newn; 118 | 119 | for (;;) { 120 | // most common case first 121 | if (node->start > val) { 122 | // val = 2 123 | // 3 <-- we are here 124 | // / \ 125 | // 2 4 126 | 127 | newn = tree_left(node); 128 | } else { 129 | // val = 2 130 | // 1 <-- we are here 131 | // / \ 132 | // 0 3 133 | 134 | if ((node->start <= val) && 135 | (node->end >= val)) { 136 | 137 | return &countries[node->country]; 138 | } 139 | 140 | newn = tree_right(node); 141 | } 142 | 143 | if (newn == NULL) { 144 | return NULL; 145 | } 146 | 147 | node = newn; 148 | } 149 | } 150 | 151 | 152 | /* this function will be called once when php is started */ 153 | PHP_MINIT_FUNCTION(ip2country) { 154 | REGISTER_INI_ENTRIES(); 155 | 156 | FILE* fp = fopen(INI_STR("ip2country.db"), "r"); 157 | 158 | if (fp != NULL) { 159 | // first 4 bytes is the number of countries 160 | fread(&num_countries, sizeof(unsigned int), 1, fp); 161 | 162 | countries = (country_t*)pemalloc(sizeof(country_t) * num_countries, 1); 163 | 164 | fread(countries, sizeof(country_t) * num_countries, 1, fp); 165 | 166 | 167 | // next 4 bytes is the number of ranges 168 | unsigned int num_ranges = 0; 169 | fread(&num_ranges, sizeof(unsigned int), 1, fp); 170 | 171 | ranges = (range_t*)pemalloc(sizeof(range_t) * num_ranges, 1); 172 | 173 | fread(ranges, sizeof(range_t) * num_ranges, 1, fp); 174 | 175 | // the root is the last range in the array cause of the reverse order they are written in 176 | root = &ranges[num_ranges - 1]; 177 | 178 | fclose(fp); 179 | } 180 | 181 | 182 | if (INI_BOOL("ip2country.stat")) { 183 | shmid = shmget(IPC_PRIVATE, sizeof(ip2country_stat_t), 0777|IPC_CREAT); 184 | 185 | if (shmid != -1) { 186 | stats = (ip2country_stat_t*)shmat(shmid, 0, 0); 187 | memset(stats, 0, sizeof(ip2country_stat_t)); 188 | } 189 | } 190 | 191 | return SUCCESS; 192 | } 193 | 194 | 195 | /* this function will get called when php is shutdown */ 196 | PHP_MSHUTDOWN_FUNCTION(ip2country) { 197 | pefree(countries, 1); 198 | pefree(ranges, 1); 199 | 200 | if (stats != NULL) { 201 | shmdt(stats); 202 | shmctl(shmid, IPC_RMID, 0); 203 | } 204 | 205 | UNREGISTER_INI_ENTRIES(); 206 | 207 | return SUCCESS; 208 | } 209 | 210 | 211 | PHP_RINIT_FUNCTION(ip2country) { 212 | return SUCCESS; 213 | } 214 | 215 | 216 | PHP_RSHUTDOWN_FUNCTION(ip2country) { 217 | return SUCCESS; 218 | } 219 | 220 | 221 | PHP_MINFO_FUNCTION(ip2country) { 222 | php_info_print_table_start(); 223 | php_info_print_table_header(2, "ip2country support", "enabled"); 224 | php_info_print_table_end(); 225 | 226 | DISPLAY_INI_ENTRIES(); 227 | } 228 | 229 | 230 | /* this function can be called from php code. 231 | * return a country code and optionally name from an ip. 232 | * the ip can either be a string or a number. 233 | */ 234 | PHP_FUNCTION(ip2country) { 235 | zval* zip; 236 | zend_bool full = 0; 237 | country_t* country; 238 | unsigned long ip; 239 | 240 | // this check will also print if arguments are missing or wrong 241 | if (zend_parse_parameters_ex(0, ZEND_NUM_ARGS() TSRMLS_CC, "z|b", &zip, &full) == FAILURE) { 242 | RETURN_NULL(); 243 | } 244 | 245 | if (Z_TYPE_P(zip) == IS_LONG) { 246 | ip = Z_LVAL_P(zip); 247 | } else if (Z_TYPE_P(zip) == IS_STRING) { 248 | ip = ntohl(inet_addr(Z_STRVAL_P(zip))); // convert the string to a number 249 | } else { 250 | convert_to_long(zip); 251 | ip = Z_LVAL_P(zip); 252 | } 253 | 254 | if (ranges == NULL) { 255 | php_error(E_WARNING, "%s: No db loaded (could not read %s)", get_active_function_name(TSRMLS_C), INI_STR("ip2country.db")); 256 | RETURN_NULL(); 257 | } 258 | 259 | country = tree_find(ip); 260 | 261 | if (country == NULL) { 262 | if (stats != NULL) { 263 | stats->misses++; 264 | stats->lastmisses[stats->misses % 10] = ip; 265 | } 266 | 267 | RETURN_NULL(); 268 | } 269 | 270 | if (stats != NULL) { 271 | stats->hits++; 272 | } 273 | 274 | if (full) { 275 | array_init(return_value); 276 | 277 | add_assoc_string(return_value, "code", country->code, 1); 278 | add_assoc_string(return_value, "name", country->name, 1); 279 | } else { 280 | RETURN_STRING(country->code, 1); 281 | } 282 | } 283 | 284 | 285 | /* this function can be called from php code. 286 | * return a country name from a code. 287 | */ 288 | PHP_FUNCTION(code2country) { 289 | char* code; 290 | int code_len; 291 | country_t* country; 292 | 293 | if (zend_parse_parameters_ex(0, ZEND_NUM_ARGS() TSRMLS_CC, "s", &code, &code_len) == FAILURE) { 294 | RETURN_NULL(); 295 | } 296 | 297 | country = country_get(code); 298 | 299 | if (country == NULL) { 300 | RETURN_NULL(); 301 | } else { 302 | RETURN_STRING(country->name, 1); 303 | } 304 | } 305 | 306 | 307 | /* this function can be called from php code. 308 | * return some statistics if enabled. 309 | */ 310 | PHP_FUNCTION(ip2country_stat) { 311 | if (stats == NULL) { 312 | RETURN_NULL(); 313 | } 314 | 315 | array_init(return_value); 316 | 317 | add_assoc_long(return_value, "hits", stats->hits); 318 | add_assoc_long(return_value, "misses", stats->misses); 319 | 320 | zval *lastmisses; 321 | ALLOC_INIT_ZVAL(lastmisses); 322 | array_init(lastmisses); 323 | 324 | int i; 325 | for (i = 0; i < 10; i++) { 326 | add_index_long(lastmisses, i, stats->lastmisses[i]); 327 | } 328 | 329 | add_assoc_zval(return_value, "lastmisses", lastmisses); 330 | } 331 | 332 | -------------------------------------------------------------------------------- /makedb/makedb.c: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../db.h" 8 | 9 | 10 | #ifndef max 11 | #define max(a, b) (((a) > (b)) ? (a) : (b)) 12 | #endif 13 | 14 | 15 | static range_tree_t* root = NULL; 16 | static unsigned int next_id = 0; 17 | 18 | static country_list_t* countries = NULL; 19 | static unsigned int countries_s = 0; 20 | 21 | 22 | 23 | static country_list_t* country_add(char* code, char* name) { 24 | if (countries == NULL) { 25 | countries = (country_list_t*)malloc(sizeof(country_list_t)); 26 | 27 | strcpy(countries->data.code, code); 28 | strcpy(countries->data.name, name); 29 | 30 | countries->next = NULL; 31 | countries->id = countries_s++; 32 | 33 | return countries; 34 | } 35 | 36 | 37 | country_list_t* country = countries; 38 | 39 | for (;;) { 40 | if (strcmp(country->data.code, code) == 0) { 41 | return country; 42 | } 43 | 44 | if (country->next == NULL) { 45 | break; 46 | } else { 47 | country = country->next; 48 | } 49 | } 50 | 51 | country->next = (country_list_t*)malloc(sizeof(country_list_t)); 52 | country = country->next; 53 | 54 | strcpy(country->data.code, code); 55 | strcpy(country->data.name, name); 56 | 57 | country->next = NULL; 58 | country->id = countries_s++; 59 | 60 | return country; 61 | } 62 | 63 | 64 | 65 | static range_tree_t* tree_left(unsigned long* flags, range_tree_t* node) { 66 | if (((*flags) & done_left) || 67 | (node->left == NULL )) { 68 | return NULL; 69 | } else { 70 | (*flags) = 0; 71 | 72 | return node->left; 73 | } 74 | } 75 | 76 | 77 | static range_tree_t* tree_right(unsigned long* flags, range_tree_t* node) { 78 | if (((*flags) & done_right) || 79 | (node->right == NULL )) { 80 | return NULL; 81 | } else { 82 | (*flags) = 0; 83 | 84 | return node->right; 85 | } 86 | } 87 | 88 | 89 | static range_tree_t* tree_up(unsigned long* flags, range_tree_t* node) { 90 | if (node->parent == NULL) { 91 | return NULL; 92 | } else { 93 | if (node->parent->left == node) { 94 | (*flags) = done_left | done_this; 95 | } else { 96 | (*flags) = done_left | done_this | done_right; 97 | } 98 | 99 | return node->parent; 100 | } 101 | } 102 | 103 | 104 | static range_tree_t* tree_deepest(range_tree_t* node) { 105 | unsigned int ldepth = (node->left == NULL) ? 0 : node->left->depth; 106 | unsigned int rdepth = (node->right == NULL) ? 0 : node->right->depth; 107 | 108 | if (ldepth > rdepth) { 109 | return node->left; 110 | } else { 111 | return node->right; 112 | } 113 | } 114 | 115 | 116 | /* return the root of a new balanced tree */ 117 | static range_tree_t* tree_rotate(range_tree_t* a) { 118 | /* no need to check anything because the tree is unbalanced so none of these will be NULL */ 119 | range_tree_t* b = tree_deepest(a); 120 | range_tree_t* c = tree_deepest(b); 121 | 122 | range_tree_t* d1, * d2, * d3, * d4; 123 | range_tree_t *r; 124 | 125 | /* there are 4 different unbalanced trees */ 126 | if (a->left == b) { 127 | if (b->left == c) { 128 | // a b 129 | // / \ / \ 130 | // b 4 c a 131 | // / \ / \ / \ 132 | // c 3 1 2 3 4 133 | // / \ 134 | // 1 2 135 | d1 = c->left; 136 | d2 = c->right; 137 | d3 = b->right; 138 | d4 = a->right; 139 | 140 | r = b; 141 | r->left = c; 142 | r->right = a; 143 | } else { 144 | // a c 145 | // / \ / \ 146 | // b 4 b a 147 | // / \ / \ / \ 148 | // 1 c 1 2 3 4 149 | // / \ 150 | // 2 3 151 | d1 = b->left; 152 | d2 = c->left; 153 | d3 = c->right; 154 | d4 = a->right; 155 | 156 | r = c; 157 | r->left = b; 158 | r->right = a; 159 | } 160 | } else { 161 | if (b->left == c) { 162 | // a c 163 | // / \ / \ 164 | // 1 b a b 165 | // / \ / \ / \ 166 | // c 4 1 2 3 4 167 | // / \ 168 | // 2 3 169 | d1 = a->left; 170 | d2 = c->left; 171 | d3 = c->right; 172 | d4 = b->right; 173 | 174 | r = c; 175 | r->left = a; 176 | r->right = b; 177 | } else { 178 | // a b 179 | // / \ / \ 180 | // 1 b a c 181 | // / \ / \ / \ 182 | // 2 c 1 2 3 4 183 | // / \ 184 | // 3 4 185 | d1 = a->left; 186 | d2 = b->left; 187 | d3 = c->left; 188 | d4 = c->right; 189 | 190 | r = b; 191 | r->left = a; 192 | r->right = c; 193 | } 194 | } 195 | 196 | r->left->parent = r; 197 | r->right->parent = r; 198 | 199 | r->left->left = d1; 200 | r->left->right = d2; 201 | r->right->left = d3; 202 | r->right->right = d4; 203 | 204 | if (d1 != NULL) d1->parent = r->left; 205 | if (d2 != NULL) d2->parent = r->left; 206 | if (d3 != NULL) d3->parent = r->right; 207 | if (d4 != NULL) d4->parent = r->right; 208 | 209 | r->left->depth = max((d1 == NULL) ? 1 : d1->depth + 1, 210 | (d2 == NULL) ? 1 : d2->depth + 1); 211 | r->right->depth = max((d3 == NULL) ? 1 : d3->depth + 1, 212 | (d4 == NULL) ? 1 : d4->depth + 1); 213 | 214 | r->depth = max(r->left->depth, r->right->depth) + 1; 215 | 216 | return r; 217 | } 218 | 219 | 220 | 221 | int tree_insert(range_tree_t* node) { 222 | if (root == NULL) { // no root 223 | node->parent = NULL; 224 | node->left = node->right = NULL; 225 | node->depth = 1; 226 | root = node; 227 | } else { 228 | int depth; 229 | range_tree_t* curr = root; 230 | 231 | /* walk down the tree to search for a place to insert the node */ 232 | for (;;) { 233 | if (node->start == curr->start) { 234 | return -1; 235 | } 236 | 237 | if (node->start < curr->start) { 238 | if (curr->left == NULL) { 239 | curr->left = node; 240 | curr->left->parent = curr; 241 | break; 242 | } else { 243 | curr = curr->left; 244 | } 245 | } else { 246 | if (curr->right == NULL) { 247 | curr->right = node; 248 | curr->right->parent = curr; 249 | break; 250 | } else { 251 | curr = curr->right; 252 | } 253 | } 254 | } 255 | 256 | node->left = node->right = NULL; 257 | node->depth = 1; 258 | 259 | for (node = node->parent, depth = 2; node != NULL; node = node->parent, depth++) { 260 | int ldepth, rdepth; 261 | 262 | // if the depth of this node doesn't change, 263 | // the depths of the nodes above won't change either. 264 | if (node->depth >= depth) { 265 | break; 266 | } 267 | 268 | ldepth = (node->left == NULL) ? 0 : node->left->depth; 269 | rdepth = (node->right == NULL) ? 0 : node->right->depth; 270 | 271 | if (abs(ldepth - rdepth) > 1) { 272 | range_tree_t* p; 273 | 274 | if (node->parent == NULL) { // root 275 | root = tree_rotate(node); 276 | root->parent = NULL; 277 | } else if (node->parent->left == node) { 278 | p = node->parent; 279 | p->left = tree_rotate(node); 280 | p->left->parent = p; 281 | } else { 282 | p = node->parent; 283 | p->right = tree_rotate(node); 284 | p->right->parent = p; 285 | } 286 | break; 287 | } else { 288 | node->depth = depth; 289 | } 290 | } 291 | } 292 | 293 | return 0; 294 | } 295 | 296 | 297 | 298 | // write all ranges who's depth equals de depth given as argument 299 | void write_with_depth(unsigned int depth, FILE* fp) { 300 | range_tree_t* node = root; 301 | unsigned long flags = 0; 302 | range_t node_tmp; 303 | 304 | unsigned int cdepth = 0; 305 | 306 | for (;;) { 307 | range_tree_t* curr = node; 308 | 309 | if (!(flags & done_this) && (cdepth == depth)) { 310 | memset(&node_tmp, 0, sizeof(range_t)); 311 | 312 | node_tmp.left = (curr->left != NULL) ? curr->left->id : UINT_MAX; 313 | node_tmp.right = (curr->right != NULL) ? curr->right->id : UINT_MAX; 314 | node_tmp.start = curr->start; 315 | node_tmp.end = curr->end; 316 | node_tmp.country = curr->country->id; 317 | 318 | fwrite(&node_tmp, sizeof(range_t), 1, fp); 319 | 320 | curr->id = next_id++; 321 | } 322 | 323 | node = tree_left(&flags, curr); 324 | 325 | if (node == NULL) { 326 | node = tree_right(&flags, curr); 327 | 328 | if (node == NULL) { 329 | node = tree_up(&flags, curr); 330 | 331 | if (node == NULL) { 332 | return; 333 | } else { 334 | cdepth--; 335 | } 336 | } else { 337 | cdepth++; 338 | } 339 | } else { 340 | cdepth++; 341 | } 342 | } 343 | } 344 | 345 | 346 | 347 | 348 | int main(int argc, char* argv[]) { 349 | if (argc != 3) { 350 | printf("usage: makedb \n"); 351 | return 0; 352 | } 353 | 354 | 355 | FILE* fp = fopen(argv[1], "r"); 356 | 357 | if (fp == NULL) { 358 | printf("error: could not open '%s' for reading\n", argv[1]); 359 | return -1; 360 | } 361 | 362 | 363 | unsigned int start = 0; 364 | unsigned int end = 0; 365 | char code[4]; 366 | char name[60]; 367 | char dummy[512]; 368 | range_tree_t* node; 369 | 370 | memset(code, 0, sizeof(code)); 371 | memset(name, 0, sizeof(name)); 372 | 373 | unsigned int rows = 0; 374 | 375 | for (;;) { 376 | int n = fscanf(fp, "%[^,],%[^,],\"%u\",\"%u\",\"%2c\",\"%59[^\"]\"", &dummy, &dummy, &start, &end, code, name); 377 | 378 | if (n != 6) { 379 | if (fgets(dummy, 512, fp) == NULL) { 380 | break; 381 | } 382 | 383 | continue; 384 | } 385 | 386 | node = (range_tree_t*)malloc(sizeof(range_tree_t)); 387 | node->start = start; 388 | node->end = end; 389 | node->country = country_add(code, name); 390 | 391 | if (tree_insert(node) == -1) { 392 | return -1; 393 | } 394 | 395 | rows++; 396 | } 397 | 398 | fclose(fp); 399 | 400 | 401 | printf("%u ranges read from the input file\n", rows); 402 | 403 | if (rows == 0) { 404 | printf("nothing to output.\n"); 405 | return 0; 406 | } 407 | 408 | 409 | fp = fopen(argv[2], "wb"); 410 | 411 | if (fp == NULL) { 412 | printf("error: could not open '%s' for writing\n", argv[2]); 413 | return -2; 414 | } 415 | 416 | 417 | 418 | fwrite(&countries_s, sizeof(unsigned int), 1, fp); 419 | 420 | country_list_t* country = countries; 421 | 422 | while (country != NULL) { 423 | fwrite(&country->data, sizeof(country_t), 1, fp); 424 | 425 | country = country->next; 426 | } 427 | 428 | 429 | fwrite(&rows, sizeof(unsigned int), 1, fp); 430 | 431 | int depth; 432 | for (depth = root->depth; depth >= 0; --depth) { 433 | write_with_depth(depth, fp); 434 | } 435 | 436 | 437 | fclose(fp); 438 | 439 | printf("done.\n"); 440 | 441 | return 0; 442 | } 443 | 444 | --------------------------------------------------------------------------------