Ajla 0.1.0
[ajla.git] / resolver.c
blob3cb94e7b2d68565e21b558d97c27ec261f637d9b
1 /*
2 * Copyright (C) 2024 Mikulas Patocka
4 * This file is part of Ajla.
6 * Ajla is free software: you can redistribute it and/or modify it under the
7 * terms of the GNU General Public License as published by the Free Software
8 * Foundation, either version 3 of the License, or (at your option) any later
9 * version.
11 * Ajla is distributed in the hope that it will be useful, but WITHOUT ANY
12 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
13 * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along with
16 * Ajla. If not, see <https://www.gnu.org/licenses/>.
19 #include "ajla.h"
21 #include "mem_al.h"
22 #include "str.h"
23 #include "list.h"
24 #include "tree.h"
25 #include "thread.h"
26 #include "os.h"
28 #include "resolver.h"
30 /*#include <unistd.h>*/
32 #ifndef PIPE_BUF
33 #define PIPE_BUF 512
34 #endif
36 #define RESOLVER_THREADS_MAX 4
38 #define RESOLVER_WORK_GETADDRINFO 1
39 #define RESOLVER_WORK_GETNAMEINFO 2
41 struct resolver_request {
42 struct list entry;
43 handle_t p;
44 char type;
45 int port;
46 size_t name_len;
47 char name[FLEXIBLE_ARRAY];
50 #ifndef THREAD_NONE
51 static struct list resolver_queue;
52 static uchar_efficient_t resolver_end;
53 static cond_t resolver_cond;
54 static thread_t *resolver_threads;
55 static size_t resolver_threads_l;
56 static size_t resolver_threads_idle;
57 #endif
59 static void resolver_free_result(struct address *result, size_t result_l)
61 size_t i;
62 for (i = 0; i < result_l; i++)
63 mem_free(result[i].address);
64 mem_free(result);
67 static int address_compare(const struct tree_entry *e1, uintptr_t e2)
69 struct address *addr1 = get_struct(e1, struct address, entry);
70 struct address *addr2 = num_to_ptr(e2);
71 if (unlikely(addr1->address_length < addr2->address_length))
72 return -1;
73 if (unlikely(addr1->address_length > addr2->address_length))
74 return 1;
75 return memcmp(addr1->address, addr2->address, addr1->address_length);
78 static void resolver_do_lookup(struct resolver_request *work)
80 ajla_error_t err;
81 char error_record[9];
82 struct address *result = NULL;
83 size_t result_l = 0; /* avoid warning */
84 size_t i;
85 char *str = NULL;
86 size_t str_l;
87 struct tree dedup_tree;
88 char *name;
89 size_t l;
91 if (unlikely(os_write(work->p, "", 1, &err) != 1))
92 return;
94 if (unlikely(!array_init_mayfail(char, &str, &str_l, &err)))
95 goto fail;
97 if (unlikely(!array_add_mayfail(char, &str, &str_l, 0, NULL, &err)))
98 goto fail;
100 switch (work->type) {
101 case RESOLVER_WORK_GETADDRINFO:
102 if (unlikely(!os_getaddrinfo(work->name, work->port, &result, &result_l, &err)))
103 goto fail;
104 tree_init(&dedup_tree);
105 for (i = 0; i < result_l; i++) {
106 struct address *addr = &result[i];
107 char c[2];
108 struct tree_insert_position pos;
110 if (unlikely(tree_find_for_insert(&dedup_tree, address_compare, ptr_to_num(addr), &pos) != NULL))
111 continue;
112 #ifdef THREAD_NONE
113 if (str_l + 4 + addr->address_length > PIPE_BUF - 1)
114 continue;
115 #endif
116 tree_insert_after_find(&addr->entry, &pos);
118 c[0] = addr->address_length;
119 c[1] = addr->address_length >> 8;
120 if (unlikely(!array_add_multiple_mayfail(char, &str, &str_l, c, 2, NULL, &err)))
121 goto fail;
122 if (unlikely(!array_add_multiple_mayfail(char, &str, &str_l, addr->address, addr->address_length, NULL, &err)))
123 goto fail;
125 resolver_free_result(result, result_l);
126 break;
127 case RESOLVER_WORK_GETNAMEINFO:
128 name = os_getnameinfo(cast_ptr(unsigned char *, work->name), work->name_len, &err);
129 if (unlikely(!name))
130 goto fail;
131 l = strlen(name);
132 if (unlikely(!array_add_multiple_mayfail(char, &str, &str_l, name, l, NULL, &err)))
133 goto fail;
134 mem_free(name);
135 break;
136 default:
137 internal(file_line, "resolver_do_lookup: invalid work type %d", work->type);
140 os_write(work->p, str, str_l, &err);
142 mem_free(str);
144 return;
146 fail:
147 if (result)
148 resolver_free_result(result, result_l);
149 if (str)
150 mem_free(str);
151 error_record[0] = 1;
152 error_record[1] = err.error_class;
153 error_record[2] = err.error_class >> 8;
154 error_record[3] = err.error_type;
155 error_record[4] = err.error_type >> 8;
156 error_record[5] = err.error_aux;
157 error_record[6] = err.error_aux >> 8;
158 error_record[7] = err.error_aux >> 16;
159 error_record[8] = err.error_aux >> 24;
160 os_write(work->p, error_record, 9, &err);
163 #ifndef THREAD_NONE
164 thread_function_decl(resolver_thread_function,
165 struct resolver_request *work;
166 uchar_efficient_t end;
168 next:
169 cond_lock(&resolver_cond);
170 while (list_is_empty(&resolver_queue)) {
171 if (resolver_end) {
172 cond_unlock(&resolver_cond);
173 goto ret;
175 resolver_threads_idle++;
176 cond_wait(&resolver_cond);
177 resolver_threads_idle--;
179 work = get_struct(resolver_queue.prev, struct resolver_request, entry);
180 list_del(&work->entry);
181 end = resolver_end;
182 cond_unlock(&resolver_cond);
184 if (unlikely(end))
185 goto close_next;
187 resolver_do_lookup(work);
189 close_next:
190 os_close(work->p);
191 mem_free(work);
192 goto next;
193 ret:;
195 #endif
197 static bool resolver_submit_work(struct resolver_request *work, ajla_error_t attr_unused *err)
199 #ifndef THREAD_NONE
200 cond_lock(&resolver_cond);
201 if (!resolver_threads_idle && resolver_threads_l < (os_getaddrinfo_is_thread_safe() ? RESOLVER_THREADS_MAX : 1)) {
202 thread_t resolver_thread;
203 if (unlikely(!thread_spawn(&resolver_thread, resolver_thread_function, NULL, PRIORITY_IO, err))) {
204 cond_unlock(&resolver_cond);
205 mem_free(work);
206 return false;
208 array_add(thread_t, &resolver_threads, &resolver_threads_l, resolver_thread);
210 list_add(&resolver_queue, &work->entry);
211 cond_unlock_signal(&resolver_cond);
212 #else
213 resolver_do_lookup(work);
214 os_close(work->p);
215 mem_free(work);
216 #endif
217 return true;
220 bool resolver_resolve(char *name, int port, handle_t p, ajla_error_t *err)
222 struct resolver_request *work;
224 if (unlikely(port < 0) || unlikely(port >= 65536)) {
225 fatal_mayfail(error_ajla(EC_SYNC, AJLA_ERROR_INVALID_OPERATION), err, "invalid port: %d", port);
226 return false;
229 work = struct_alloc_array_mayfail(mem_alloc_mayfail, struct resolver_request, name, strlen(name) + 1, err);
230 if (unlikely(!work))
231 return false;
233 work->p = p;
234 work->type = RESOLVER_WORK_GETADDRINFO;
235 work->port = port;
236 strcpy(work->name, name);
238 return resolver_submit_work(work, err);
241 bool resolver_resolve_reverse(char *addr, size_t addrlen, handle_t p, ajla_error_t *err)
243 struct resolver_request *work;
245 work = struct_alloc_array_mayfail(mem_alloc_mayfail, struct resolver_request, name, addrlen, err);
246 if (unlikely(!work))
247 return false;
249 work->p = p;
250 work->type = RESOLVER_WORK_GETNAMEINFO;
251 work->name_len = addrlen;
252 memcpy(work->name, addr, addrlen);
254 return resolver_submit_work(work, err);
257 void resolver_init(void)
259 #ifndef THREAD_NONE
260 list_init(&resolver_queue);
261 resolver_end = false;
262 cond_init(&resolver_cond);
263 array_init(thread_t, &resolver_threads, &resolver_threads_l);
264 resolver_threads_idle = 0;
265 #endif
268 void resolver_done(void)
270 #ifndef THREAD_NONE
271 size_t i;
272 cond_lock(&resolver_cond);
273 resolver_end = true;
274 cond_unlock_broadcast(&resolver_cond);
275 for (i = 0; i < resolver_threads_l; i++)
276 thread_join(&resolver_threads[i]);
277 mem_free(resolver_threads);
278 cond_done(&resolver_cond);
279 #endif