Add version= to node/way/relations
[planetdump.git] / planet.c
1 #ifndef _GNU_SOURCE
2 #define _GNU_SOURCE
3 #endif
4
5 #undef USE_ICONV
6
7 #include <stdio.h>
8 #include <stdlib.h>
9 #include <string.h>
10 #include <stdarg.h>
11 #include <sys/types.h>
12 #include <sys/stat.h>
13 #include <sys/socket.h>
14 #include <sys/un.h>
15 #include <unistd.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <limits.h>
19 #include <time.h>
20 #include <utime.h>
21
22
23 #include <mysql.h>
24 #include <mysqld_error.h>
25 #include <signal.h>
26 #include <stdarg.h>
27 #include <sslopt-vars.h>
28 #include <assert.h>
29
30 #include "keyvals.h"
31
32 #define INDENT "  "
33
34
35 #ifdef USE_ICONV
36 #include <iconv.h>
37 #define ICONV_ERROR ((iconv_t)-1)
38 static iconv_t cd = ICONV_ERROR;
39 #endif
40
41
42 static char **user_list;
43 static unsigned long max_uid;
44
45 /* const char *xmlescape(char *in)
46  *
47  * Character escaping for valid XML output as per http://www.w3.org/TR/REC-xml/
48  *
49  * WARNING: this function uses a static buffer so do not rely on the result
50  * being constant if called more than once
51  */
52 static const char *xmlescape(char *in)
53
54     static char escape_tmp[1024];
55     int len;
56     // Convert from DB charset to UTF8
57     // Note: this assumes that inbuf is C-string compatible, i.e. has no embedded NUL like UTF16!
58     // To fix this we'd need to fix the DB output parameters too
59 #ifdef USE_ICONV 
60     if (cd != ICONV_ERROR) {
61         char iconv_tmp[1024];
62         char *inbuf = in, *outbuf = iconv_tmp;
63         size_t ret;
64         size_t inlen = strlen(inbuf);
65         size_t outlen = sizeof(iconv_tmp);
66         bzero(iconv_tmp, sizeof(iconv_tmp));
67         iconv(cd, NULL, 0, NULL, 0);
68
69         ret = iconv(cd, &inbuf, &inlen, &outbuf, &outlen);
70
71         if (ret == -1) {
72             fprintf(stderr, "failed to convert '%s'\n", in);
73             // Carry on regardless
74         }
75         in = iconv_tmp;
76     }
77 #endif
78
79     len = 0;
80     while(*in) {
81         int left = sizeof(escape_tmp) - len - 1;
82
83         if (left < 7)
84             break;
85
86         if (*in == '&') {
87             strcpy(&escape_tmp[len], "&amp;");
88             len += strlen("&amp;");
89         } else if (*in == '<') {
90             strcpy(&escape_tmp[len], "&lt;");
91             len += strlen("&lt;");
92         } else if (*in == '>') {
93             strcpy(&escape_tmp[len], "&gt;");
94             len += strlen("&lt;");
95         } else if (*in == '"') {
96             strcpy(&escape_tmp[len], "&quot;");
97             len += strlen("&quot;");
98         } else if ((*in >= 0) && (*in < 32)) {
99             escape_tmp[len] = '?';
100             len++;
101         } else {
102             escape_tmp[len] = *in;
103             len++;
104         }
105         
106         in++;
107     }
108     escape_tmp[len] = '\0';
109     return escape_tmp;
110 }
111
112 static void osm_tags(struct keyval *tags)
113 {
114     struct keyval *p;
115
116     while ((p = popItem(tags)) != NULL) {
117         printf(INDENT INDENT "<tag k=\"%s\"", xmlescape(p->key));
118         printf(" v=\"%s\" />\n", xmlescape(p->value));
119         freeItem(p);
120     }
121
122    resetList(tags);
123 }
124
125 static const char *lookup_user(const char *s)
126 {
127     unsigned long int user_id = strtoul(s, NULL, 10);
128     const char *user = (user_id >= 0 && user_id <= max_uid) ? user_list[user_id] : "";
129     return user ? user : "";
130 }
131
132 static void osm_node(int id, long double lat, long double lon, struct keyval *tags, const char *ts, const char *user, int version)
133 {
134     if (listHasData(tags)) {
135         printf(INDENT "<node id=\"%d\" lat=\"%.7Lf\" lon=\"%.7Lf\" timestamp=\"%s\" version=\"%d\"%s>\n", id, lat, lon, ts, version, user);
136         osm_tags(tags);
137         printf(INDENT "</node>\n");
138     } else {
139         printf(INDENT "<node id=\"%d\" lat=\"%.7Lf\" lon=\"%.7Lf\" timestamp=\"%s\" version=\"%d\"%s/>\n", id, lat, lon, ts, version, user);
140     }
141 }
142
143 static void osm_way(int id, struct keyval *nodes, struct keyval *tags, const char *ts, const char *user, int version)
144 {
145     struct keyval *p;
146
147     if (listHasData(tags) || listHasData(nodes)) {
148         printf(INDENT "<way id=\"%d\" timestamp=\"%s\" version=\"%d\"%s>\n", id, ts, version, user);
149         while ((p = popItem(nodes)) != NULL) {
150             printf(INDENT INDENT "<nd ref=\"%s\" />\n", p->value);
151             freeItem(p);
152         }
153         osm_tags(tags);
154         printf(INDENT "</way>\n");
155     } else {
156         printf(INDENT "<way id=\"%d\" timestamp=\"%s\" version=\"%d\"%s/>\n", id, ts, version, user);
157     }
158 }
159
160 static void osm_relation(int id, struct keyval *members, struct keyval *roles, struct keyval *tags, const char *ts, const char *user, int version)
161 {
162     struct keyval *p, *q;
163
164     if (listHasData(tags) || listHasData(members)) {
165         printf(INDENT "<relation id=\"%d\" timestamp=\"%s\" version=\"%d\"%s>\n", id, ts, version, user);
166         while (((p = popItem(members)) != NULL) && ((q = popItem(roles)) != NULL)) {
167             const char *m_type = p->key;
168             const char *m_id   = p->value;
169             const char *m_role = q->value;
170             printf(INDENT INDENT "<member type=\"%s\" ref=\"%s\" role=\"%s\"/>\n", m_type, m_id, m_role);
171             freeItem(p);
172             freeItem(q);
173         }
174         osm_tags(tags);
175         printf(INDENT "</relation>\n");
176     } else {
177         printf(INDENT "<relation id=\"%d\" timestamp=\"%s\" version=\"%d\"%s/>\n", id, ts, version, user);
178     }
179 }
180
181 /* In-place unescape the tag string. The char * passed in will
182    be returned from the function. Unescaping can only ever make
183    the string shorter so this is safe */
184 char *tag_unescape(char *str)
185 {
186    char *p = str;
187    char *w = str;
188    char ch;
189
190    while ( *p ) {
191        ch = *p++;
192        if ( ch == '\\' ) {
193            ch = *p++;
194            switch ( ch ) {
195            case 's': ch = ';'; break;
196            case 'e': ch = '='; break;
197            case '\\': ch = '\\'; break;
198            default:
199                // Drop the \ and allow any other char to pass unchanged
200                break;
201            }
202        }
203        *w++ = ch;
204    }
205    *w = '\0';
206    return str;
207 }
208
209 void read_tags(const char *str, struct keyval *tags)
210 {
211    enum tagState { sKey, sValue, sDone, sEnd} s;
212    char *key, *value;
213    const char *p, *key_start, *value_start;
214
215    if (!str || !*str)
216     return;
217    // key=value;key=value;...
218    // Note: This is a simple algorithm which makes no attempt to reconstruct odd data like:
219    // a=b=c  or
220    // key=value1;value2
221    p = str;
222    key_start = p;
223    s = sKey;
224    value_start = key = value = NULL;
225    while(s != sEnd) {
226        switch(s) {
227            case sKey:
228                if (!*p) {
229                    s = sEnd;
230                    break;
231                }
232                if (*p == '=') {
233                    key = strndup(key_start, p - key_start);
234                    s = sValue;
235                    value_start = p+1;
236                }
237                p++;
238                break;
239
240            case sValue:
241                if (!*p || *p == ';') {
242                    value = strndup(value_start, p - value_start);
243                    s = sDone;
244                    key_start = p+1;
245                }
246                if (*p) p++;
247                break;
248
249            case sDone:
250                addItem(tags, tag_unescape(key), tag_unescape(value), 0);
251                free(key);
252                free(value);
253                s = sKey;
254                break;
255
256            case sEnd: /* Never reached, but keeps compiler happy */
257                break;
258        }
259    }
260 }
261
262 const char *reformDate(const char *str)
263 {
264     static char out[64], prev[64]; // Not thread safe
265
266     time_t tmp;
267     struct tm tm;
268
269     // Re-use the previous answer if we asked to convert the same timestamp twice
270     // This accelerates bulk uploaded data where sequential features often have the same timestamp
271     if (!strncmp(prev, str, sizeof(prev)))
272         return out;
273     else
274         strncpy(prev, str, sizeof(prev));
275
276     // 2007-05-20 13:51:35
277     bzero(&tm, sizeof(tm));
278     int n = sscanf(str, "%d-%d-%d %d:%d:%d",
279                    &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec);
280
281     if (n !=6)
282         printf("failed to parse date string, got(%d): %s\n", n, str);
283
284     tm.tm_year -= 1900;
285     tm.tm_mon  -= 1;
286     tm.tm_isdst = -1;
287
288     // Rails stores the timestamps in the DB using UK localtime (ugh), convert to UTC
289     tmp = mktime(&tm);
290     gmtime_r(&tmp, &tm);
291
292     //2007-07-10T11:32:32Z
293     snprintf(out, sizeof(out), "%d-%02d-%02dT%02d:%02d:%02dZ",
294              tm.tm_year+1900, tm.tm_mon+1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec);
295
296     return out;
297 }
298
299
300 #define TAG_CACHE (10000)
301
302 struct tCache {
303     int id;
304     struct keyval tags;
305 };
306
307 static struct tCache cache[TAG_CACHE+1];
308 static MYSQL_STMT *tags_stmt;
309 static int cache_off;
310
311 void tags_init(MYSQL *mysql, const char *table)
312 {
313     int i;
314     char query[255];
315     MYSQL_RES *prepare_meta_result;
316     tags_stmt = mysql_stmt_init(mysql);
317     assert(tags_stmt);
318
319     snprintf(query, sizeof(query), "SELECT id, k, v FROM %s WHERE id >= ? ORDER BY id LIMIT 10000", table); // LIMIT == TAG_CACHE
320
321     if (mysql_stmt_prepare(tags_stmt, query, strlen(query))) {
322         fprintf(stderr,"Cannot setup prepared query for %s: %s\n", table, mysql_error(mysql));
323         exit(1);
324     }
325     assert(mysql_stmt_param_count(tags_stmt) == 1);
326     prepare_meta_result = mysql_stmt_result_metadata(tags_stmt);
327     assert(prepare_meta_result);
328     assert(mysql_num_fields(prepare_meta_result) == 3);
329     mysql_free_result(prepare_meta_result);
330
331     for (i=0; i< TAG_CACHE; i++) {
332         initList(&cache[i].tags);
333         cache[i].id = 0;
334     }
335     cache_off = 0;
336 }
337
338 void tags_exit(void)
339 {
340     int i;
341     mysql_stmt_close(tags_stmt);
342     tags_stmt = NULL;
343     for (i=0; i< TAG_CACHE; i++)
344         resetList(&cache[i].tags);
345 }
346
347 void refill_tags(MYSQL *mysql, const int id)
348 {
349     unsigned long length[3];
350     my_bool       is_null[3];
351     my_bool       error[3];
352     MYSQL_BIND tags_bind_param[1];
353     MYSQL_BIND tags_bind_res[3];
354     char key[256], value[256];
355     int i, row_id, last_id, cache_slot;
356
357     for (i=0; i<TAG_CACHE; i++) {
358         if (!cache[i].id)
359             break;
360         resetList(&cache[i].tags);
361         cache[i].id = 0;
362     }
363
364     memset(tags_bind_param, 0, sizeof(tags_bind_param));
365     tags_bind_param[0].buffer_type= MYSQL_TYPE_LONG;
366     tags_bind_param[0].buffer= (char *)&id;
367     tags_bind_param[0].is_null= 0;
368     tags_bind_param[0].length= 0;
369
370     if (mysql_stmt_bind_param(tags_stmt, tags_bind_param)) {
371         fprintf(stderr, " mysql_stmt_bind_param() failed\n");
372         fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
373         exit(0);
374     }
375
376     if (mysql_stmt_execute(tags_stmt))
377     {
378         fprintf(stderr, " mysql_stmt_execute(), 1 failed\n");
379         fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
380         exit(0);
381     }
382
383     memset(tags_bind_res, 0, sizeof(tags_bind_res));
384
385     tags_bind_res[0].buffer_type= MYSQL_TYPE_LONG;
386     tags_bind_res[0].buffer= (char *)&row_id;
387     tags_bind_res[0].is_null= &is_null[0];
388     tags_bind_res[0].length= &length[0];
389     tags_bind_res[0].error= &error[0];
390
391     tags_bind_res[1].buffer_type= MYSQL_TYPE_VAR_STRING;
392     tags_bind_res[1].buffer_length= sizeof(key);
393     tags_bind_res[1].buffer= key;
394     tags_bind_res[1].is_null= &is_null[0];
395     tags_bind_res[1].length= &length[0];
396     tags_bind_res[1].error= &error[0];
397
398     tags_bind_res[2].buffer_type= MYSQL_TYPE_VAR_STRING;
399     tags_bind_res[2].buffer_length= sizeof(value);
400     tags_bind_res[2].buffer= value;
401     tags_bind_res[2].is_null= &is_null[1];
402     tags_bind_res[2].length= &length[1];
403     tags_bind_res[2].error= &error[1];
404
405
406     if (mysql_stmt_bind_result(tags_stmt, tags_bind_res))
407     {
408         fprintf(stderr, " mysql_stmt_bind_result() failed\n");
409         fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
410         exit(0);
411     }
412
413     if (mysql_stmt_store_result(tags_stmt))
414     {
415         fprintf(stderr, " mysql_stmt_store_result() failed\n");
416         fprintf(stderr, " %s\n", mysql_stmt_error(tags_stmt));
417         exit(0);
418     }
419
420     cache_slot = 0;
421     last_id = 0;
422     while (!mysql_stmt_fetch(tags_stmt)) {
423         if (last_id != row_id) {
424             if (last_id)
425                cache_slot++;
426             cache[cache_slot].id = row_id;
427             last_id = row_id;
428         }
429         addItem(&cache[cache_slot].tags, key, value, 0);
430     }
431     // We need to clean out final slot since it may be truncated, unless
432     // we only got a single slot filled then we hit the end of the table
433     // which we assume _is_ complete
434     if (cache_slot) {
435         resetList(&cache[cache_slot].tags);
436         cache[cache_slot].id = 0;
437     } else {
438         // This algorithm can not cope with > TAG_CACHE on a single way
439         assert(countList(&cache[cache_slot].tags) != TAG_CACHE);
440     }
441 }
442
443 struct keyval *get_generic_tags(MYSQL *mysql, const int id)
444 {
445     while (1) {
446         if (!cache[cache_off].id) {
447             if (cache_off == 1)
448                 return NULL; // No more tags in DB table
449             refill_tags(mysql, id);
450             cache_off = 0;
451         }
452
453         if (cache[cache_off].id > id)
454             return NULL; // No tags for this way ID
455
456         if (cache[cache_off].id == id)
457             return &cache[cache_off++].tags;
458
459         cache_off++;
460         assert (cache_off <= TAG_CACHE);
461     }
462 }
463
464 void nodes(MYSQL *mysql, MYSQL *tags_mysql)
465 {
466     char query[255];
467     MYSQL_RES *res;
468     MYSQL_ROW row;
469     struct keyval *tags;
470
471     snprintf(query, sizeof(query), "select id, latitude, longitude, timestamp, user_id, version from current_nodes where visible = 1 order by id");
472
473     if ((mysql_query(mysql, query)) || !(res= mysql_use_result(mysql))) 
474     {
475         fprintf(stderr, "Cannot query nodes: %s\n", mysql_error(mysql));
476         exit(1);
477     }
478
479     tags_init(tags_mysql, "current_node_tags");
480
481     while ((row= mysql_fetch_row(res))) {
482         int id, version;
483         long double latitude,longitude;
484
485         assert(mysql_num_fields(res) == 6);
486
487         id = strtol(row[0], NULL, 10);
488         latitude  = strtol(row[1], NULL, 10) / 10000000.0;
489         longitude = strtol(row[2], NULL, 10) / 10000000.0;
490         version = strtol(row[5], NULL, 10);
491         tags = get_generic_tags(tags_mysql, id);
492         osm_node(id, latitude, longitude, tags, reformDate(row[3]), lookup_user(row[4]), version);
493     }
494
495     mysql_free_result(res);
496     tags_exit();
497 }
498
499 void ways(MYSQL *ways_mysql, MYSQL *nodes_mysql, MYSQL *tags_mysql)
500 {
501     char ways_query[255], nodes_query[255];
502     MYSQL_RES *ways_res, *nodes_res;
503     MYSQL_ROW ways_row, nodes_row;
504     struct keyval *tags, nodes;
505
506     initList(&nodes);
507
508     snprintf(ways_query, sizeof(ways_query),
509              "select id, timestamp, user_id, version from current_ways where visible = 1 order by id");
510     snprintf(nodes_query, sizeof(nodes_query),
511              "select id, node_id from current_way_nodes ORDER BY id, sequence_id");
512
513     if ((mysql_query(ways_mysql, ways_query)) || !(ways_res= mysql_use_result(ways_mysql)))
514     {
515         fprintf(stderr,"Cannot query current_ways: %s\n", mysql_error(ways_mysql));
516         exit(1);
517     }
518     if ((mysql_query(nodes_mysql, nodes_query)) || !(nodes_res= mysql_use_result(nodes_mysql)))
519     {
520         fprintf(stderr,"Cannot query current_way_nodes: %s\n", mysql_error(nodes_mysql));
521         exit(1);
522     }
523
524     tags_init(tags_mysql, "current_way_tags");
525
526     ways_row = mysql_fetch_row(ways_res);
527     nodes_row = mysql_fetch_row(nodes_res);
528
529     while (ways_row) {
530         int way_id = strtol(ways_row[0], NULL, 10);
531         int way_version = strtol(ways_row[3], NULL, 10);
532         // Terminating way_nd_id is necessary to ensure final way is generated.
533         int way_nd_id = nodes_row ? strtol(nodes_row[0], NULL, 10): INT_MAX;
534
535         if (way_id < way_nd_id) {
536             // no more nodes in this way
537             tags = get_generic_tags(tags_mysql, way_id);
538             osm_way(way_id, &nodes, tags, reformDate(ways_row[1]), lookup_user(ways_row[2]), way_version);
539             // fetch new way
540             ways_row= mysql_fetch_row(ways_res);
541             assert(mysql_num_fields(ways_res) == 4);
542         } else if (way_id > way_nd_id) {
543             // we have entries in current_way_nodes for a missing way, discard!
544             // fetch next way_seg
545             nodes_row = mysql_fetch_row(nodes_res);
546             assert(mysql_num_fields(nodes_res) == 2);
547         } else {
548             // in step, add current node and fetch the next one
549             addItem(&nodes, "", nodes_row[1], 0);
550             nodes_row = mysql_fetch_row(nodes_res);
551             assert(mysql_num_fields(nodes_res) == 2);
552         }
553     }
554
555     mysql_free_result(ways_res);
556     mysql_free_result(nodes_res);
557     tags_exit();
558 }
559
560 void relations(MYSQL *relations_mysql, MYSQL *members_mysql, MYSQL *tags_mysql)
561 {
562     char relations_query[255], members_query[255];
563     MYSQL_RES *relations_res, *members_res;
564     MYSQL_ROW relations_row, members_row;
565     struct keyval *tags, members, roles;
566
567     initList(&members);
568     initList(&roles);
569
570     snprintf(relations_query, sizeof(relations_query),
571              "select id, timestamp, user_id, version from current_relations where visible = 1 ORDER BY id");
572     snprintf(members_query, sizeof(members_query),
573              "select id, member_id, member_type, member_role from current_relation_members ORDER BY id");
574
575     if ((mysql_query(relations_mysql, relations_query)) || !(relations_res= mysql_use_result(relations_mysql)))
576     {
577         fprintf(stderr,"Cannot query current_relations: %s\n", mysql_error(relations_mysql));
578         exit(1);
579     }
580     if ((mysql_query(members_mysql, members_query)) || !(members_res= mysql_use_result(members_mysql)))
581     {
582         fprintf(stderr,"Cannot query current_relation_members: %s\n", mysql_error(members_mysql));
583         exit(1);
584     }
585
586     tags_init(tags_mysql, "current_relation_tags");
587
588     relations_row = mysql_fetch_row(relations_res);
589     members_row = mysql_fetch_row(members_res);
590
591     while (relations_row) {
592         int relation_id      = strtol(relations_row[0], NULL, 10);
593         int relation_version = strtol(relations_row[3], NULL, 10);
594         // Terminating relation_memb_id is necessary to ensure final way is generated.
595         int relation_memb_id = members_row ? strtol(members_row[0], NULL, 10): INT_MAX;
596
597         if (relation_id < relation_memb_id) {
598             // no more members in this way
599             tags = get_generic_tags(tags_mysql, relation_id);
600             osm_relation(relation_id, &members, &roles, tags, reformDate(relations_row[1]), lookup_user(relations_row[2]), relation_version);
601             // fetch new way
602             relations_row= mysql_fetch_row(relations_res);
603             assert(mysql_num_fields(relations_res) == 4);
604         } else if (relation_id > relation_memb_id) {
605             // we have entries in current_way_members for a missing way, discard!
606             // fetch next way_seg
607             members_row = mysql_fetch_row(members_res);
608             assert(mysql_num_fields(members_res) == 4);
609         } else {
610             // in step, add current member and fetch the next one
611             const char *m_id   = members_row[1];
612             const char *m_type = members_row[2];
613             const char *m_role = members_row[3];
614
615             addItem(&members, m_type, m_id, 0);
616             addItem(&roles, "", m_role, 0);
617             members_row = mysql_fetch_row(members_res);
618             assert(mysql_num_fields(members_res) == 4);
619         }
620     }
621
622     mysql_free_result(relations_res);
623     mysql_free_result(members_res);
624     tags_exit();
625 }
626
627 unsigned long int max_userid(MYSQL *mysql)
628 {
629     const char *sql = "SELECT MAX(id) FROM users";
630     MYSQL_RES *res;
631     MYSQL_ROW row;
632     unsigned long int max = 0;
633
634     if ((mysql_query(mysql, sql)) || !(res= mysql_use_result(mysql))) {
635         fprintf(stderr,"Cannot query users: %s\n", mysql_error(mysql));
636         exit(1);
637     }
638
639     while ((row=mysql_fetch_row(res))) {
640         max = strtol(row[0], NULL, 10);
641     }
642
643     mysql_free_result(res);
644     //printf("Maximum user id = %lu\n", max);
645     return max;
646 }
647
648
649 void fetch_users(MYSQL *mysql)
650 {
651     char sql[256];
652     MYSQL_RES *res;
653     MYSQL_ROW row;
654
655     sprintf(sql, "SELECT id,display_name from users where id <= %lu and data_public = 1", max_uid);
656
657     if ((mysql_query(mysql, sql)) || !(res= mysql_use_result(mysql))) {
658         fprintf(stderr,"Cannot query users: %s\n", mysql_error(mysql));
659         exit(1);
660     }
661
662     while ((row=mysql_fetch_row(res))) {
663         unsigned long int id;
664         const char *display_name;
665         char tmp[1024];
666         assert(mysql_num_fields(res) == 2);
667
668         id = strtoul(row[0], NULL, 10);
669         display_name = xmlescape(row[1]);
670         assert(id <= max_uid);
671         //user_list[id] = display_name;
672         //printf("User: %lu %s\n", id, display_name);
673         snprintf(tmp, sizeof(tmp), " user=\"%s\" uid=\"%lu\"", display_name, id);
674         user_list[id] = strdup(tmp);
675         assert(user_list[id]);
676     }
677
678     mysql_free_result(res); 
679 }
680
681
682 int main(int argc, char **argv)
683 {
684     // 3 MySQL connections are required to fetch way data from multiple tables
685 #define NUM_CONN (3)
686     MYSQL mysql[NUM_CONN];
687 #ifdef USE_ICONV
688     MYSQL_ROW row;
689     MYSQL_RES *res;
690 #endif
691     int i;
692     const char *set_timeout = "SET SESSION net_write_timeout=60*60*6";
693     int want_nodes, want_ways, want_relations;
694
695     // Database timestamps use UK localtime
696     setenv("TZ", ":GB", 1);
697
698     if (argc == 1)
699         want_nodes = want_ways = want_relations = 1;
700     else {
701         want_nodes = want_ways = want_relations = 0;
702         for(i=1; i<argc; i++) {
703             if (!strcmp(argv[i], "--nodes"))
704                 want_nodes = 1;
705             else if (!strcmp(argv[i], "--ways"))
706                 want_ways = 1;
707             else if (!strcmp(argv[i], "--relations"))
708                 want_relations = 1;
709             else {
710                 fprintf(stderr, "Usage error:\n");
711                 fprintf(stderr, "\t%s [--nodes] [--ways] [--relations]\n\n", argv[0]);
712                 fprintf(stderr, "Writes OSM planet dump to STDOUT. If no flags are specified then all data is output.\n");
713                 fprintf(stderr, "If one or more flags are set then only the requested data is dumped.\n");
714                 exit(2);
715             }
716         }
717     }
718
719     for (i=0; i<NUM_CONN; i++) {
720         mysql_init(&mysql[i]);
721 #if 0
722         if (mysql_options(&mysql[i], MYSQL_SET_CHARSET_NAME , "utf8")) {
723             fprintf(stderr, "set options failed\n");
724             exit(1);
725         }
726 #endif
727         if (!(mysql_real_connect(&mysql[i],"","openstreetmap","openstreetmap","openstreetmap",MYSQL_PORT,NULL,0)))
728         {
729             fprintf(stderr,"%s: %s\n",argv[0],mysql_error(&mysql[i]));
730             exit(1);
731         }
732
733         if (mysql_query(mysql, set_timeout)) {
734             fprintf(stderr,"FAILED %s: %s\n", set_timeout, mysql_error(mysql));
735             exit(1);
736        }
737     }
738
739 #ifdef USE_ICONV
740     if (mysql_query(mysql, "SHOW VARIABLES like 'character_set_results'") || !(res= mysql_use_result(mysql))) {
741             fprintf(stderr,"FAILED show variables: %s\n", mysql_error(mysql));
742             exit(1);
743     }
744
745     if ((row= mysql_fetch_row(res))) {
746         fprintf(stderr, "Setting up iconv for %s = %s\n", row[0], row[1]);
747         cd = iconv_open("UTF8", row[1]);
748         if (cd == (iconv_t)-1) {
749             perror("iconv_open");
750             exit(1);
751         }
752         row = mysql_fetch_row(res);
753         assert(!row);
754     } else {
755         fprintf(stderr, "Failed to fetch DB charset, assuming UTF8\n");
756     }
757 #endif
758
759     printf("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
760     printf("<osm version=\"0.5\" generator=\"OpenStreetMap planet.c\">\n");
761     printf("  <bound box=\"-90,-180,90,180\" origin=\"http://www.openstreetmap.org/api/0.5\" />\n");
762
763     max_uid = max_userid(&mysql[0]);
764     user_list = calloc(max_uid + 1, sizeof(char *));
765     if (!user_list) {
766         fprintf(stderr, "Malloc of user_list failed for %lu users\n", max_uid);
767         exit(1);
768     }
769
770     fetch_users(&mysql[0]);
771
772     if (want_nodes)
773         nodes(&mysql[0], &mysql[2]);
774
775     if (want_ways)
776         ways(&mysql[0], &mysql[1], &mysql[2]);
777
778     if (want_relations)
779         relations(&mysql[0], &mysql[1], &mysql[2]);
780
781     printf("</osm>\n");
782
783     for (i=0; i<NUM_CONN; i++)
784         mysql_close(&mysql[i]);
785 #ifdef USE_ICONV
786     iconv_close(cd);
787 #endif
788
789     for(i=0; i<=max_uid; i++)
790         free(user_list[i]);
791     free(user_list);
792
793     return 0;
794 }