├── 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 |
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 |
--------------------------------------------------------------------------------