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