libacfutils
A general purpose library of utility functions designed to make it easier to develop addons for the X-Plane flight simulator.
Loading...
Searching...
No Matches
chartdb.c
1/*
2 * CDDL HEADER START
3 *
4 * This file and its contents are supplied under the terms of the
5 * Common Development and Distribution License ("CDDL"), version 1.0.
6 * You may only use this file in accordance with the terms of version
7 * 1.0 of the CDDL.
8 *
9 * A full copy of the text of the CDDL should have accompanied this
10 * source. A copy of the CDDL is also available via the Internet at
11 * http://www.illumos.org/license/CDDL.
12 *
13 * CDDL HEADER END
14*/
15/*
16 * Copyright 2023 Saso Kiselkov. All rights reserved.
17 */
18
19#include <errno.h>
20#include <stddef.h>
21#include <string.h>
22#include <stdio.h>
23
24#if IBM
25#include <io.h>
26#include <fcntl.h>
27#else
28#include <sys/types.h>
29#include <sys/stat.h>
30#include <sys/wait.h>
31#include <unistd.h>
32#endif
33
34#include <curl/curl.h>
35#include <libxml/parser.h>
36#include <libxml/xpath.h>
37#include <png.h>
38
39#if IBM
40#include <windows.h>
41#elif APL
42#include <sys/sysctl.h>
43#endif /* APL */
44
45#include "acfutils/assert.h"
46#include "acfutils/avl.h"
47#include "acfutils/chartdb.h"
48#include "acfutils/helpers.h"
49#include "acfutils/list.h"
51#include "acfutils/png.h"
52#include "acfutils/stat.h"
53#include "acfutils/thread.h"
54#include "acfutils/worker.h"
55
56#include "chartdb_impl.h"
57#include "chart_prov_common.h"
58#include "chart_prov_faa.h"
59#include "chart_prov_autorouter.h"
60#include "chart_prov_navigraph.h"
61
62#define MAX_METAR_AGE 60 /* seconds */
63#define MAX_TAF_AGE 300 /* seconds */
64#define RETRY_INTVAL 30 /* seconds */
65#define WRITE_BUFSZ 4096 /* bytes */
66#define READ_BUFSZ 4096 /* bytes */
67
68#define DESTROY_HANDLE(__handle__) \
69 do { \
70 if ((__handle__) != NULL) { \
71 CloseHandle((__handle__)); \
72 (__handle__) = NULL; \
73 } \
74 } while (0)
75
76static chart_prov_t prov[NUM_PROVIDERS] = {
77 {
78 .name = "aeronav.faa.gov",
79 .init = chart_faa_init,
80 .fini = chart_faa_fini,
81 .get_chart = chart_faa_get_chart
82 },
83 {
84 .name = "autorouter.aero",
85 .init = chart_autorouter_init,
86 .fini = chart_autorouter_fini,
87 .get_chart = chart_autorouter_get_chart,
88 .arpt_lazyload = chart_autorouter_arpt_lazyload,
89 .test_conn = chart_autorouter_test_conn
90 },
91 {
92 .name = "navigraph.com",
93 .init = chart_navigraph_init,
94 .fini = chart_navigraph_fini,
95 .get_chart = chart_navigraph_get_chart,
96 .watermark_chart = chart_navigraph_watermark_chart,
97 .arpt_lazy_discover = chart_navigraph_arpt_lazy_discover,
98 .pending_ext_account_setup = chart_navigraph_pending_ext_account_setup
99 }
100};
101
102static chart_arpt_t *arpt_find(chartdb_t *cdb, const char *icao);
103static char *download_metar(chartdb_t *cdb, const char *icao);
104static char *download_taf(chartdb_t *cdb, const char *icao);
105
106#if IBM
107
108static uint64_t
109physmem(void)
110{
111 MEMORYSTATUSEX status;
112 status.dwLength = sizeof(status);
113 VERIFY(GlobalMemoryStatusEx(&status));
114 return (status.ullTotalPhys);
115}
116
117#elif APL
118
119static uint64_t
120physmem(void)
121{
122 int mib[2] = { CTL_HW, HW_MEMSIZE };
123 int64_t mem;
124 size_t length = sizeof(int64_t);
125 sysctl(mib, 2, &mem, &length, NULL, 0);
126 return (mem);
127}
128
129#else /* LIN */
130
131static uint64_t
132physmem(void)
133{
134 uint64_t pages = sysconf(_SC_PHYS_PAGES);
135 uint64_t page_size = sysconf(_SC_PAGE_SIZE);
136 return (pages * page_size);
137}
138
139#endif /* LIN */
140
141static int
142chart_name_compar(const void *a, const void *b)
143{
144 const chart_t *ca = a, *cb = b;
145 int res = strcmp(ca->name, cb->name);
146
147 if (res < 0)
148 return (-1);
149 if (res == 0)
150 return (0);
151 return (1);
152}
153
154static int
155arpt_compar(const void *a, const void *b)
156{
157 const chart_arpt_t *ca = a, *cb = b;
158 int res = strcmp(ca->icao, cb->icao);
159
160 if (res < 0)
161 return (-1);
162 if (res == 0)
163 return (0);
164 return (1);
165}
166
167void
168chartdb_chart_destroy(chart_t *chart)
169{
170 ASSERT(chart != NULL);
171
172 if (chart->surf != NULL)
173 cairo_surface_destroy(chart->surf);
174 if (chart->png_data != NULL) {
175 memset(chart->png_data, 0, chart->png_data_len);
176 free(chart->png_data);
177 }
178 free(chart->name);
179 free(chart->codename);
180 free(chart->filename);
181 free(chart->filename_night);
182 ZERO_FREE(chart);
183}
184
185static void
186arpt_destroy(chart_arpt_t *arpt)
187{
188 void *cookie;
189 chart_t *chart;
190
191 cookie = NULL;
192 while ((chart = avl_destroy_nodes(&arpt->charts, &cookie)) != NULL)
193 chartdb_chart_destroy(chart);
194 avl_destroy(&arpt->charts);
195
196 free(arpt->name);
197 free(arpt->city);
198 free(arpt->metar);
199 free(arpt->taf);
200 free(arpt->codename);
201
202 free(arpt);
203}
204
205static void
206remove_old_airacs(chartdb_t *cdb)
207{
208 char *dpath = mkpathname(cdb->path, cdb->prov_name, NULL);
209 DIR *dp;
210 struct dirent *de;
211 time_t now = time(NULL);
212
213 if (!file_exists(dpath, NULL))
214 goto out;
215
216 dp = opendir(dpath);
217 if (dp == NULL) {
218 logMsg("Error accessing directory %s: %s", dpath,
219 strerror(errno));
220 goto out;
221 }
222 while ((de = readdir(dp)) != NULL) {
223 unsigned nr = 0;
224 char *subpath;
225 struct stat st;
226
227 if (strlen(de->d_name) != 4 ||
228 sscanf(de->d_name, "%u", &nr) != 1 ||
229 nr < 1000 || nr >= cdb->airac) {
230 continue;
231 }
232 subpath = mkpathname(dpath, de->d_name, NULL);
233 if (stat(subpath, &st) == 0) {
234 if (now - st.st_mtime > 30 * 86400)
235 remove_directory(subpath);
236 }
237 free(subpath);
238 }
239
240 closedir(dp);
241out:
242 free(dpath);
243}
244
245static bool_t
246loader_init(void *userinfo)
247{
248 chartdb_t *cdb = userinfo;
249
250 ASSERT(cdb != NULL);
251 ASSERT3U(cdb->prov, <, NUM_PROVIDERS);
252
253 /* Expunge outdated AIRACs */
254 remove_old_airacs(cdb);
255
256 if (!prov[cdb->prov].init(cdb))
257 return (B_FALSE);
258
259 mutex_enter(&cdb->lock);
260 cdb->init_complete = B_TRUE;
261 mutex_exit(&cdb->lock);
262
263 return (B_TRUE);
264}
265
266static void
267loader_purge(chartdb_t *cdb)
268{
269 for (chart_arpt_t *arpt = avl_first(&cdb->arpts); arpt != NULL;
270 arpt = AVL_NEXT(&cdb->arpts, arpt)) {
271 for (chart_t *chart = avl_first(&arpt->charts); chart != NULL;
272 chart = AVL_NEXT(&arpt->charts, chart)) {
273 if (chart->surf != NULL) {
274 cairo_surface_destroy(chart->surf);
275 chart->surf = NULL;
276 }
277 if (chart->png_data != NULL) {
278 free(chart->png_data);
279 chart->png_data = NULL;
280 chart->png_data_len = 0;
281 }
282 }
283 }
284 while (list_remove_head(&cdb->load_seq) != NULL)
285 ;
286}
287
288chart_arpt_t *
289chartdb_add_arpt(chartdb_t *cdb, const char *icao, const char *name,
290 const char *city_name, const char *state_id)
291{
292 chart_arpt_t *arpt, srch;
293 avl_index_t where;
294
295 ASSERT(cdb != NULL);
296
297 lacf_strlcpy(srch.icao, icao, sizeof (srch.icao));
298
299 mutex_enter(&cdb->lock);
300 arpt = avl_find(&cdb->arpts, &srch, &where);
301 if (arpt == NULL) {
302 arpt = safe_calloc(1, sizeof (*arpt));
303 avl_create(&arpt->charts, chart_name_compar, sizeof (chart_t),
304 offsetof(chart_t, node));
305 lacf_strlcpy(arpt->icao, icao, sizeof (arpt->icao));
306 arpt->name = safe_strdup(name);
307 arpt->city = safe_strdup(city_name);
308 lacf_strlcpy(arpt->state, state_id, sizeof (arpt->state));
309 arpt->db = cdb;
310 avl_insert(&cdb->arpts, arpt, where);
311 }
312 mutex_exit(&cdb->lock);
313
314 return (arpt);
315}
316
317bool_t
318chartdb_add_chart(chart_arpt_t *arpt, chart_t *chart)
319{
320 avl_index_t where;
321 chartdb_t *cdb = arpt->db;
322
323 ASSERT(cdb != NULL);
324
325 mutex_enter(&cdb->lock);
326 if (avl_find(&arpt->charts, chart, &where) != NULL) {
327 mutex_exit(&cdb->lock);
328 return (B_FALSE);
329 }
330 avl_insert(&arpt->charts, chart, where);
331 chart->arpt = arpt;
332 chart->num_pages = -1;
333 arpt->load_complete = B_TRUE;
334 mutex_exit(&cdb->lock);
335
336 return (B_TRUE);
337}
338
339char *
340chartdb_mkpath(chart_t *chart)
341{
342 chart_arpt_t *arpt = chart->arpt;
343 chartdb_t *cdb;
344 char airac_nr[8];
345
346 ASSERT(arpt != NULL);
347 cdb = arpt->db;
348 ASSERT(cdb != NULL);
349
350 snprintf(airac_nr, sizeof (airac_nr), "%d", cdb->airac);
351 if (cdb->flat_db) {
352 return (mkpathname(cdb->path, prov[cdb->prov].name, airac_nr,
353 chart->filename, NULL));
354 } else {
355 return (mkpathname(cdb->path, prov[cdb->prov].name, airac_nr,
356 arpt->icao, chart->filename, NULL));
357 }
358}
359
360int
361chartdb_pdf_count_pages_direct(const char *pdfinfo_path, const uint8_t *buf,
362 size_t len)
363{
364 int num_pages = -1;
365 char *dpath = lacf_dirname(pdfinfo_path);
366 int fd_in = -1, fd_out = -1;
367 char *line = NULL;
368 size_t cap = 0, fill = 0;
369 size_t written = 0;
370 char *out_buf = NULL;
371 char **lines = NULL;
372 size_t num_lines;
373
374#if IBM
375 SECURITY_ATTRIBUTES sa;
376 HANDLE stdin_rd_handle = NULL;
377 HANDLE stdin_wr_handle = NULL;
378 HANDLE stdout_rd_handle = NULL;
379 HANDLE stdout_wr_handle = NULL;
380 PROCESS_INFORMATION pi;
381 STARTUPINFO si;
382 char cmd[3 * MAX_PATH];
383 TCHAR cmdT[3 * MAX_PATH];
384
385 memset(&pi, 0, sizeof (pi));
386
387 snprintf(cmd, sizeof (cmd), "\"%s\" fd://0", pdfinfo_path);
388 MultiByteToWideChar(CP_UTF8, 0, cmd, -1, cmdT, 3 * MAX_PATH);
389
390 sa.nLength = sizeof(sa);
391 sa.bInheritHandle = TRUE;
392 sa.lpSecurityDescriptor = NULL;
393
394 if (!CreatePipe(&stdout_rd_handle, &stdout_wr_handle, &sa, 0) ||
395 !SetHandleInformation(stdout_rd_handle, HANDLE_FLAG_INHERIT, 0) ||
396 !CreatePipe(&stdin_rd_handle, &stdin_wr_handle, &sa, 0) ||
397 !SetHandleInformation(stdin_wr_handle, HANDLE_FLAG_INHERIT, 0)) {
398 win_perror(GetLastError(), "Error creating pipe");
399 goto errout;
400 }
401 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
402 ZeroMemory(&si, sizeof(STARTUPINFO));
403 si.cb = sizeof(STARTUPINFO);
404 si.hStdInput = stdin_rd_handle;
405 si.hStdOutput = stdout_wr_handle;
406 si.dwFlags |= STARTF_USESTDHANDLES;
407
408 fd_in = _open_osfhandle((intptr_t)stdin_wr_handle, _O_WRONLY);
409 fd_out = _open_osfhandle((intptr_t)stdout_rd_handle, _O_RDONLY);
410 if (fd_in == -1 || fd_out == -1) {
411 win_perror(GetLastError(), "Error opening pipe as fd");
412 goto errout;
413 }
414 /*
415 * OSF open take over the handle!
416 */
417 stdin_wr_handle = NULL;
418 stdout_rd_handle = NULL;
419
420 if (!CreateProcess(NULL, cmdT, NULL, NULL, TRUE,
421 CREATE_NO_WINDOW | BELOW_NORMAL_PRIORITY_CLASS,
422 NULL, NULL, &si, &pi)) {
423 win_perror(GetLastError(), "Error invoking %s", pdfinfo_path);
424 goto errout;
425 }
426#else /* !IBM */
427 int child_pid;
428 int stdin_pipe[2] = { -1, -1 };
429 int stdout_pipe[2] = { -1, -1 };
430
431 if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0) {
432 logMsg("Error counting PDF pages: pipe failed: %s\n",
433 strerror(errno));
434 goto errout;
435 }
436
437 child_pid = fork();
438 switch (child_pid) {
439 case -1:
440 logMsg("Error counting PDF pages: fork failed: %s\n",
441 strerror(errno));
442 goto errout;
443 case 0:
444 dup2(stdin_pipe[0], STDIN_FILENO);
445 dup2(stdout_pipe[1], STDOUT_FILENO);
446 for (int i = 0; i < 2; i++) {
447 close(stdin_pipe[i]);
448 close(stdout_pipe[i]);
449 }
450#if APL
451 setenv("DYLD_LIBRARY_PATH", dpath, 1);
452#else
453 setenv("LD_LIBRARY_PATH", dpath, 1);
454#endif
455 execl(pdfinfo_path, pdfinfo_path, "fd://0", NULL);
456 logMsg("Error counting PDF pages: execl failed: %s\n",
457 strerror(errno));
458 exit(EXIT_FAILURE);
459 default:
460 fd_in = dup(stdin_pipe[1]);
461 fd_out = dup(stdout_pipe[0]);
462 VERIFY(fd_in != -1);
463 VERIFY(fd_out != -1);
464 for (int i = 0; i < 2; i++) {
465 close(stdin_pipe[i]);
466 stdin_pipe[i] = -1;
467 close(stdout_pipe[i]);
468 stdout_pipe[i] = -1;
469 }
470 break;
471 }
472#endif /* !IBM */
473
474 while (written < len) {
475 int n = write(fd_in, &buf[written], len - written);
476 if (n == -1) {
477 logMsg("write error: %s", strerror(errno));
478 goto errout;
479 }
480 if (n == 0)
481 break;
482 written += n;
483 }
484 close(fd_in);
485 fd_in = -1;
486
487 for (;;) {
488 int n, to_read;
489
490 if (cap - fill < READ_BUFSZ) {
491 cap += READ_BUFSZ;
492 out_buf = safe_realloc(out_buf, cap);
493 }
494 to_read = cap - fill;
495 n = read(fd_out, &out_buf[fill], to_read);
496 if (n == -1) {
497 logMsg("read error: %s", strerror(errno));
498 goto errout;
499 }
500 if (n == 0) {
501 /* EOF */
502 break;
503 }
504 fill += n;
505#if IBM
506 /*
507 * On windows, a short byte count indicates an EOF, so
508 * we need to exit now, or else we'll block indefinitely.
509 */
510 if (n < to_read)
511 break;
512#endif /* IBM */
513 }
514 close(fd_out);
515 fd_out = -1;
516
517 lines = strsplit(out_buf, "\n", B_TRUE, &num_lines);
518 for (size_t i = 0; i < num_lines; i++) {
519 char *line = lines[i];
520 if (strncmp(line, "Pages:", 6) == 0) {
521 size_t n_comps;
522 char **comps;
523
524 strip_space(line);
525 comps = strsplit(line, " ", B_TRUE, &n_comps);
526 if (n_comps >= 2)
527 num_pages = atoi(comps[1]);
528 free_strlist(comps, n_comps);
529 break;
530 }
531 }
532
533#if IBM
534 WaitForSingleObject(pi.hProcess, INFINITE);
535#else /* !IBM */
536 /* reap child process */
537 waitpid(child_pid, NULL, 0);
538#endif /* !IBM */
539
540errout:
541 if (lines != NULL)
542 free_strlist(lines, num_lines);
543 if (fd_in != -1)
544 close(fd_in);
545 if (fd_out != -1)
546 close(fd_out);
547#if IBM
548 DESTROY_HANDLE(stdin_rd_handle);
549 DESTROY_HANDLE(stdin_wr_handle);
550 DESTROY_HANDLE(stdout_rd_handle);
551 DESTROY_HANDLE(stdout_wr_handle);
552 DESTROY_HANDLE(pi.hProcess);
553 DESTROY_HANDLE(pi.hThread);
554#else /* !IBM */
555 if (stdin_pipe[0] != -1) {
556 close(stdin_pipe[0]);
557 close(stdin_pipe[1]);
558 }
559 if (stdout_pipe[0] != -1) {
560 close(stdout_pipe[0]);
561 close(stdout_pipe[1]);
562 }
563#endif /* !IBM */
564
565 free(line);
566 free(dpath);
567 free(out_buf);
568 if (num_pages == -1)
569 logMsg("Unable to read page count");
570
571 return (num_pages);
572}
573
574int
575chartdb_pdf_count_pages_file(const char *pdfinfo_path, const char *path)
576{
577 uint8_t *buf = NULL;
578 size_t len = 0, fill = 0;
579 FILE *infp = fopen(path, "rb");
580 int pages = -1;
581
582 if (infp == NULL) {
583 logMsg("Error counting PDF pages %s: can't read input: %s",
584 path, strerror(errno));
585 return (-1);
586 }
587 for (;;) {
588 int ret;
589
590 if (len - fill < READ_BUFSZ) {
591 len += READ_BUFSZ;
592 buf = safe_realloc(buf, len);
593 }
594 ret = fread(&buf[fill], 1, len - fill, infp);
595 if (ret > 0)
596 fill += ret;
597 if (ret < READ_BUFSZ) {
598 if (ferror(infp)) {
599 logMsg("Error counting PDF pages %s: "
600 "error reading input", path);
601 goto errout;
602 }
603 break;
604 }
605 }
606 pages = chartdb_pdf_count_pages_direct(pdfinfo_path, buf, fill);
607errout:
608 fclose(infp);
609 free(buf);
610
611 return (pages);
612}
613
614char *
615chartdb_pdf_convert_file(const char *pdftoppm_path, char *old_path, int page,
616 double zoom)
617{
618 char *ext = NULL, *new_path = NULL;
619 uint8_t *pdf_buf = NULL;
620 size_t pdf_len = 0, pdf_fill = 0;
621 uint8_t *png_buf = NULL;
622 size_t png_len;
623 FILE *infp = NULL, *outfp = NULL;
624
625 infp = fopen(old_path, "rb");
626 if (infp == NULL) {
627 logMsg("Error converting chart %s: can't read input: %s",
628 old_path, strerror(errno));
629 return (NULL);
630 }
631 for (;;) {
632 int ret;
633
634 if (pdf_len - pdf_fill < READ_BUFSZ) {
635 pdf_len += READ_BUFSZ;
636 pdf_buf = safe_realloc(pdf_buf, pdf_len);
637 }
638 ret = fread(&pdf_buf[pdf_fill], 1, pdf_len - pdf_fill, infp);
639 if (ret > 0)
640 pdf_fill += ret;
641 if (ret < READ_BUFSZ) {
642 if (ferror(infp)) {
643 logMsg("Error converting chart %s: "
644 "error reading input", old_path);
645 goto errout;
646 }
647 break;
648 }
649 }
650 fclose(infp);
651 infp = NULL;
652
653 png_buf = chartdb_pdf_convert_direct(pdftoppm_path, pdf_buf, pdf_fill,
654 page, zoom, &png_len);
655 if (png_buf == NULL)
656 goto errout;
657
658 new_path = safe_strdup(old_path);
659 ext = strrchr(new_path, '.');
660 VERIFY(ext != NULL);
661 lacf_strlcpy(&ext[1], "png", strlen(&ext[1]) + 1);
662
663 outfp = fopen(new_path, "wb");
664 if (outfp == NULL) {
665 logMsg("Error converting chart %s: can't write output "
666 "file %s: %s", old_path, new_path, strerror(errno));
667 goto errout;
668 }
669 if (fwrite(png_buf, 1, png_len, outfp) < png_len) {
670 logMsg("Error converting chart %s: can't write output to "
671 "file %s", old_path, new_path);
672 goto errout;
673 }
674 fclose(outfp);
675 outfp = NULL;
676
677 free(pdf_buf);
678 free(png_buf);
679
680 return (new_path);
681errout:
682 if (infp != NULL)
683 fclose(infp);
684 if (outfp != NULL)
685 fclose(outfp);
686 free(new_path);
687 free(pdf_buf);
688 free(png_buf);
689 return (NULL);
690}
691
692uint8_t *
693chartdb_pdf_convert_direct(const char *pdftoppm_path, const uint8_t *pdf_data,
694 size_t len, int page, double zoom, size_t *out_len)
695{
696 char *dpath;
697 int fd_in = -1, fd_out = -1;
698 int exit_code;
699#if !IBM
700 int child_pid;
701#endif
702 size_t written = 0;
703 uint8_t *png_buf = NULL;
704 int png_buf_sz = 0, png_buf_fill = 0;
705
706 zoom = clamp(zoom, 0.1, 10.0);
707 dpath = lacf_dirname(pdftoppm_path);
708
709 /*
710 * As the PDF conversion process can be rather CPU-intensive, run
711 * with reduced priority to avoid starving the sim, even if that
712 * means taking longer.
713 */
714#if IBM
715 SECURITY_ATTRIBUTES sa;
716 HANDLE stdin_rd_handle = NULL;
717 HANDLE stdin_wr_handle = NULL;
718 HANDLE stdout_rd_handle = NULL;
719 HANDLE stdout_wr_handle = NULL;
720 PROCESS_INFORMATION pi;
721 STARTUPINFO si;
722 char cmd[3 * MAX_PATH];
723 TCHAR cmdT[3 * MAX_PATH];
724 DWORD exit_code_win;
725
726 sa.nLength = sizeof(sa);
727 sa.bInheritHandle = TRUE;
728 sa.lpSecurityDescriptor = NULL;
729
730 if (!CreatePipe(&stdout_rd_handle, &stdout_wr_handle, &sa, 0) ||
731 !SetHandleInformation(stdout_rd_handle, HANDLE_FLAG_INHERIT, 0) ||
732 !CreatePipe(&stdin_rd_handle, &stdin_wr_handle, &sa, 0) ||
733 !SetHandleInformation(stdin_wr_handle, HANDLE_FLAG_INHERIT, 0)) {
734 win_perror(GetLastError(), "Error creating pipes");
735 goto errout;
736 }
737 ZeroMemory(&pi, sizeof(PROCESS_INFORMATION));
738 ZeroMemory(&si, sizeof(STARTUPINFO));
739 si.cb = sizeof(STARTUPINFO);
740 si.hStdInput = stdin_rd_handle;
741 si.hStdOutput = stdout_wr_handle;
742 si.dwFlags |= STARTF_USESTDHANDLES;
743
744 fd_in = _open_osfhandle((intptr_t)stdin_wr_handle, _O_WRONLY);
745 fd_out = _open_osfhandle((intptr_t)stdout_rd_handle, _O_RDONLY);
746 if (fd_in == -1 || fd_out == -1) {
747 win_perror(GetLastError(), "Error opening pipe as fd");
748 goto errout;
749 }
750 /*
751 * OSF open take over the handle!
752 */
753 stdin_wr_handle = NULL;
754 stdout_rd_handle = NULL;
755
756 snprintf(cmd, sizeof (cmd), "\"%s\" -png -f %d -l %d -r %d -cropbox",
757 pdftoppm_path, page + 1, page + 1, (int)(100 * zoom));
758 MultiByteToWideChar(CP_UTF8, 0, cmd, -1, cmdT, 3 * MAX_PATH);
759
760 if (!CreateProcess(NULL, cmdT, NULL, NULL, TRUE,
761 CREATE_NO_WINDOW | BELOW_NORMAL_PRIORITY_CLASS,
762 NULL, NULL, &si, &pi)) {
763 win_perror(GetLastError(), "Error converting chart to PNG");
764 goto errout;
765 }
766#else /* !IBM */
767 int stdin_pipe[2] = { -1, -1 };
768 int stdout_pipe[2] = { -1, -1 };
769 char page_nr[8], zoom_nr[8];
770
771 if (pipe(stdin_pipe) < 0 || pipe(stdout_pipe) < 0) {
772 logMsg("Error converting chart to PNG: "
773 "error creating pipes: %s", strerror(errno));
774 goto errout;
775 }
776 snprintf(page_nr, sizeof (page_nr), "%d", page + 1);
777 snprintf(zoom_nr, sizeof (zoom_nr), "%d", (int)(100 * zoom));
778
779 child_pid = fork();
780 switch (child_pid) {
781 case -1:
782 logMsg("Error converting chart to PNG: fork failed: %s",
783 strerror(errno));
784 goto errout;
785 case 0:
786 dup2(stdin_pipe[0], STDIN_FILENO);
787 dup2(stdout_pipe[1], STDOUT_FILENO);
788 for (int i = 0; i < 2; i++) {
789 close(stdin_pipe[i]);
790 close(stdout_pipe[i]);
791 }
792#if APL
793 setenv("DYLD_LIBRARY_PATH", dpath, 1);
794#else /* !APL */
795 setenv("LD_LIBRARY_PATH", dpath, 1);
796#endif /* !APL */
797 /* drop exec priority so the sim doesn't stutter */
798 if (nice(10)) { /*shut up GCC */ }
799 execl(pdftoppm_path, pdftoppm_path, "-png", "-f", page_nr,
800 "-l", page_nr, "-r", zoom_nr, "-cropbox", NULL);
801 logMsg("Error converting chart to PNG: execv failed: %s",
802 strerror(errno));
803 exit(EXIT_FAILURE);
804 default:
805 fd_in = dup(stdin_pipe[1]);
806 fd_out = dup(stdout_pipe[0]);
807 for (int i = 0; i < 2; i++) {
808 close(stdin_pipe[i]);
809 stdin_pipe[i] = -1;
810 close(stdout_pipe[i]);
811 stdout_pipe[i] = -1;
812 }
813 break;
814 }
815#endif /* !IBM */
816
817 while (written < len) {
818 int n = write(fd_in, &pdf_data[written], len - written);
819 if (n == -1) {
820 logMsg("write error: %s", strerror(errno));
821 goto errout;
822 }
823 if (n == 0)
824 break;
825 written += n;
826 }
827 close(fd_in);
828 fd_in = -1;
829
830 for (;;) {
831 int n, to_read;
832
833 if (png_buf_sz - png_buf_fill < READ_BUFSZ) {
834 png_buf_sz += READ_BUFSZ;
835 png_buf = safe_realloc(png_buf, png_buf_sz);
836 }
837 to_read = png_buf_sz - png_buf_fill;
838 n = read(fd_out, &png_buf[png_buf_fill], to_read);
839 if (n <= 0)
840 break;
841 png_buf_fill += n;
842#if IBM
843 /*
844 * On windows, a short byte count indicates an EOF, so
845 * we need to exit now, or else we'll block indefinitely.
846 */
847 if (n < to_read)
848 break;
849#endif /* IBM */
850 }
851 close(fd_out);
852 fd_out = -1;
853
854#if IBM
855 DESTROY_HANDLE(stdin_rd_handle);
856 DESTROY_HANDLE(stdin_wr_handle);
857 DESTROY_HANDLE(stdout_rd_handle);
858 DESTROY_HANDLE(stdout_wr_handle);
859 stdin_rd_handle = NULL;
860 stdin_wr_handle = NULL;
861 stdout_rd_handle = NULL;
862 stdout_wr_handle = NULL;
863
864 WaitForSingleObject(pi.hProcess, INFINITE);
865 VERIFY(GetExitCodeProcess(pi.hProcess, &exit_code_win));
866 exit_code = exit_code_win;
867 DESTROY_HANDLE(pi.hProcess);
868 DESTROY_HANDLE(pi.hThread);
869#else /* !IBM */
870 while (waitpid(child_pid, &exit_code, 0) < 0) {
871 if (errno != EINTR) {
872 logMsg("Error converting chart to PNG: "
873 "waitpid failed: %s", strerror(errno));
874 goto errout;
875 }
876 }
877 /* chunk out the exit code only */
878 exit_code = WEXITSTATUS(exit_code);
879#endif /* !IBM */
880
881 if (exit_code != 0) {
882 logMsg("Error converting chart to PNG. Command returned "
883 "error code %d", exit_code);
884 goto errout;
885 }
886
887 free(dpath);
888 *out_len = png_buf_fill;
889 return (png_buf);
890errout:
891 if (fd_in != -1)
892 close(fd_in);
893 if (fd_out != -1)
894 close(fd_out);
895#if IBM
896 DESTROY_HANDLE(stdout_rd_handle);
897 DESTROY_HANDLE(stdout_wr_handle);
898 DESTROY_HANDLE(stdin_rd_handle);
899 DESTROY_HANDLE(stdin_wr_handle);
900#else /* !IBM */
901 if (stdin_pipe[0] != -1) {
902 close(stdin_pipe[0]);
903 close(stdin_pipe[1]);
904 }
905 if (stdout_pipe[0] != -1) {
906 close(stdout_pipe[0]);
907 close(stdout_pipe[1]);
908 }
909#endif /* !IBM */
910 free(dpath);
911 free(png_buf);
912 return (NULL);
913}
914
915static void
916invert_surface(cairo_surface_t *surf)
917{
918 uint8_t *data = cairo_image_surface_get_data(surf);
919 int stride = cairo_image_surface_get_stride(surf);
920 int width = cairo_image_surface_get_width(surf);
921 int height = cairo_image_surface_get_height(surf);
922
923 cairo_surface_flush(surf);
924
925 switch (cairo_image_surface_get_format(surf)) {
926 case CAIRO_FORMAT_ARGB32:
927 case CAIRO_FORMAT_RGB24:
928 for (int y = 0; y < height; y++) {
929 uint8_t *p = data + y * stride;
930
931 for (int x = 0; x < width; x++) {
932#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
933 p[1] = 255 - p[1];
934 p[2] = 255 - p[2];
935 p[3] = 255 - p[3];
936#else
937 p[0] = 255 - p[0];
938 p[1] = 255 - p[1];
939 p[2] = 255 - p[2];
940#endif
941 p += 4;
942 }
943 }
944 break;
945 default:
946 logMsg("Unable to invert surface colors: unsupported "
947 "format %x", cairo_image_surface_get_format(surf));
948 break;
949 }
950 cairo_surface_mark_dirty(surf);
951}
952
953static cairo_surface_t *
954chart_get_surface_nocache(chartdb_t *cdb, chart_t *chart)
955{
956 int width, height;
957 uint8_t *png_pixels;
958 cairo_surface_t *surf;
959 uint32_t *surf_data;
960
961 ASSERT(cdb != NULL);
962 ASSERT(chart != NULL);
963
964 if (chart->png_data == NULL)
965 return (NULL);
966 png_pixels = png_load_from_buffer_cairo_argb32(chart->png_data,
967 chart->png_data_len, &width, &height);
968 if (png_pixels == NULL)
969 return (NULL);
970 surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, width, height);
971 surf_data = (uint32_t *)cairo_image_surface_get_data(surf);
972 ASSERT(surf_data != NULL);
973 memcpy(surf_data, png_pixels, width * height * 4);
974 cairo_surface_mark_dirty(surf);
975
976 ZERO_FREE_N(png_pixels, width * height * 4);
977
978 return (surf);
979}
980
981static bool_t
982chart_needs_get(chartdb_t *cdb, chart_t *chart)
983{
984 ASSERT(cdb != NULL);
985 ASSERT(chart != NULL);
986 if (!cdb->disallow_caching) {
987 /*
988 * If we use caching, try to redownload the chart once,
989 * or redownload if the file doesn't exist on disk.
990 */
991 char *path = chartdb_mkpath(chart);
992 bool_t result = (!chart->refreshed || !file_exists(path, NULL));
993 free(path);
994 return (result);
995 } else {
996 /*
997 * If we are not allowed to cache the chart, look for the
998 * png_data pointer. The chart provider will populate it.
999 * Also refresh the data if the day/night status changed
1000 * and the chart provider can do day/night specific charts.
1001 */
1002 return (chart->png_data == NULL ||
1003 (chart->filename_night != NULL &&
1004 chart->night_prev != chart->night));
1005 }
1006}
1007
1008static cairo_surface_t *
1009chart_get_surface(chartdb_t *cdb, chart_t *chart)
1010{
1011 char *path = NULL, *ext = NULL;
1012 cairo_surface_t *surf = NULL;
1013
1014 ASSERT(cdb != NULL);
1015 ASSERT(chart != NULL);
1016
1017 if (chart->load_cb != NULL)
1018 return (chart->load_cb(chart));
1019 if (chart_needs_get(cdb, chart)) {
1020 chart->refreshed = B_TRUE;
1021 if (!prov[cdb->prov].get_chart(chart)) {
1022 mutex_enter(&cdb->lock);
1023 chart->load_error = B_TRUE;
1024 mutex_exit(&cdb->lock);
1025 goto out;
1026 }
1027 }
1028 chart->night_prev = chart->night;
1029 if (cdb->disallow_caching) {
1030 surf = chart_get_surface_nocache(cdb, chart);
1031 goto out;
1032 }
1033 path = chartdb_mkpath(chart);
1034 ext = strrchr(path, '.');
1035 if (ext != NULL &&
1036 (strcmp(&ext[1], "pdf") == 0 || strcmp(&ext[1], "PDF") == 0)) {
1037 if (cdb->pdfinfo_path == NULL ||
1038 cdb->pdftoppm_path == NULL) {
1039 logMsg("Attempted to load PDF chart, but this chart "
1040 "DB instance doesn't support PDF conversion");
1041 mutex_enter(&cdb->lock);
1042 chart->load_error = B_TRUE;
1043 mutex_exit(&cdb->lock);
1044 goto out;
1045 }
1046 if (chart->num_pages == -1) {
1047 chart->num_pages = chartdb_pdf_count_pages_file(
1048 cdb->pdfinfo_path, path);
1049 }
1050 if (chart->num_pages == -1) {
1051 mutex_enter(&cdb->lock);
1052 chart->load_error = B_TRUE;
1053 mutex_exit(&cdb->lock);
1054 goto out;
1055 }
1056 path = chartdb_pdf_convert_file(cdb->pdftoppm_path, path,
1057 chart->load_page, chart->zoom);
1058 if (path == NULL) {
1059 mutex_enter(&cdb->lock);
1060 chart->load_page = chart->cur_page;
1061 chart->load_error = B_TRUE;
1062 mutex_exit(&cdb->lock);
1063 goto out;
1064 }
1065 }
1066 surf = cairo_image_surface_create_from_png(path);
1067out:
1068 free(path);
1069 return (surf);
1070}
1071
1072static void
1073loader_load(chartdb_t *cdb, chart_t *chart)
1074{
1075 cairo_surface_t *surf = chart_get_surface(cdb, chart);
1076 cairo_status_t st;
1077
1078 if (surf == NULL)
1079 return;
1080 if ((st = cairo_surface_status(surf)) == CAIRO_STATUS_SUCCESS) {
1081 /*
1082 * If night mode was selected and this provider doesn't
1083 * explicitly support supplying night charts, simply invert
1084 * the surface's colors.
1085 */
1086 if (chart->night && chart->filename_night == NULL)
1087 invert_surface(surf);
1088 if (prov[cdb->prov].watermark_chart != NULL)
1089 prov[cdb->prov].watermark_chart(chart, surf);
1090 mutex_enter(&cdb->lock);
1091 CAIRO_SURFACE_DESTROY(chart->surf);
1092 chart->surf = surf;
1093 chart->cur_page = chart->load_page;
1094 mutex_exit(&cdb->lock);
1095 } else {
1096 logMsg("Can't load chart %s PNG file %s", chart->name,
1097 cairo_status_to_string(st));
1098 mutex_enter(&cdb->lock);
1099 chart->load_error = B_TRUE;
1100 mutex_exit(&cdb->lock);
1101 }
1102}
1103
1104static uint64_t
1105chart_mem_usage(chartdb_t *cdb)
1106{
1107 uint64_t total = 0;
1108
1109 for (chart_t *c = list_head(&cdb->load_seq); c != NULL;
1110 c = list_next(&cdb->load_seq, c)) {
1111 if (c->surf != NULL) {
1112 unsigned w = cairo_image_surface_get_stride(c->surf);
1113 unsigned h = cairo_image_surface_get_height(c->surf);
1114
1115 total += w * h * 4;
1116 }
1117 total += c->png_data_len;
1118 }
1119
1120 return (total);
1121}
1122
1123static bool_t
1124loader(void *userinfo)
1125{
1126 chartdb_t *cdb = userinfo;
1127 chart_t *chart;
1128 chart_arpt_t *arpt;
1129
1130 mutex_enter(&cdb->lock);
1131 while ((arpt = list_remove_head(&cdb->loader_arpt_queue)) != NULL) {
1132 if (arpt->load_complete ||
1133 prov[cdb->prov].arpt_lazyload == NULL)
1134 continue;
1135 mutex_exit(&cdb->lock);
1136 prov[cdb->prov].arpt_lazyload(arpt);
1137 mutex_enter(&cdb->lock);
1138 }
1139 while ((chart = list_remove_head(&cdb->loader_queue)) != NULL) {
1140 if (chart == &cdb->loader_cmd_purge) {
1141 loader_purge(cdb);
1142 } else if (chart == &cdb->loader_cmd_metar) {
1143 char *metar;
1144
1145 chart->arpt->metar_load_t = time(NULL);
1146 mutex_exit(&cdb->lock);
1147 metar = download_metar(cdb, chart->arpt->icao);
1148 mutex_enter(&cdb->lock);
1149 if (chart->arpt->metar != NULL)
1150 free(chart->arpt->metar);
1151 chart->arpt->metar = metar;
1152 if (metar == NULL) {
1153 chart->arpt->metar_load_t = time(NULL) -
1154 (MAX_METAR_AGE - RETRY_INTVAL);
1155 }
1156 } else if (chart == &cdb->loader_cmd_taf) {
1157 char *taf;
1158
1159 chart->arpt->taf_load_t = time(NULL);
1160 mutex_exit(&cdb->lock);
1161 taf = download_taf(cdb, chart->arpt->icao);
1162 mutex_enter(&cdb->lock);
1163 if (chart->arpt->taf != NULL)
1164 free(chart->arpt->taf);
1165 chart->arpt->taf = taf;
1166 if (taf == NULL) {
1167 chart->arpt->taf_load_t = time(NULL) -
1168 (MAX_TAF_AGE - RETRY_INTVAL);
1169 }
1170 } else {
1171 mutex_exit(&cdb->lock);
1172 loader_load(cdb, chart);
1173 mutex_enter(&cdb->lock);
1174 /* Move to the head of the load sequence list */
1175 if (list_link_active(&chart->load_seq_node))
1176 list_remove(&cdb->load_seq, chart);
1177 list_insert_head(&cdb->load_seq, chart);
1178
1179 while (list_count(&cdb->load_seq) > 1 &&
1180 chart_mem_usage(cdb) > cdb->load_limit) {
1181 chart_t *c = list_tail(&cdb->load_seq);
1182
1183 if (c->surf != NULL) {
1184 cairo_surface_destroy(c->surf);
1185 c->surf = NULL;
1186 }
1187 if (c->png_data != NULL) {
1188 free(c->png_data);
1189 c->png_data = NULL;
1190 c->png_data_len = 0;
1191 }
1192 list_remove(&cdb->load_seq, c);
1193 }
1194 }
1195 }
1196 mutex_exit(&cdb->lock);
1197
1198 return (B_TRUE);
1199}
1200
1201static void
1202loader_fini(void *userinfo)
1203{
1204 chartdb_t *cdb = userinfo;
1205
1206 ASSERT(cdb != NULL);
1207 ASSERT3U(cdb->prov, <, NUM_PROVIDERS);
1208 prov[cdb->prov].fini(cdb);
1209}
1210
1211chartdb_t *
1212chartdb_init(const char *cache_path, const char *pdftoppm_path,
1213 const char *pdfinfo_path, unsigned airac, const char *provider_name,
1214 const chart_prov_info_login_t *provider_login)
1215{
1216 chartdb_t *cdb;
1217 chart_prov_id_t pid;
1218
1219 ASSERT(cache_path != NULL);
1220 /* pdftoppm_path can be NULL */
1221 /* pdfinfo_path can be NULL */
1222 ASSERT(provider_name != NULL);
1223 /* provider_login can be NULL */
1224
1225 for (pid = 0; pid < NUM_PROVIDERS; pid++) {
1226 if (strcmp(provider_name, prov[pid].name) == 0)
1227 break;
1228 }
1229 if (pid >= NUM_PROVIDERS)
1230 return (NULL);
1231
1232 cdb = safe_calloc(1, sizeof (*cdb));
1233 mutex_init(&cdb->lock);
1234 avl_create(&cdb->arpts, arpt_compar, sizeof (chart_arpt_t),
1235 offsetof(chart_arpt_t, node));
1236 cdb->path = safe_strdup(cache_path);
1237 if (pdftoppm_path != NULL)
1238 cdb->pdftoppm_path = safe_strdup(pdftoppm_path);
1239 if (pdfinfo_path != NULL)
1240 cdb->pdfinfo_path = safe_strdup(pdfinfo_path);
1241 cdb->airac = airac;
1242 cdb->prov = pid;
1243 /* Deep-copy the login information */
1244 if (provider_login != NULL) {
1245 cdb->prov_login = safe_calloc(1, sizeof (*cdb->prov_login));
1246 if (provider_login->username != NULL) {
1247 cdb->prov_login->username = safe_strdup(
1248 provider_login->username);
1249 }
1250 if (provider_login->password != NULL) {
1251 cdb->prov_login->password = safe_strdup(
1252 provider_login->password);
1253 }
1254 if (provider_login->cainfo != NULL) {
1255 cdb->prov_login->cainfo = safe_strdup(
1256 provider_login->cainfo);
1257 }
1258 }
1259 cdb->normalize_non_icao = B_TRUE;
1260 /* Default to 1/32 of physical memory, but no more than 256MB */
1261 cdb->load_limit = MIN(physmem() >> 5, 256 << 20);
1262 lacf_strlcpy(cdb->prov_name, provider_name, sizeof (cdb->prov_name));
1263
1264 list_create(&cdb->loader_queue, sizeof (chart_t),
1265 offsetof(chart_t, loader_node));
1266 list_create(&cdb->loader_arpt_queue, sizeof (chart_arpt_t),
1267 offsetof(chart_arpt_t, loader_node));
1268 list_create(&cdb->load_seq, sizeof (chart_t),
1269 offsetof(chart_t, load_seq_node));
1270
1271 worker_init2(&cdb->loader, loader_init, loader, loader_fini, 0, cdb,
1272 "chartdb");
1273
1274 return (cdb);
1275}
1276
1277void
1278chartdb_fini(chartdb_t *cdb)
1279{
1280 void *cookie;
1281 chart_arpt_t *arpt;
1282
1283 worker_fini(&cdb->loader);
1284
1285 while(list_remove_head(&cdb->load_seq) != NULL)
1286 ;
1287 list_destroy(&cdb->load_seq);
1288 while(list_remove_head(&cdb->loader_queue) != NULL)
1289 ;
1290 list_destroy(&cdb->loader_queue);
1291 while(list_remove_head(&cdb->loader_arpt_queue) != NULL)
1292 ;
1293 list_destroy(&cdb->loader_arpt_queue);
1294
1295 cookie = NULL;
1296 while ((arpt = avl_destroy_nodes(&cdb->arpts, &cookie)) != NULL)
1297 arpt_destroy(arpt);
1298 avl_destroy(&cdb->arpts);
1299 mutex_destroy(&cdb->lock);
1300
1301 free(cdb->proxy);
1302 free(cdb->path);
1303 free(cdb->pdftoppm_path);
1304 free(cdb->pdfinfo_path);
1305 if (cdb->prov_login != NULL) {
1306 if (cdb->prov_login->username != NULL) {
1307 ZERO_FREE_N(cdb->prov_login->username,
1308 strlen(cdb->prov_login->username));
1309 }
1310 if (cdb->prov_login->password != NULL) {
1311 ZERO_FREE_N(cdb->prov_login->password,
1312 strlen(cdb->prov_login->password));
1313 }
1314 if (cdb->prov_login->cainfo != NULL) {
1315 ZERO_FREE_N(cdb->prov_login->cainfo,
1316 strlen(cdb->prov_login->cainfo));
1317 }
1318 ZERO_FREE(cdb->prov_login);
1319 }
1320 ZERO_FREE(cdb);
1321}
1322
1323bool_t
1324chartdb_test_connection(const char *provider_name,
1325 const chart_prov_info_login_t *creds)
1326{
1327 return (chartdb_test_connection2(provider_name, creds, NULL));
1328}
1329
1330bool_t
1331chartdb_test_connection2(const char *provider_name,
1332 const chart_prov_info_login_t *creds, const char *proxy)
1333{
1334 ASSERT(provider_name != NULL);
1335 /* creds can be NULL */
1336 /* proxy can be NULL */
1337
1338 for (chart_prov_id_t pid = 0; pid < NUM_PROVIDERS; pid++) {
1339 if (strcmp(provider_name, prov[pid].name) == 0) {
1340 if (prov[pid].test_conn == NULL)
1341 return (B_TRUE);
1342 return (prov[pid].test_conn(creds, proxy));
1343 }
1344 }
1345 return (B_FALSE);
1346}
1347
1348void
1349chartdb_set_load_limit(chartdb_t *cdb, uint64_t bytes)
1350{
1351 bytes = MAX(bytes, 16 << 20);
1352 if (cdb->load_limit != bytes) {
1353 cdb->load_limit = bytes;
1354 worker_wake_up(&cdb->loader);
1355 }
1356}
1357
1358void
1359chartdb_purge(chartdb_t *cdb)
1360{
1361 mutex_enter(&cdb->lock);
1362
1363 /* purge the queue */
1364 while (list_remove_head(&cdb->loader_queue) != NULL)
1365 ;
1366 list_insert_tail(&cdb->loader_queue, &cdb->loader_cmd_purge);
1367 worker_wake_up(&cdb->loader);
1368
1369 mutex_exit(&cdb->lock);
1370}
1371
1372void
1373chartdb_set_proxy(chartdb_t *cdb, const char *proxy)
1374{
1375 ASSERT(cdb != NULL);
1376
1377 mutex_enter(&cdb->lock);
1378 LACF_DESTROY(cdb->proxy);
1379 if (proxy != NULL)
1380 cdb->proxy = safe_strdup(proxy);
1381 mutex_exit(&cdb->lock);
1382}
1383
1384size_t
1385chartdb_get_proxy(chartdb_t *cdb, char *proxy, size_t cap)
1386{
1387 size_t len;
1388
1389 ASSERT(cdb != NULL);
1390 ASSERT(proxy != NULL || cap == 0);
1391
1392 mutex_enter(&cdb->lock);
1393 if (cdb->proxy != NULL) {
1394 len = strlen(cdb->proxy) + 1;
1395 lacf_strlcpy(proxy, cdb->proxy, cap);
1396 } else {
1397 len = 0;
1398 lacf_strlcpy(proxy, "", cap);
1399 }
1400 mutex_exit(&cdb->lock);
1401
1402 return (len);
1403}
1404
1405char **
1406chartdb_get_chart_names(chartdb_t *cdb, const char *icao, chart_type_t type,
1407 size_t *num_charts)
1408{
1409 chart_arpt_t *arpt;
1410 char **charts;
1411 chart_t *chart;
1412 size_t i, num;
1413
1414 mutex_enter(&cdb->lock);
1415
1416 arpt = arpt_find(cdb, icao);
1417 if (arpt == NULL) {
1418 mutex_exit(&cdb->lock);
1419 *num_charts = 0;
1420 return (NULL);
1421 }
1422 if (!arpt->load_complete) {
1423 if (!list_link_active(&arpt->loader_node)) {
1424 list_insert_tail(&cdb->loader_arpt_queue, arpt);
1425 /*
1426 * If an airport change has been detected, dump
1427 * everything the loader is doing and try and get
1428 * the airport load in as quickly as possible.
1429 */
1430 while (list_remove_head(&cdb->loader_queue) != NULL)
1431 ;
1432 worker_wake_up(&cdb->loader);
1433 }
1434 mutex_exit(&cdb->lock);
1435 *num_charts = 0;
1436 return (NULL);
1437 }
1438 for (chart = avl_first(&arpt->charts), num = 0; chart != NULL;
1439 chart = AVL_NEXT(&arpt->charts, chart)) {
1440 if ((chart->type & type) != 0)
1441 num++;
1442 }
1443 if (num == 0) {
1444 mutex_exit(&cdb->lock);
1445 *num_charts = 0;
1446 return (NULL);
1447 }
1448 charts = safe_calloc(num, sizeof (*charts));
1449 for (chart = avl_first(&arpt->charts), i = 0; chart != NULL;
1450 chart = AVL_NEXT(&arpt->charts, chart)) {
1451 if ((chart->type & type) != 0) {
1452 ASSERT3U(i, <, num);
1453 ASSERT(chart->name[0] != '\0');
1454 charts[i] = safe_strdup(chart->name);
1455 i++;
1456 }
1457 }
1458 if (cdb->chart_sort_func != NULL) {
1459 lacf_qsort_r(charts, num, sizeof (*charts),
1460 cdb->chart_sort_func, cdb);
1461 }
1462
1463 mutex_exit(&cdb->lock);
1464
1465 *num_charts = num;
1466
1467 return (charts);
1468}
1469
1470void
1471chartdb_free_str_list(char **l, size_t num)
1472{
1473 free_strlist(l, num);
1474}
1475
1476static chart_arpt_t *
1477arpt_find(chartdb_t *cdb, const char *icao)
1478{
1479 chart_arpt_t srch = {};
1480 chart_arpt_t *arpt;
1481
1482 ASSERT(icao != NULL);
1483
1484 if (cdb->normalize_non_icao) {
1485 switch (strlen(icao)) {
1486 case 3:
1487 /*
1488 * In the US it's common to omit the leading 'K',
1489 * especially for non-ICAO airports. Adapt to them.
1490 */
1491 snprintf(srch.icao, sizeof (srch.icao), "K%s", icao);
1492 break;
1493 case 4:
1494 lacf_strlcpy(srch.icao, icao, sizeof (srch.icao));
1495 break;
1496 default:
1497 return (NULL);
1498 }
1499 } else {
1500 lacf_strlcpy(srch.icao, icao, sizeof (srch.icao));
1501 }
1502 arpt = avl_find(&cdb->arpts, &srch, NULL);
1503 if (arpt == NULL && prov[cdb->prov].arpt_lazy_discover != NULL)
1504 arpt = prov[cdb->prov].arpt_lazy_discover(cdb, icao);
1505 return (arpt);
1506}
1507
1508static chart_t *
1509chart_find(chartdb_t *cdb, const char *icao, const char *chart_name)
1510{
1511 chart_arpt_t *arpt = arpt_find(cdb, icao);
1512 chart_t srch_chart = { .name = (char *)chart_name };
1513 if (arpt == NULL)
1514 return (NULL);
1515 return (avl_find(&arpt->charts, &srch_chart, NULL));
1516}
1517
1518char *
1519chartdb_get_chart_codename(chartdb_t *cdb, const char *icao,
1520 const char *chart_name)
1521{
1522 chart_t *chart;
1523 char *codename = NULL;
1524
1525 ASSERT(cdb != NULL);
1526 ASSERT(icao != NULL);
1527 ASSERT(chart_name != NULL);
1528
1529 mutex_enter(&cdb->lock);
1530
1531 chart = chart_find(cdb, icao, chart_name);
1532 if (chart == NULL || chart->load_error) {
1533 mutex_exit(&cdb->lock);
1534 return (NULL);
1535 }
1536 if (chart->codename != NULL)
1537 codename = safe_strdup(chart->codename);
1538 mutex_exit(&cdb->lock);
1539
1540 return (codename);
1541}
1542
1543chart_type_t
1544chartdb_get_chart_type(chartdb_t *cdb, const char *icao, const char *chart_name)
1545{
1546 chart_t *chart;
1547 chart_type_t type;
1548
1549 ASSERT(cdb != NULL);
1550 ASSERT(icao != NULL);
1551 ASSERT(chart_name != NULL);
1552
1553 mutex_enter(&cdb->lock);
1554 chart = chart_find(cdb, icao, chart_name);
1555 if (chart == NULL || chart->load_error) {
1556 mutex_exit(&cdb->lock);
1557 return (CHART_TYPE_UNKNOWN);
1558 }
1559 type = chart->type;
1560 mutex_exit(&cdb->lock);
1561
1562 return (type);
1563}
1564
1566chartdb_get_chart_georef(chartdb_t *cdb, const char *icao,
1567 const char *chart_name)
1568{
1569 chart_t *chart;
1570 chart_georef_t georef;
1571
1572 ASSERT(cdb != NULL);
1573 ASSERT(icao != NULL);
1574 ASSERT(chart_name != NULL);
1575
1576 mutex_enter(&cdb->lock);
1577 chart = chart_find(cdb, icao, chart_name);
1578 if (chart == NULL || chart->load_error) {
1579 mutex_exit(&cdb->lock);
1580 return ((chart_georef_t){});
1581 }
1582 georef = chart->georef;
1583 mutex_exit(&cdb->lock);
1584
1585 return (georef);
1586}
1587
1589chartdb_get_chart_view(chartdb_t *cdb, const char *icao,
1590 const char *chart_name, chart_view_t view)
1591{
1592 chart_t *chart;
1593 chart_bbox_t bbox;
1594
1595 ASSERT(cdb != NULL);
1596 ASSERT(icao != NULL);
1597 ASSERT(chart_name != NULL);
1598 ASSERT3U(view, <, ARRAY_NUM_ELEM(chart->views));
1599
1600 mutex_enter(&cdb->lock);
1601 chart = chart_find(cdb, icao, chart_name);
1602 if (chart == NULL || chart->load_error) {
1603 mutex_exit(&cdb->lock);
1604 return ((chart_bbox_t){});
1605 }
1606 bbox = chart->views[view];
1607 mutex_exit(&cdb->lock);
1608
1609 return (bbox);
1610}
1611
1613chartdb_get_chart_procs(chartdb_t *cdb, const char *icao,
1614 const char *chart_name)
1615{
1616 chart_t *chart;
1617 chart_procs_t procs;
1618
1619 ASSERT(cdb != NULL);
1620 ASSERT(icao != NULL);
1621 ASSERT(chart_name != NULL);
1622
1623 mutex_enter(&cdb->lock);
1624 chart = chart_find(cdb, icao, chart_name);
1625 if (chart == NULL || chart->load_error) {
1626 mutex_exit(&cdb->lock);
1627 return ((chart_procs_t){});
1628 }
1629 procs = chart->procs;
1630 mutex_exit(&cdb->lock);
1631
1632 return (procs);
1633}
1634
1635bool_t
1636chartdb_get_chart_surface(chartdb_t *cdb, const char *icao,
1637 const char *chart_name, int page, double zoom, bool_t night,
1638 cairo_surface_t **surf, int *num_pages)
1639{
1640 chart_t *chart;
1641
1642 ASSERT(cdb != NULL);
1643 ASSERT(icao != NULL);
1644 ASSERT(chart_name != NULL);
1645 ASSERT(surf != NULL);
1646 /* num_pages can be NULL */
1647
1648 *surf = NULL;
1649 if (num_pages != NULL)
1650 *num_pages = 0;
1651
1652 mutex_enter(&cdb->lock);
1653
1654 chart = chart_find(cdb, icao, chart_name);
1655 if (chart == NULL || chart->load_error) {
1656 mutex_exit(&cdb->lock);
1657 return (B_FALSE);
1658 }
1659
1660 if ((chart->surf == NULL || chart->zoom != zoom ||
1661 chart->night != night || chart->cur_page != page) &&
1662 !list_link_active(&chart->loader_node)) {
1663 chart->zoom = zoom;
1664 chart->load_page = page;
1665 chart->night = night;
1666 CAIRO_SURFACE_DESTROY(chart->surf);
1667 /*
1668 * Dump everything else in the queue so we get in first.
1669 */
1670 while (list_remove_head(&cdb->loader_queue) != NULL)
1671 ;
1672 list_insert_tail(&cdb->loader_queue, chart);
1673 worker_wake_up(&cdb->loader);
1674 }
1675
1676 if (chart->surf != NULL && page == chart->cur_page &&
1677 chart->night == night) {
1678 *surf = cairo_surface_reference(chart->surf);
1679 } else {
1680 *surf = NULL;
1681 }
1682 if (num_pages != NULL)
1683 *num_pages = chart->num_pages;
1684
1685 mutex_exit(&cdb->lock);
1686
1687 return (B_TRUE);
1688}
1689
1690static char *
1691get_metar_taf_common(chartdb_t *cdb, const char *icao, bool_t metar)
1692{
1693 char *result = NULL;
1694 time_t now = time(NULL);
1695 chart_arpt_t *arpt;
1696
1697 mutex_enter(&cdb->lock);
1698
1699 arpt = arpt_find(cdb, icao);
1700 if (arpt == NULL) {
1701 mutex_exit(&cdb->lock);
1702 return (NULL);
1703 }
1704
1705 /*
1706 * We could have NULLs in the cache here if the download
1707 * failed. In that case, wait a little before retrying
1708 * another download.
1709 */
1710 if (metar && now - arpt->metar_load_t < MAX_METAR_AGE) {
1711 /* Fresh METAR still cached, return that. */
1712 if (arpt->metar != NULL)
1713 result = safe_strdup(arpt->metar);
1714 } else if (!metar && now - arpt->taf_load_t < MAX_TAF_AGE) {
1715 /* Fresh TAF still cached, return that. */
1716 if (arpt->taf != NULL)
1717 result = safe_strdup(arpt->taf);
1718 } else {
1719 if (metar) {
1720 if (!list_link_active(
1721 &cdb->loader_cmd_metar.loader_node)) {
1722 /* Initiate async download of METAR */
1723 cdb->loader_cmd_metar.arpt = arpt;
1724 list_insert_tail(&cdb->loader_queue,
1725 &cdb->loader_cmd_metar);
1726 worker_wake_up(&cdb->loader);
1727 }
1728 /* If we have an old METAR, return that for now */
1729 if (arpt->metar != NULL)
1730 result = safe_strdup(arpt->metar);
1731 } else {
1732 if (!list_link_active(
1733 &cdb->loader_cmd_taf.loader_node)) {
1734 /* Initiate async download of TAF */
1735 cdb->loader_cmd_taf.arpt = arpt;
1736 list_insert_tail(&cdb->loader_queue,
1737 &cdb->loader_cmd_taf);
1738 worker_wake_up(&cdb->loader);
1739 }
1740 /* If we have an old TAF, return that for now */
1741 if (arpt->taf != NULL)
1742 result = safe_strdup(arpt->taf);
1743 }
1744 }
1745
1746 mutex_exit(&cdb->lock);
1747 return (result);
1748}
1749
1750bool_t
1751chartdb_is_ready(chartdb_t *cdb)
1752{
1753 bool_t init_complete;
1754 mutex_enter(&cdb->lock);
1755 init_complete = cdb->init_complete;
1756 mutex_exit(&cdb->lock);
1757 return (init_complete);
1758}
1759
1760bool_t
1761chartdb_is_arpt_known(chartdb_t *cdb, const char *icao)
1762{
1763 chart_arpt_t *arpt;
1764 mutex_enter(&cdb->lock);
1765 arpt = arpt_find(cdb, icao);
1766 mutex_exit(&cdb->lock);
1767 return (arpt != NULL);
1768}
1769
1770#define ARPT_GET_COMMON(field_name) \
1771 do { \
1772 chart_arpt_t *arpt; \
1773 char *field_name; \
1774 mutex_enter(&cdb->lock); \
1775 arpt = arpt_find(cdb, icao); \
1776 if (arpt == NULL) { \
1777 mutex_exit(&cdb->lock); \
1778 return (NULL); \
1779 } \
1780 field_name = safe_strdup(arpt->field_name); \
1781 mutex_exit(&cdb->lock); \
1782 return (field_name); \
1783 } while (0)
1784
1785char *
1786chartdb_get_arpt_name(chartdb_t *cdb, const char *icao)
1787{
1788 ARPT_GET_COMMON(name);
1789}
1790
1791char *
1792chartdb_get_arpt_city(chartdb_t *cdb, const char *icao)
1793{
1794 ARPT_GET_COMMON(city);
1795}
1796
1797char *
1798chartdb_get_arpt_state(chartdb_t *cdb, const char *icao)
1799{
1800 ARPT_GET_COMMON(state);
1801}
1802
1803char *
1804chartdb_get_metar(chartdb_t *cdb, const char *icao)
1805{
1806 return (get_metar_taf_common(cdb, icao, B_TRUE));
1807}
1808
1809char *
1810chartdb_get_taf(chartdb_t *cdb, const char *icao)
1811{
1812 return (get_metar_taf_common(cdb, icao, B_FALSE));
1813}
1814
1815static char *
1816download_metar_taf_common(chartdb_t *cdb, const char *icao, const char *source,
1817 const char *node_name)
1818{
1819 chart_dl_info_t info;
1820 char url[256];
1821 char error_reason[128];
1822 xmlDoc *doc = NULL;
1823 xmlXPathContext *xpath_ctx = NULL;
1824 xmlXPathObject *xpath_obj = NULL;
1825 char query[128];
1826 char *result;
1827 chart_prov_info_login_t login = { .username = NULL };
1828
1829 if (strcmp(source, "metars") == 0) {
1830 snprintf(url, sizeof (url), "https://aviationweather.gov/api/"
1831 "data/metar?format=xml&ids=%s&taf=false&hours=2", icao);
1832 } else if (strcmp(source, "tafs") == 0) {
1833 snprintf(url, sizeof (url), "https://aviationweather.gov/api/"
1834 "data/taf?format=xml&ids=%s&metar=false", icao);
1835 } else {
1836 VERIFY_FAIL();
1837 }
1838 snprintf(error_reason, sizeof (error_reason), "Error downloading %s",
1839 node_name);
1840 snprintf(query, sizeof (query), "/response/data/%s/raw_text",
1841 node_name);
1842
1843 if (cdb->prov_login != NULL) {
1844 /*
1845 * If the caller supplied a CAINFO path, we ONLY want to use
1846 * that for the METAR download. We do NOT want to send in any
1847 * user credentials, which might be meant for the main chart
1848 * data provider.
1849 */
1850 login.cainfo = cdb->prov_login->cainfo;
1851 }
1852
1853 if (!chart_download(cdb, url, NULL, &login, error_reason, &info))
1854 return (NULL);
1855 doc = xmlParseMemory((char *)info.buf, info.bufsz);
1856 if (doc == NULL) {
1857 logMsg("Error parsing %s: XML parsing error", node_name);
1858 goto errout;
1859 }
1860 xpath_ctx = xmlXPathNewContext(doc);
1861 if (xpath_ctx == NULL) {
1862 logMsg("Error creating XPath context for XML");
1863 goto errout;
1864 }
1865 xpath_obj = xmlXPathEvalExpression((xmlChar *)query, xpath_ctx);
1866 if (xpath_obj->nodesetval->nodeNr == 0 ||
1867 xpath_obj->nodesetval->nodeTab[0]->children == NULL ||
1868 xpath_obj->nodesetval->nodeTab[0]->children->content == NULL) {
1869 char *path = mkpathname(cdb->path, "metar.xml", NULL);
1870
1871 logMsg("Error parsing %s, valid but incorrect XML structure. "
1872 "For debugging purposes, I'm going to dump the raw data "
1873 "into a file named %s.", node_name, path);
1874 FILE *fp = fopen(path, "wb");
1875 if (fp != NULL) {
1876 fwrite(info.buf, 1, info.bufsz, fp);
1877 fclose(fp);
1878 }
1879 free(path);
1880 goto errout;
1881 }
1882 result = safe_strdup(
1883 (char *)xpath_obj->nodesetval->nodeTab[0]->children->content);
1884 xmlXPathFreeObject(xpath_obj);
1885 xmlXPathFreeContext(xpath_ctx);
1886 xmlFreeDoc(doc);
1887 free(info.buf);
1888 return (result);
1889errout:
1890 if (xpath_obj != NULL)
1891 xmlXPathFreeObject(xpath_obj);
1892 if (xpath_ctx != NULL)
1893 xmlXPathFreeContext(xpath_ctx);
1894 if (doc != NULL)
1895 xmlFreeDoc(doc);
1896 free(info.buf);
1897
1898 return (NULL);
1899}
1900
1901static char *
1902download_metar(chartdb_t *cdb, const char *icao)
1903{
1904 return (download_metar_taf_common(cdb, icao, "metars", "METAR"));
1905}
1906
1907static char *
1908download_taf(chartdb_t *cdb, const char *icao)
1909{
1910 return (download_metar_taf_common(cdb, icao, "tafs", "TAF"));
1911}
1912
1913bool_t
1915{
1916 ASSERT(cdb != NULL);
1917 if (prov[cdb->prov].pending_ext_account_setup != NULL)
1918 return (prov[cdb->prov].pending_ext_account_setup(cdb));
1919 else
1920 return (B_FALSE);
1921}
#define VERIFY_FAIL()
Definition assert.h:160
#define VERIFY(x)
Definition assert.h:78
#define ASSERT3U(x, op, y)
Definition assert.h:210
#define ASSERT(x)
Definition assert.h:208
void * avl_destroy_nodes(avl_tree_t *tree, void **cookie)
Definition avl.c:938
void * avl_first(const avl_tree_t *tree)
Definition avl.c:172
uintptr_t avl_index_t
Definition avl.h:119
void avl_insert(avl_tree_t *tree, void *node, avl_index_t where)
Definition avl.c:471
void avl_create(avl_tree_t *tree, int(*compar)(const void *, const void *), size_t size, size_t offset)
Definition avl.c:867
#define AVL_NEXT(tree, node)
Definition avl.h:212
void avl_destroy(avl_tree_t *tree)
Definition avl.c:890
void * avl_find(const avl_tree_t *tree, const void *node, avl_index_t *where)
Definition avl.c:244
#define CAIRO_SURFACE_DESTROY(surf)
Definition cairo_utils.h:45
char * chartdb_get_chart_codename(chartdb_t *cdb, const char *icao, const char *chart_name)
Definition chartdb.c:1519
bool_t chartdb_pending_ext_account_setup(chartdb_t *cdb)
Definition chartdb.c:1914
chart_georef_t chartdb_get_chart_georef(chartdb_t *cdb, const char *icao, const char *chart_name)
Definition chartdb.c:1566
bool_t chartdb_test_connection2(const char *provider_name, const chart_prov_info_login_t *creds, const char *proxy)
Definition chartdb.c:1331
char * chartdb_get_taf(chartdb_t *cdb, const char *icao)
Definition chartdb.c:1810
char * chartdb_get_arpt_city(chartdb_t *cdb, const char *icao)
Definition chartdb.c:1792
bool_t chartdb_is_ready(chartdb_t *cdb)
Definition chartdb.c:1751
char * chartdb_get_metar(chartdb_t *cdb, const char *icao)
Definition chartdb.c:1804
chartdb_t * chartdb_init(const char *cache_path, const char *pdftoppm_path, const char *pdfinfo_path, unsigned airac, const char *provider_name, const chart_prov_info_login_t *provider_login)
Definition chartdb.c:1212
void chartdb_fini(chartdb_t *cdb)
Definition chartdb.c:1278
chart_bbox_t chartdb_get_chart_view(chartdb_t *cdb, const char *icao, const char *chart_name, chart_view_t view)
Definition chartdb.c:1589
chart_type_t chartdb_get_chart_type(chartdb_t *cdb, const char *icao, const char *chart_name)
Definition chartdb.c:1544
size_t chartdb_get_proxy(chartdb_t *cdb, char *proxy, size_t cap)
Definition chartdb.c:1385
char * chartdb_get_arpt_name(chartdb_t *cdb, const char *icao)
Definition chartdb.c:1786
chart_view_t
Definition chartdb.h:141
void chartdb_free_str_list(char **name_list, size_t num)
Definition chartdb.c:1471
bool_t chartdb_test_connection(const char *provider_name, const chart_prov_info_login_t *creds)
Definition chartdb.c:1324
char * chartdb_get_arpt_state(chartdb_t *cdb, const char *icao)
Definition chartdb.c:1798
char ** chartdb_get_chart_names(chartdb_t *cdb, const char *icao, chart_type_t type, size_t *num_charts)
Definition chartdb.c:1406
chart_procs_t chartdb_get_chart_procs(chartdb_t *cdb, const char *icao, const char *chart_name)
Definition chartdb.c:1613
void chartdb_set_proxy(chartdb_t *cdb, const char *proxy)
Definition chartdb.c:1373
void chartdb_set_load_limit(chartdb_t *cdb, uint64_t bytes)
Definition chartdb.c:1349
bool_t chartdb_is_arpt_known(chartdb_t *cdb, const char *icao)
Definition chartdb.c:1761
void chartdb_purge(chartdb_t *cdb)
Definition chartdb.c:1359
bool_t chartdb_get_chart_surface(chartdb_t *cdb, const char *icao, const char *chart_name, int page, double zoom, bool_t night, cairo_surface_t **surf, int *num_pages)
Definition chartdb.c:1636
#define ARRAY_NUM_ELEM(_array)
Definition core.h:171
#define LACF_DESTROY(ptr)
Definition core.h:158
char * lacf_dirname(const char *filename)
Definition helpers.c:1489
char ** strsplit(const char *input, const char *sep, bool_t skip_empty, size_t *num)
Definition helpers.c:650
char * mkpathname(const char *comp,...)
Definition helpers.c:833
bool_t remove_directory(const char *dirname)
Definition helpers.c:1382
void lacf_qsort_r(void *base, size_t nmemb, size_t size, int(*compar)(const void *, const void *, void *), void *arg)
Definition helpers.c:1650
void lacf_strlcpy(char *dest, const char *src, size_t cap)
Definition helpers.c:1667
void free_strlist(char **comps, size_t num)
Definition helpers.c:703
bool_t file_exists(const char *path, bool_t *isdir)
Definition helpers.c:1222
int list_link_active(const list_node_t *)
Definition list.c:525
void * list_tail(const list_t *)
Definition list.c:304
void list_destroy(list_t *)
Definition list.c:136
void * list_head(const list_t *)
Definition list.c:292
void list_create(list_t *, size_t, size_t)
Definition list.c:113
void list_insert_head(list_t *, void *)
Definition list.c:199
void * list_next(const list_t *, const void *)
Definition list.c:344
size_t list_count(const list_t *)
Definition list.c:543
void list_remove(list_t *, void *)
Definition list.c:226
void * list_remove_head(list_t *)
Definition list.c:251
void list_insert_tail(list_t *, void *)
Definition list.c:213
#define logMsg(...)
Definition log.h:112
static double clamp(double x, double min_val, double max_val)
Definition math_core.h:60
static void strip_space(char *line)
static void * safe_realloc(void *oldptr, size_t size)
Definition safe_alloc.h:86
#define ZERO_FREE(ptr)
Definition safe_alloc.h:253
static char * safe_strdup(const char *str2)
Definition safe_alloc.h:201
static void * safe_calloc(size_t nmemb, size_t size)
Definition safe_alloc.h:71
#define ZERO_FREE_N(ptr, num)
Definition safe_alloc.h:275
static void mutex_destroy(mutex_t *mtx)
Definition thread.h:499
static void mutex_enter(mutex_t *mtx)
Definition thread.h:530
static void mutex_exit(mutex_t *mtx)
Definition thread.h:556
static void mutex_init(mutex_t *mtx)
Definition thread.h:488