diff options
Diffstat (limited to 'src/client.c')
-rw-r--r-- | src/client.c | 1058 |
1 files changed, 1058 insertions, 0 deletions
diff --git a/src/client.c b/src/client.c new file mode 100644 index 0000000..dbbdd5f --- /dev/null +++ b/src/client.c | |||
@@ -0,0 +1,1058 @@ | |||
1 | /* | ||
2 | * client.c | ||
3 | * | ||
4 | * Copyright (C) 2009 Hector Martin <hector@marcansoft.com> | ||
5 | * Copyright (C) 2009 Nikias Bassen <nikias@gmx.li> | ||
6 | * | ||
7 | * This program is free software; you can redistribute it and/or modify | ||
8 | * it under the terms of the GNU General Public License as published by | ||
9 | * the Free Software Foundation, either version 2 or version 3. | ||
10 | * | ||
11 | * This program is distributed in the hope that it will be useful, | ||
12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
14 | * GNU General Public License for more details. | ||
15 | * | ||
16 | * You should have received a copy of the GNU General Public License | ||
17 | * along with this program; if not, write to the Free Software | ||
18 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA | ||
19 | */ | ||
20 | |||
21 | #ifdef HAVE_CONFIG_H | ||
22 | #include <config.h> | ||
23 | #endif | ||
24 | |||
25 | #define _GNU_SOURCE 1 | ||
26 | |||
27 | #include <stdlib.h> | ||
28 | #include <string.h> | ||
29 | #include <stdio.h> | ||
30 | #include <errno.h> | ||
31 | #include <unistd.h> | ||
32 | #include <sys/types.h> | ||
33 | #include <sys/socket.h> | ||
34 | #include <netinet/in.h> | ||
35 | #include <netinet/tcp.h> | ||
36 | #include <sys/un.h> | ||
37 | #include <arpa/inet.h> | ||
38 | #include <fcntl.h> | ||
39 | |||
40 | #include <plist/plist.h> | ||
41 | #include <libimobiledevice-glue/collection.h> | ||
42 | #include <libimobiledevice-glue/thread.h> | ||
43 | |||
44 | #include "log.h" | ||
45 | #include "usb.h" | ||
46 | #include "client.h" | ||
47 | #include "device.h" | ||
48 | #include "conf.h" | ||
49 | |||
50 | #define CMD_BUF_SIZE 0x10000 | ||
51 | #define REPLY_BUF_SIZE 0x10000 | ||
52 | |||
53 | enum client_state { | ||
54 | CLIENT_COMMAND, // waiting for command | ||
55 | CLIENT_LISTEN, // listening for devices | ||
56 | CLIENT_CONNECTING1, // issued connection request | ||
57 | CLIENT_CONNECTING2, // connection established, but waiting for response message to get sent | ||
58 | CLIENT_CONNECTED, // connected | ||
59 | CLIENT_DEAD | ||
60 | }; | ||
61 | |||
62 | struct mux_client { | ||
63 | int fd; | ||
64 | unsigned char *ob_buf; | ||
65 | uint32_t ob_size; | ||
66 | uint32_t ob_capacity; | ||
67 | unsigned char *ib_buf; | ||
68 | uint32_t ib_size; | ||
69 | uint32_t ib_capacity; | ||
70 | short events, devents; | ||
71 | uint32_t connect_tag; | ||
72 | int connect_device; | ||
73 | enum client_state state; | ||
74 | uint32_t proto_version; | ||
75 | uint32_t number; | ||
76 | plist_t info; | ||
77 | }; | ||
78 | |||
79 | static struct collection client_list; | ||
80 | mutex_t client_list_mutex; | ||
81 | static uint32_t client_number = 0; | ||
82 | |||
83 | #ifdef SO_PEERCRED | ||
84 | static char* _get_process_name_by_pid(const int pid) | ||
85 | { | ||
86 | char* name = (char*)calloc(1024, sizeof(char)); | ||
87 | if(name) { | ||
88 | sprintf(name, "/proc/%d/cmdline", pid); | ||
89 | FILE* f = fopen(name, "r"); | ||
90 | if(f) { | ||
91 | size_t size; | ||
92 | size = fread(name, sizeof(char), 1024, f); | ||
93 | if(size > 0) { | ||
94 | if('\n' == name[size-1]) | ||
95 | name[size-1]='\0'; | ||
96 | } | ||
97 | fclose(f); | ||
98 | } | ||
99 | } | ||
100 | return name; | ||
101 | } | ||
102 | #endif | ||
103 | |||
104 | /** | ||
105 | * Receive raw data from the client socket. | ||
106 | * | ||
107 | * @param client Client to read from. | ||
108 | * @param buffer Buffer to store incoming data. | ||
109 | * @param len Max number of bytes to read. | ||
110 | * @return Same as recv() system call. Number of bytes read; when < 0 errno will be set. | ||
111 | */ | ||
112 | int client_read(struct mux_client *client, void *buffer, uint32_t len) | ||
113 | { | ||
114 | usbmuxd_log(LL_SPEW, "client_read fd %d buf %p len %d", client->fd, buffer, len); | ||
115 | if(client->state != CLIENT_CONNECTED) { | ||
116 | usbmuxd_log(LL_ERROR, "Attempted to read from client %d not in CONNECTED state", client->fd); | ||
117 | return -1; | ||
118 | } | ||
119 | return recv(client->fd, buffer, len, 0); | ||
120 | } | ||
121 | |||
122 | /** | ||
123 | * Send raw data to the client socket. | ||
124 | * | ||
125 | * @param client Client to send to. | ||
126 | * @param buffer The data to send. | ||
127 | * @param len Number of bytes to write. | ||
128 | * @return Same as system call send(). Number of bytes written; when < 0 errno will be set. | ||
129 | */ | ||
130 | int client_write(struct mux_client *client, void *buffer, uint32_t len) | ||
131 | { | ||
132 | int sret = -1; | ||
133 | |||
134 | usbmuxd_log(LL_SPEW, "client_write fd %d buf %p len %d", client->fd, buffer, len); | ||
135 | if(client->state != CLIENT_CONNECTED) { | ||
136 | usbmuxd_log(LL_ERROR, "Attempted to write to client %d not in CONNECTED state", client->fd); | ||
137 | return -1; | ||
138 | } | ||
139 | |||
140 | sret = send(client->fd, buffer, len, 0); | ||
141 | if (sret < 0) { | ||
142 | if ((errno == EAGAIN) || (errno == EWOULDBLOCK)) { | ||
143 | usbmuxd_log(LL_DEBUG, "client_write: fd %d not ready for writing", client->fd); | ||
144 | sret = 0; | ||
145 | } else { | ||
146 | usbmuxd_log(LL_ERROR, "ERROR: client_write: sending to fd %d failed: %s", client->fd, strerror(errno)); | ||
147 | } | ||
148 | } | ||
149 | return sret; | ||
150 | } | ||
151 | |||
152 | /** | ||
153 | * Set event mask to use for ppoll()ing the client socket. | ||
154 | * Typically POLLOUT and/or POLLIN. Note that this overrides | ||
155 | * the current mask, that is, it is not ORing the argument | ||
156 | * into the current mask. | ||
157 | * | ||
158 | * @param client The client to set the event mask on. | ||
159 | * @param events The event mask to sert. | ||
160 | * @return 0 on success, -1 on error. | ||
161 | */ | ||
162 | int client_set_events(struct mux_client *client, short events) | ||
163 | { | ||
164 | if((client->state != CLIENT_CONNECTED) && (client->state != CLIENT_CONNECTING2)) { | ||
165 | usbmuxd_log(LL_ERROR, "client_set_events to client %d not in CONNECTED state", client->fd); | ||
166 | return -1; | ||
167 | } | ||
168 | client->devents = events; | ||
169 | if(client->state == CLIENT_CONNECTED) | ||
170 | client->events = events; | ||
171 | return 0; | ||
172 | } | ||
173 | |||
174 | /** | ||
175 | * Wait for an inbound connection on the usbmuxd socket | ||
176 | * and create a new mux_client instance for it, and store | ||
177 | * the client in the client list. | ||
178 | * | ||
179 | * @param listenfd the socket fd to accept() on. | ||
180 | * @return The connection fd for the client, or < 0 for error | ||
181 | * in which case errno will be set. | ||
182 | */ | ||
183 | int client_accept(int listenfd) | ||
184 | { | ||
185 | struct sockaddr_un addr; | ||
186 | int cfd; | ||
187 | socklen_t len = sizeof(struct sockaddr_un); | ||
188 | cfd = accept(listenfd, (struct sockaddr *)&addr, &len); | ||
189 | if (cfd < 0) { | ||
190 | usbmuxd_log(LL_ERROR, "accept() failed (%s)", strerror(errno)); | ||
191 | return cfd; | ||
192 | } | ||
193 | |||
194 | int flags = fcntl(cfd, F_GETFL, 0); | ||
195 | if (flags < 0) { | ||
196 | usbmuxd_log(LL_ERROR, "ERROR: Could not get socket flags!"); | ||
197 | } else { | ||
198 | if (fcntl(cfd, F_SETFL, flags | O_NONBLOCK) < 0) { | ||
199 | usbmuxd_log(LL_ERROR, "ERROR: Could not set socket to non-blocking mode"); | ||
200 | } | ||
201 | } | ||
202 | |||
203 | int bufsize = 0x20000; | ||
204 | if (setsockopt(cfd, SOL_SOCKET, SO_SNDBUF, &bufsize, sizeof(int)) == -1) { | ||
205 | usbmuxd_log(LL_WARNING, "Could not set send buffer for client socket"); | ||
206 | } | ||
207 | if (setsockopt(cfd, SOL_SOCKET, SO_RCVBUF, &bufsize, sizeof(int)) == -1) { | ||
208 | usbmuxd_log(LL_WARNING, "Could not set receive buffer for client socket"); | ||
209 | } | ||
210 | |||
211 | int yes = 1; | ||
212 | setsockopt(cfd, IPPROTO_TCP, TCP_NODELAY, (void*)&yes, sizeof(int)); | ||
213 | |||
214 | struct mux_client *client; | ||
215 | client = malloc(sizeof(struct mux_client)); | ||
216 | memset(client, 0, sizeof(struct mux_client)); | ||
217 | |||
218 | client->fd = cfd; | ||
219 | client->ob_buf = malloc(REPLY_BUF_SIZE); | ||
220 | client->ob_size = 0; | ||
221 | client->ob_capacity = REPLY_BUF_SIZE; | ||
222 | client->ib_buf = malloc(CMD_BUF_SIZE); | ||
223 | client->ib_size = 0; | ||
224 | client->ib_capacity = CMD_BUF_SIZE; | ||
225 | client->state = CLIENT_COMMAND; | ||
226 | client->events = POLLIN; | ||
227 | client->info = NULL; | ||
228 | |||
229 | mutex_lock(&client_list_mutex); | ||
230 | client->number = client_number++; | ||
231 | collection_add(&client_list, client); | ||
232 | mutex_unlock(&client_list_mutex); | ||
233 | |||
234 | #ifdef SO_PEERCRED | ||
235 | if (log_level >= LL_INFO) { | ||
236 | struct ucred cr; | ||
237 | len = sizeof(struct ucred); | ||
238 | getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); | ||
239 | |||
240 | if (getpid() == cr.pid) { | ||
241 | usbmuxd_log(LL_INFO, "Client %d accepted: %s[%d]", client->fd, PACKAGE_NAME, cr.pid); | ||
242 | } else { | ||
243 | char* process_name = _get_process_name_by_pid(cr.pid); | ||
244 | usbmuxd_log(LL_INFO, "Client %d accepted: %s[%d]", client->fd, process_name, cr.pid); | ||
245 | free(process_name); | ||
246 | } | ||
247 | } | ||
248 | #else | ||
249 | usbmuxd_log(LL_INFO, "Client %d accepted", client->fd); | ||
250 | #endif | ||
251 | return client->fd; | ||
252 | } | ||
253 | |||
254 | void client_close(struct mux_client *client) | ||
255 | { | ||
256 | int found = 0; | ||
257 | mutex_lock(&client_list_mutex); | ||
258 | FOREACH(struct mux_client *lc, &client_list) { | ||
259 | if (client == lc) { | ||
260 | found = 1; | ||
261 | break; | ||
262 | } | ||
263 | } ENDFOREACH | ||
264 | if (!found) { | ||
265 | // in case we get called again but client was already freed | ||
266 | usbmuxd_log(LL_DEBUG, "%s: ignoring for non-existing client %p", __func__, client); | ||
267 | mutex_unlock(&client_list_mutex); | ||
268 | return; | ||
269 | } | ||
270 | #ifdef SO_PEERCRED | ||
271 | if (log_level >= LL_INFO) { | ||
272 | struct ucred cr; | ||
273 | socklen_t len = sizeof(struct ucred); | ||
274 | getsockopt(client->fd, SOL_SOCKET, SO_PEERCRED, &cr, &len); | ||
275 | |||
276 | if (getpid() == cr.pid) { | ||
277 | usbmuxd_log(LL_INFO, "Client %d is going to be disconnected: %s[%d]", client->fd, PACKAGE_NAME, cr.pid); | ||
278 | } else { | ||
279 | char* process_name = _get_process_name_by_pid(cr.pid); | ||
280 | usbmuxd_log(LL_INFO, "Client %d is going to be disconnected: %s[%d]", client->fd, process_name, cr.pid); | ||
281 | free(process_name); | ||
282 | } | ||
283 | } | ||
284 | #else | ||
285 | usbmuxd_log(LL_INFO, "Client %d is going to be disconnected", client->fd); | ||
286 | #endif | ||
287 | if(client->state == CLIENT_CONNECTING1 || client->state == CLIENT_CONNECTING2) { | ||
288 | usbmuxd_log(LL_INFO, "Client died mid-connect, aborting device %d connection", client->connect_device); | ||
289 | client->state = CLIENT_DEAD; | ||
290 | device_abort_connect(client->connect_device, client); | ||
291 | } | ||
292 | close(client->fd); | ||
293 | free(client->ob_buf); | ||
294 | free(client->ib_buf); | ||
295 | plist_free(client->info); | ||
296 | |||
297 | collection_remove(&client_list, client); | ||
298 | mutex_unlock(&client_list_mutex); | ||
299 | free(client); | ||
300 | } | ||
301 | |||
302 | void client_get_fds(struct fdlist *list) | ||
303 | { | ||
304 | mutex_lock(&client_list_mutex); | ||
305 | FOREACH(struct mux_client *client, &client_list) { | ||
306 | fdlist_add(list, FD_CLIENT, client->fd, client->events); | ||
307 | } ENDFOREACH | ||
308 | mutex_unlock(&client_list_mutex); | ||
309 | } | ||
310 | |||
311 | static int output_buffer_add_message(struct mux_client *client, uint32_t tag, enum usbmuxd_msgtype msg, void *payload, int payload_length) | ||
312 | { | ||
313 | struct usbmuxd_header hdr; | ||
314 | hdr.version = client->proto_version; | ||
315 | hdr.length = sizeof(hdr) + payload_length; | ||
316 | hdr.message = msg; | ||
317 | hdr.tag = tag; | ||
318 | usbmuxd_log(LL_DEBUG, "Client %d output buffer got tag %d msg %d payload_length %d", client->fd, tag, msg, payload_length); | ||
319 | |||
320 | uint32_t available = client->ob_capacity - client->ob_size; | ||
321 | /* the output buffer _should_ be large enough, but just in case */ | ||
322 | if(available < hdr.length) { | ||
323 | unsigned char* new_buf; | ||
324 | uint32_t new_size = ((client->ob_capacity + hdr.length + 4096) / 4096) * 4096; | ||
325 | usbmuxd_log(LL_DEBUG, "%s: Enlarging client %d output buffer %d -> %d", __func__, client->fd, client->ob_capacity, new_size); | ||
326 | new_buf = realloc(client->ob_buf, new_size); | ||
327 | if (!new_buf) { | ||
328 | usbmuxd_log(LL_FATAL, "%s: Failed to realloc.", __func__); | ||
329 | return -1; | ||
330 | } | ||
331 | client->ob_buf = new_buf; | ||
332 | client->ob_capacity = new_size; | ||
333 | } | ||
334 | memcpy(client->ob_buf + client->ob_size, &hdr, sizeof(hdr)); | ||
335 | if(payload && payload_length) | ||
336 | memcpy(client->ob_buf + client->ob_size + sizeof(hdr), payload, payload_length); | ||
337 | client->ob_size += hdr.length; | ||
338 | client->events |= POLLOUT; | ||
339 | return hdr.length; | ||
340 | } | ||
341 | |||
342 | static int send_plist(struct mux_client *client, uint32_t tag, plist_t plist) | ||
343 | { | ||
344 | int res = -1; | ||
345 | char *xml = NULL; | ||
346 | uint32_t xmlsize = 0; | ||
347 | plist_to_xml(plist, &xml, &xmlsize); | ||
348 | if (xml) { | ||
349 | res = output_buffer_add_message(client, tag, MESSAGE_PLIST, xml, xmlsize); | ||
350 | free(xml); | ||
351 | } else { | ||
352 | usbmuxd_log(LL_ERROR, "%s: Could not convert plist to xml", __func__); | ||
353 | } | ||
354 | return res; | ||
355 | } | ||
356 | |||
357 | static int send_result(struct mux_client *client, uint32_t tag, uint32_t result) | ||
358 | { | ||
359 | int res = -1; | ||
360 | if (client->proto_version == 1) { | ||
361 | /* XML plist packet */ | ||
362 | plist_t dict = plist_new_dict(); | ||
363 | plist_dict_set_item(dict, "MessageType", plist_new_string("Result")); | ||
364 | plist_dict_set_item(dict, "Number", plist_new_uint(result)); | ||
365 | res = send_plist(client, tag, dict); | ||
366 | plist_free(dict); | ||
367 | } else { | ||
368 | /* binary packet */ | ||
369 | res = output_buffer_add_message(client, tag, MESSAGE_RESULT, &result, sizeof(uint32_t)); | ||
370 | } | ||
371 | return res; | ||
372 | } | ||
373 | |||
374 | int client_notify_connect(struct mux_client *client, enum usbmuxd_result result) | ||
375 | { | ||
376 | usbmuxd_log(LL_SPEW, "client_notify_connect fd %d result %d", client->fd, result); | ||
377 | if(client->state == CLIENT_DEAD) | ||
378 | return -1; | ||
379 | if(client->state != CLIENT_CONNECTING1) { | ||
380 | usbmuxd_log(LL_ERROR, "client_notify_connect when client %d is not in CONNECTING1 state", client->fd); | ||
381 | return -1; | ||
382 | } | ||
383 | if(send_result(client, client->connect_tag, result) < 0) | ||
384 | return -1; | ||
385 | if(result == RESULT_OK) { | ||
386 | client->state = CLIENT_CONNECTING2; | ||
387 | client->events = POLLOUT; // wait for the result packet to go through | ||
388 | // no longer need this | ||
389 | free(client->ib_buf); | ||
390 | client->ib_buf = NULL; | ||
391 | } else { | ||
392 | client->state = CLIENT_COMMAND; | ||
393 | } | ||
394 | return 0; | ||
395 | } | ||
396 | |||
397 | static plist_t create_device_attached_plist(struct device_info *dev) | ||
398 | { | ||
399 | plist_t dict = plist_new_dict(); | ||
400 | plist_dict_set_item(dict, "MessageType", plist_new_string("Attached")); | ||
401 | plist_dict_set_item(dict, "DeviceID", plist_new_uint(dev->id)); | ||
402 | plist_t props = plist_new_dict(); | ||
403 | plist_dict_set_item(props, "ConnectionSpeed", plist_new_uint(dev->speed)); | ||
404 | plist_dict_set_item(props, "ConnectionType", plist_new_string("USB")); | ||
405 | plist_dict_set_item(props, "DeviceID", plist_new_uint(dev->id)); | ||
406 | plist_dict_set_item(props, "LocationID", plist_new_uint(dev->location)); | ||
407 | plist_dict_set_item(props, "ProductID", plist_new_uint(dev->pid)); | ||
408 | plist_dict_set_item(props, "SerialNumber", plist_new_string(dev->serial)); | ||
409 | plist_dict_set_item(dict, "Properties", props); | ||
410 | return dict; | ||
411 | } | ||
412 | |||
413 | static int send_device_list(struct mux_client *client, uint32_t tag) | ||
414 | { | ||
415 | int res = -1; | ||
416 | plist_t dict = plist_new_dict(); | ||
417 | plist_t devices = plist_new_array(); | ||
418 | |||
419 | struct device_info *devs = NULL; | ||
420 | struct device_info *dev; | ||
421 | int i; | ||
422 | |||
423 | int count = device_get_list(0, &devs); | ||
424 | dev = devs; | ||
425 | for (i = 0; devs && i < count; i++) { | ||
426 | plist_t device = create_device_attached_plist(dev++); | ||
427 | if (device) { | ||
428 | plist_array_append_item(devices, device); | ||
429 | } | ||
430 | } | ||
431 | if (devs) | ||
432 | free(devs); | ||
433 | |||
434 | plist_dict_set_item(dict, "DeviceList", devices); | ||
435 | res = send_plist(client, tag, dict); | ||
436 | plist_free(dict); | ||
437 | return res; | ||
438 | } | ||
439 | |||
440 | static int send_listener_list(struct mux_client *client, uint32_t tag) | ||
441 | { | ||
442 | int res = -1; | ||
443 | |||
444 | plist_t dict = plist_new_dict(); | ||
445 | plist_t listeners = plist_new_array(); | ||
446 | |||
447 | mutex_lock(&client_list_mutex); | ||
448 | FOREACH(struct mux_client *lc, &client_list) { | ||
449 | if (lc->state == CLIENT_LISTEN) { | ||
450 | plist_t n = NULL; | ||
451 | plist_t l = plist_new_dict(); | ||
452 | plist_dict_set_item(l, "Blacklisted", plist_new_bool(0)); | ||
453 | n = NULL; | ||
454 | if (lc->info) { | ||
455 | n = plist_dict_get_item(lc->info, "BundleID"); | ||
456 | } | ||
457 | if (n) { | ||
458 | plist_dict_set_item(l, "BundleID", plist_copy(n)); | ||
459 | } | ||
460 | plist_dict_set_item(l, "ConnType", plist_new_uint(0)); | ||
461 | |||
462 | n = NULL; | ||
463 | char *progname = NULL; | ||
464 | if (lc->info) { | ||
465 | n = plist_dict_get_item(lc->info, "ProgName"); | ||
466 | } | ||
467 | if (n) { | ||
468 | plist_get_string_val(n, &progname); | ||
469 | } | ||
470 | if (!progname) { | ||
471 | progname = strdup("unknown"); | ||
472 | } | ||
473 | char *idstring = malloc(strlen(progname) + 12); | ||
474 | sprintf(idstring, "%u-%s", client->number, progname); | ||
475 | |||
476 | plist_dict_set_item(l, "ID String", plist_new_string(idstring)); | ||
477 | free(idstring); | ||
478 | plist_dict_set_item(l, "ProgName", plist_new_string(progname)); | ||
479 | free(progname); | ||
480 | |||
481 | n = NULL; | ||
482 | uint64_t version = 0; | ||
483 | if (lc->info) { | ||
484 | n = plist_dict_get_item(lc->info, "kLibUSBMuxVersion"); | ||
485 | } | ||
486 | if (n) { | ||
487 | plist_get_uint_val(n, &version); | ||
488 | } | ||
489 | plist_dict_set_item(l, "kLibUSBMuxVersion", plist_new_uint(version)); | ||
490 | |||
491 | plist_array_append_item(listeners, l); | ||
492 | } | ||
493 | } ENDFOREACH | ||
494 | mutex_unlock(&client_list_mutex); | ||
495 | |||
496 | plist_dict_set_item(dict, "ListenerList", listeners); | ||
497 | res = send_plist(client, tag, dict); | ||
498 | plist_free(dict); | ||
499 | |||
500 | return res; | ||
501 | } | ||
502 | |||
503 | static int send_system_buid(struct mux_client *client, uint32_t tag) | ||
504 | { | ||
505 | int res = -1; | ||
506 | char* buid = NULL; | ||
507 | |||
508 | config_get_system_buid(&buid); | ||
509 | |||
510 | plist_t dict = plist_new_dict(); | ||
511 | plist_dict_set_item(dict, "BUID", plist_new_string(buid)); | ||
512 | free(buid); | ||
513 | res = send_plist(client, tag, dict); | ||
514 | plist_free(dict); | ||
515 | return res; | ||
516 | } | ||
517 | |||
518 | static int send_pair_record(struct mux_client *client, uint32_t tag, const char* record_id) | ||
519 | { | ||
520 | int res = -1; | ||
521 | char* record_data = NULL; | ||
522 | uint64_t record_size = 0; | ||
523 | |||
524 | if (!record_id) { | ||
525 | return send_result(client, tag, EINVAL); | ||
526 | } | ||
527 | |||
528 | config_get_device_record(record_id, &record_data, &record_size); | ||
529 | |||
530 | if (record_data) { | ||
531 | plist_t dict = plist_new_dict(); | ||
532 | plist_dict_set_item(dict, "PairRecordData", plist_new_data(record_data, record_size)); | ||
533 | free(record_data); | ||
534 | res = send_plist(client, tag, dict); | ||
535 | plist_free(dict); | ||
536 | } else { | ||
537 | res = send_result(client, tag, ENOENT); | ||
538 | } | ||
539 | return res; | ||
540 | } | ||
541 | |||
542 | static int send_device_add(struct mux_client *client, struct device_info *dev) | ||
543 | { | ||
544 | int res = -1; | ||
545 | if (client->proto_version == 1) { | ||
546 | /* XML plist packet */ | ||
547 | plist_t dict = create_device_attached_plist(dev); | ||
548 | res = send_plist(client, 0, dict); | ||
549 | plist_free(dict); | ||
550 | } else { | ||
551 | /* binary packet */ | ||
552 | struct usbmuxd_device_record dmsg; | ||
553 | memset(&dmsg, 0, sizeof(dmsg)); | ||
554 | dmsg.device_id = dev->id; | ||
555 | strncpy(dmsg.serial_number, dev->serial, 256); | ||
556 | dmsg.serial_number[255] = 0; | ||
557 | dmsg.location = dev->location; | ||
558 | dmsg.product_id = dev->pid; | ||
559 | res = output_buffer_add_message(client, 0, MESSAGE_DEVICE_ADD, &dmsg, sizeof(dmsg)); | ||
560 | } | ||
561 | return res; | ||
562 | } | ||
563 | |||
564 | static int send_device_remove(struct mux_client *client, uint32_t device_id) | ||
565 | { | ||
566 | int res = -1; | ||
567 | if (client->proto_version == 1) { | ||
568 | /* XML plist packet */ | ||
569 | plist_t dict = plist_new_dict(); | ||
570 | plist_dict_set_item(dict, "MessageType", plist_new_string("Detached")); | ||
571 | plist_dict_set_item(dict, "DeviceID", plist_new_uint(device_id)); | ||
572 | res = send_plist(client, 0, dict); | ||
573 | plist_free(dict); | ||
574 | } else { | ||
575 | /* binary packet */ | ||
576 | res = output_buffer_add_message(client, 0, MESSAGE_DEVICE_REMOVE, &device_id, sizeof(uint32_t)); | ||
577 | } | ||
578 | return res; | ||
579 | } | ||
580 | |||
581 | static int send_device_paired(struct mux_client *client, uint32_t device_id) | ||
582 | { | ||
583 | int res = -1; | ||
584 | if (client->proto_version == 1) { | ||
585 | /* XML plist packet */ | ||
586 | plist_t dict = plist_new_dict(); | ||
587 | plist_dict_set_item(dict, "MessageType", plist_new_string("Paired")); | ||
588 | plist_dict_set_item(dict, "DeviceID", plist_new_uint(device_id)); | ||
589 | res = send_plist(client, 0, dict); | ||
590 | plist_free(dict); | ||
591 | } | ||
592 | else { | ||
593 | /* binary packet */ | ||
594 | res = output_buffer_add_message(client, 0, MESSAGE_DEVICE_PAIRED, &device_id, sizeof(uint32_t)); | ||
595 | } | ||
596 | return res; | ||
597 | } | ||
598 | |||
599 | static int start_listen(struct mux_client *client) | ||
600 | { | ||
601 | struct device_info *devs = NULL; | ||
602 | struct device_info *dev; | ||
603 | int count, i; | ||
604 | |||
605 | client->state = CLIENT_LISTEN; | ||
606 | |||
607 | count = device_get_list(0, &devs); | ||
608 | dev = devs; | ||
609 | for(i=0; devs && i < count; i++) { | ||
610 | if(send_device_add(client, dev++) < 0) { | ||
611 | free(devs); | ||
612 | return -1; | ||
613 | } | ||
614 | } | ||
615 | if (devs) | ||
616 | free(devs); | ||
617 | |||
618 | return count; | ||
619 | } | ||
620 | |||
621 | static char* plist_dict_get_string_val(plist_t dict, const char* key) | ||
622 | { | ||
623 | if (!dict || plist_get_node_type(dict) != PLIST_DICT) | ||
624 | return NULL; | ||
625 | plist_t item = plist_dict_get_item(dict, key); | ||
626 | if (!item || plist_get_node_type(item) != PLIST_STRING) | ||
627 | return NULL; | ||
628 | char *str = NULL; | ||
629 | plist_get_string_val(item, &str); | ||
630 | return str; | ||
631 | } | ||
632 | |||
633 | static void update_client_info(struct mux_client *client, plist_t dict) | ||
634 | { | ||
635 | plist_t node = NULL; | ||
636 | plist_t info = plist_new_dict(); | ||
637 | |||
638 | node = plist_dict_get_item(dict, "BundleID"); | ||
639 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
640 | plist_dict_set_item(info, "BundleID", plist_copy(node)); | ||
641 | } | ||
642 | |||
643 | node = plist_dict_get_item(dict, "ClientVersionString"); | ||
644 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
645 | plist_dict_set_item(info, "ClientVersionString", plist_copy(node)); | ||
646 | } | ||
647 | |||
648 | node = plist_dict_get_item(dict, "ProgName"); | ||
649 | if (node && (plist_get_node_type(node) == PLIST_STRING)) { | ||
650 | plist_dict_set_item(info, "ProgName", plist_copy(node)); | ||
651 | } | ||
652 | |||
653 | node = plist_dict_get_item(dict, "kLibUSBMuxVersion"); | ||
654 | if (node && (plist_get_node_type(node) == PLIST_UINT)) { | ||
655 | plist_dict_set_item(info, "kLibUSBMuxVersion", plist_copy(node)); | ||
656 | } | ||
657 | plist_free(client->info); | ||
658 | client->info = info; | ||
659 | } | ||
660 | |||
661 | static int handle_command(struct mux_client *client, struct usbmuxd_header *hdr) | ||
662 | { | ||
663 | int res; | ||
664 | usbmuxd_log(LL_DEBUG, "Client %d command len %d ver %d msg %d tag %d", client->fd, hdr->length, hdr->version, hdr->message, hdr->tag); | ||
665 | |||
666 | if(client->state != CLIENT_COMMAND) { | ||
667 | usbmuxd_log(LL_ERROR, "Client %d command received in the wrong state, got %d but want %d", client->fd, client->state, CLIENT_COMMAND); | ||
668 | if(send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) | ||
669 | return -1; | ||
670 | client_close(client); | ||
671 | return -1; | ||
672 | } | ||
673 | |||
674 | if((hdr->version != 0) && (hdr->version != 1)) { | ||
675 | usbmuxd_log(LL_INFO, "Client %d version mismatch: expected 0 or 1, got %d", client->fd, hdr->version); | ||
676 | send_result(client, hdr->tag, RESULT_BADVERSION); | ||
677 | return 0; | ||
678 | } | ||
679 | |||
680 | struct usbmuxd_connect_request *ch; | ||
681 | char *payload; | ||
682 | uint32_t payload_size; | ||
683 | |||
684 | switch(hdr->message) { | ||
685 | case MESSAGE_PLIST: | ||
686 | client->proto_version = 1; | ||
687 | payload = (char*)(hdr) + sizeof(struct usbmuxd_header); | ||
688 | payload_size = hdr->length - sizeof(struct usbmuxd_header); | ||
689 | plist_t dict = NULL; | ||
690 | plist_from_xml(payload, payload_size, &dict); | ||
691 | if (!dict) { | ||
692 | usbmuxd_log(LL_ERROR, "Could not parse plist from payload!"); | ||
693 | return -1; | ||
694 | } else { | ||
695 | char *message = NULL; | ||
696 | plist_t node = plist_dict_get_item(dict, "MessageType"); | ||
697 | if (!node || plist_get_node_type(node) != PLIST_STRING) { | ||
698 | usbmuxd_log(LL_ERROR, "Could not read valid MessageType node from plist!"); | ||
699 | plist_free(dict); | ||
700 | return -1; | ||
701 | } | ||
702 | plist_get_string_val(node, &message); | ||
703 | if (!message) { | ||
704 | usbmuxd_log(LL_ERROR, "Could not extract MessageType from plist!"); | ||
705 | plist_free(dict); | ||
706 | return -1; | ||
707 | } | ||
708 | update_client_info(client, dict); | ||
709 | if (!strcmp(message, "Listen")) { | ||
710 | free(message); | ||
711 | plist_free(dict); | ||
712 | if (send_result(client, hdr->tag, 0) < 0) | ||
713 | return -1; | ||
714 | usbmuxd_log(LL_DEBUG, "Client %d now LISTENING", client->fd); | ||
715 | return start_listen(client); | ||
716 | } else if (!strcmp(message, "Connect")) { | ||
717 | uint64_t val; | ||
718 | uint16_t portnum = 0; | ||
719 | uint32_t device_id = 0; | ||
720 | free(message); | ||
721 | // get device id | ||
722 | node = plist_dict_get_item(dict, "DeviceID"); | ||
723 | if (!node) { | ||
724 | usbmuxd_log(LL_ERROR, "Received connect request without device_id!"); | ||
725 | plist_free(dict); | ||
726 | if (send_result(client, hdr->tag, RESULT_BADDEV) < 0) | ||
727 | return -1; | ||
728 | return 0; | ||
729 | } | ||
730 | val = 0; | ||
731 | plist_get_uint_val(node, &val); | ||
732 | device_id = (uint32_t)val; | ||
733 | |||
734 | // get port number | ||
735 | node = plist_dict_get_item(dict, "PortNumber"); | ||
736 | if (!node) { | ||
737 | usbmuxd_log(LL_ERROR, "Received connect request without port number!"); | ||
738 | plist_free(dict); | ||
739 | if (send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) | ||
740 | return -1; | ||
741 | return 0; | ||
742 | } | ||
743 | val = 0; | ||
744 | plist_get_uint_val(node, &val); | ||
745 | portnum = (uint16_t)val; | ||
746 | plist_free(dict); | ||
747 | |||
748 | usbmuxd_log(LL_DEBUG, "Client %d requesting connection to device %d port %d", client->fd, device_id, ntohs(portnum)); | ||
749 | res = device_start_connect(device_id, ntohs(portnum), client); | ||
750 | if(res < 0) { | ||
751 | if (send_result(client, hdr->tag, -res) < 0) | ||
752 | return -1; | ||
753 | } else { | ||
754 | client->connect_tag = hdr->tag; | ||
755 | client->connect_device = device_id; | ||
756 | client->state = CLIENT_CONNECTING1; | ||
757 | } | ||
758 | return 0; | ||
759 | } else if (!strcmp(message, "ListDevices")) { | ||
760 | free(message); | ||
761 | plist_free(dict); | ||
762 | if (send_device_list(client, hdr->tag) < 0) | ||
763 | return -1; | ||
764 | return 0; | ||
765 | } else if (!strcmp(message, "ListListeners")) { | ||
766 | free(message); | ||
767 | plist_free(dict); | ||
768 | if (send_listener_list(client, hdr->tag) < 0) | ||
769 | return -1; | ||
770 | return 0; | ||
771 | } else if (!strcmp(message, "ReadBUID")) { | ||
772 | free(message); | ||
773 | plist_free(dict); | ||
774 | if (send_system_buid(client, hdr->tag) < 0) | ||
775 | return -1; | ||
776 | return 0; | ||
777 | } else if (!strcmp(message, "ReadPairRecord")) { | ||
778 | free(message); | ||
779 | char* record_id = plist_dict_get_string_val(dict, "PairRecordID"); | ||
780 | plist_free(dict); | ||
781 | |||
782 | res = send_pair_record(client, hdr->tag, record_id); | ||
783 | if (record_id) | ||
784 | free(record_id); | ||
785 | if (res < 0) | ||
786 | return -1; | ||
787 | return 0; | ||
788 | } else if (!strcmp(message, "SavePairRecord")) { | ||
789 | uint32_t rval = RESULT_OK; | ||
790 | free(message); | ||
791 | char* record_id = plist_dict_get_string_val(dict, "PairRecordID"); | ||
792 | char* record_data = NULL; | ||
793 | uint64_t record_size = 0; | ||
794 | plist_t rdata = plist_dict_get_item(dict, "PairRecordData"); | ||
795 | if (rdata && plist_get_node_type(rdata) == PLIST_DATA) { | ||
796 | plist_get_data_val(rdata, &record_data, &record_size); | ||
797 | } | ||
798 | |||
799 | if (record_id && record_data) { | ||
800 | res = config_set_device_record(record_id, record_data, record_size); | ||
801 | if (res < 0) { | ||
802 | rval = -res; | ||
803 | } else { | ||
804 | plist_t p_dev_id = plist_dict_get_item(dict, "DeviceID"); | ||
805 | uint32_t dev_id = 0; | ||
806 | if (p_dev_id && plist_get_node_type(p_dev_id) == PLIST_UINT) { | ||
807 | uint64_t u_dev_id = 0; | ||
808 | plist_get_uint_val(p_dev_id, &u_dev_id); | ||
809 | dev_id = (uint32_t)u_dev_id; | ||
810 | } | ||
811 | if (dev_id > 0) { | ||
812 | struct device_info *devs = NULL; | ||
813 | struct device_info *dev; | ||
814 | int i; | ||
815 | int count = device_get_list(1, &devs); | ||
816 | int found = 0; | ||
817 | dev = devs; | ||
818 | for (i = 0; devs && i < count; i++, dev++) { | ||
819 | if ((uint32_t)dev->id == dev_id && (strcmp(dev->serial, record_id) == 0)) { | ||
820 | found++; | ||
821 | break; | ||
822 | } | ||
823 | } | ||
824 | if (!found) { | ||
825 | usbmuxd_log(LL_ERROR, "ERROR: SavePairRecord: DeviceID %d (%s) is not connected\n", dev_id, record_id); | ||
826 | } else { | ||
827 | client_device_paired(dev_id); | ||
828 | } | ||
829 | free(devs); | ||
830 | } | ||
831 | } | ||
832 | free(record_id); | ||
833 | } else { | ||
834 | rval = EINVAL; | ||
835 | } | ||
836 | free(record_data); | ||
837 | plist_free(dict); | ||
838 | if (send_result(client, hdr->tag, rval) < 0) | ||
839 | return -1; | ||
840 | return 0; | ||
841 | } else if (!strcmp(message, "DeletePairRecord")) { | ||
842 | uint32_t rval = RESULT_OK; | ||
843 | free(message); | ||
844 | char* record_id = plist_dict_get_string_val(dict, "PairRecordID"); | ||
845 | plist_free(dict); | ||
846 | if (record_id) { | ||
847 | res = config_remove_device_record(record_id); | ||
848 | if (res < 0) { | ||
849 | rval = -res; | ||
850 | } | ||
851 | free(record_id); | ||
852 | } else { | ||
853 | rval = EINVAL; | ||
854 | } | ||
855 | if (send_result(client, hdr->tag, rval) < 0) | ||
856 | return -1; | ||
857 | return 0; | ||
858 | } else { | ||
859 | usbmuxd_log(LL_ERROR, "Unexpected command '%s' received!", message); | ||
860 | free(message); | ||
861 | plist_free(dict); | ||
862 | if (send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) | ||
863 | return -1; | ||
864 | return 0; | ||
865 | } | ||
866 | } | ||
867 | // should not be reached?! | ||
868 | return -1; | ||
869 | case MESSAGE_LISTEN: | ||
870 | if(send_result(client, hdr->tag, 0) < 0) | ||
871 | return -1; | ||
872 | usbmuxd_log(LL_DEBUG, "Client %d now LISTENING", client->fd); | ||
873 | return start_listen(client); | ||
874 | case MESSAGE_CONNECT: | ||
875 | ch = (void*)hdr; | ||
876 | usbmuxd_log(LL_DEBUG, "Client %d connection request to device %d port %d", client->fd, ch->device_id, ntohs(ch->port)); | ||
877 | res = device_start_connect(ch->device_id, ntohs(ch->port), client); | ||
878 | if(res < 0) { | ||
879 | if(send_result(client, hdr->tag, -res) < 0) | ||
880 | return -1; | ||
881 | } else { | ||
882 | client->connect_tag = hdr->tag; | ||
883 | client->connect_device = ch->device_id; | ||
884 | client->state = CLIENT_CONNECTING1; | ||
885 | } | ||
886 | return 0; | ||
887 | default: | ||
888 | usbmuxd_log(LL_ERROR, "Client %d invalid command %d", client->fd, hdr->message); | ||
889 | if(send_result(client, hdr->tag, RESULT_BADCOMMAND) < 0) | ||
890 | return -1; | ||
891 | return 0; | ||
892 | } | ||
893 | return -1; | ||
894 | } | ||
895 | |||
896 | static void output_buffer_process(struct mux_client *client) | ||
897 | { | ||
898 | int res; | ||
899 | if(!client->ob_size) { | ||
900 | usbmuxd_log(LL_WARNING, "Client %d OUT process but nothing to send?", client->fd); | ||
901 | client->events &= ~POLLOUT; | ||
902 | return; | ||
903 | } | ||
904 | res = send(client->fd, client->ob_buf, client->ob_size, 0); | ||
905 | if(res <= 0) { | ||
906 | usbmuxd_log(LL_ERROR, "Sending to client fd %d failed: %d %s", client->fd, res, strerror(errno)); | ||
907 | client_close(client); | ||
908 | return; | ||
909 | } | ||
910 | if((uint32_t)res == client->ob_size) { | ||
911 | client->ob_size = 0; | ||
912 | client->events &= ~POLLOUT; | ||
913 | if(client->state == CLIENT_CONNECTING2) { | ||
914 | usbmuxd_log(LL_DEBUG, "Client %d switching to CONNECTED state", client->fd); | ||
915 | client->state = CLIENT_CONNECTED; | ||
916 | client->events = client->devents; | ||
917 | // no longer need this | ||
918 | free(client->ob_buf); | ||
919 | client->ob_buf = NULL; | ||
920 | } | ||
921 | } else { | ||
922 | client->ob_size -= res; | ||
923 | memmove(client->ob_buf, client->ob_buf + res, client->ob_size); | ||
924 | } | ||
925 | } | ||
926 | static void input_buffer_process(struct mux_client *client) | ||
927 | { | ||
928 | int res; | ||
929 | int did_read = 0; | ||
930 | if(client->ib_size < sizeof(struct usbmuxd_header)) { | ||
931 | res = recv(client->fd, client->ib_buf + client->ib_size, sizeof(struct usbmuxd_header) - client->ib_size, 0); | ||
932 | if(res <= 0) { | ||
933 | if(res < 0) | ||
934 | usbmuxd_log(LL_ERROR, "Receive from client fd %d failed: %s", client->fd, strerror(errno)); | ||
935 | else | ||
936 | usbmuxd_log(LL_INFO, "Client %d connection closed", client->fd); | ||
937 | client_close(client); | ||
938 | return; | ||
939 | } | ||
940 | client->ib_size += res; | ||
941 | if(client->ib_size < sizeof(struct usbmuxd_header)) | ||
942 | return; | ||
943 | did_read = 1; | ||
944 | } | ||
945 | struct usbmuxd_header *hdr = (void*)client->ib_buf; | ||
946 | if(hdr->length > client->ib_capacity) { | ||
947 | usbmuxd_log(LL_INFO, "Client %d message is too long (%d bytes)", client->fd, hdr->length); | ||
948 | client_close(client); | ||
949 | return; | ||
950 | } | ||
951 | if(hdr->length < sizeof(struct usbmuxd_header)) { | ||
952 | usbmuxd_log(LL_ERROR, "Client %d message is too short (%d bytes)", client->fd, hdr->length); | ||
953 | client_close(client); | ||
954 | return; | ||
955 | } | ||
956 | if(client->ib_size < hdr->length) { | ||
957 | if(did_read) | ||
958 | return; //maybe we would block, so defer to next loop | ||
959 | res = recv(client->fd, client->ib_buf + client->ib_size, hdr->length - client->ib_size, 0); | ||
960 | if(res < 0) { | ||
961 | usbmuxd_log(LL_ERROR, "Receive from client fd %d failed: %s", client->fd, strerror(errno)); | ||
962 | client_close(client); | ||
963 | return; | ||
964 | } else if(res == 0) { | ||
965 | usbmuxd_log(LL_INFO, "Client %d connection closed", client->fd); | ||
966 | client_close(client); | ||
967 | return; | ||
968 | } | ||
969 | client->ib_size += res; | ||
970 | if(client->ib_size < hdr->length) | ||
971 | return; | ||
972 | } | ||
973 | handle_command(client, hdr); | ||
974 | client->ib_size = 0; | ||
975 | } | ||
976 | |||
977 | void client_process(int fd, short events) | ||
978 | { | ||
979 | struct mux_client *client = NULL; | ||
980 | mutex_lock(&client_list_mutex); | ||
981 | FOREACH(struct mux_client *lc, &client_list) { | ||
982 | if(lc->fd == fd) { | ||
983 | client = lc; | ||
984 | break; | ||
985 | } | ||
986 | } ENDFOREACH | ||
987 | mutex_unlock(&client_list_mutex); | ||
988 | |||
989 | if(!client) { | ||
990 | usbmuxd_log(LL_INFO, "client_process: fd %d not found in client list", fd); | ||
991 | return; | ||
992 | } | ||
993 | |||
994 | if(client->state == CLIENT_CONNECTED) { | ||
995 | usbmuxd_log(LL_SPEW, "client_process in CONNECTED state"); | ||
996 | device_client_process(client->connect_device, client, events); | ||
997 | } else { | ||
998 | if(events & POLLIN) { | ||
999 | input_buffer_process(client); | ||
1000 | } else if(events & POLLOUT) { //not both in case client died as part of process_recv | ||
1001 | output_buffer_process(client); | ||
1002 | } | ||
1003 | } | ||
1004 | |||
1005 | } | ||
1006 | |||
1007 | void client_device_add(struct device_info *dev) | ||
1008 | { | ||
1009 | mutex_lock(&client_list_mutex); | ||
1010 | usbmuxd_log(LL_DEBUG, "client_device_add: id %d, location 0x%x, serial %s", dev->id, dev->location, dev->serial); | ||
1011 | device_set_visible(dev->id); | ||
1012 | FOREACH(struct mux_client *client, &client_list) { | ||
1013 | if(client->state == CLIENT_LISTEN) | ||
1014 | send_device_add(client, dev); | ||
1015 | } ENDFOREACH | ||
1016 | mutex_unlock(&client_list_mutex); | ||
1017 | } | ||
1018 | |||
1019 | void client_device_remove(int device_id) | ||
1020 | { | ||
1021 | mutex_lock(&client_list_mutex); | ||
1022 | uint32_t id = device_id; | ||
1023 | usbmuxd_log(LL_DEBUG, "client_device_remove: id %d", device_id); | ||
1024 | FOREACH(struct mux_client *client, &client_list) { | ||
1025 | if(client->state == CLIENT_LISTEN) | ||
1026 | send_device_remove(client, id); | ||
1027 | } ENDFOREACH | ||
1028 | mutex_unlock(&client_list_mutex); | ||
1029 | } | ||
1030 | |||
1031 | void client_device_paired(int device_id) | ||
1032 | { | ||
1033 | mutex_lock(&client_list_mutex); | ||
1034 | uint32_t id = device_id; | ||
1035 | usbmuxd_log(LL_DEBUG, "client_device_paired: id %d", device_id); | ||
1036 | FOREACH(struct mux_client *client, &client_list) { | ||
1037 | if (client->state == CLIENT_LISTEN) | ||
1038 | send_device_paired(client, id); | ||
1039 | } ENDFOREACH | ||
1040 | mutex_unlock(&client_list_mutex); | ||
1041 | } | ||
1042 | |||
1043 | void client_init(void) | ||
1044 | { | ||
1045 | usbmuxd_log(LL_DEBUG, "client_init"); | ||
1046 | collection_init(&client_list); | ||
1047 | mutex_init(&client_list_mutex); | ||
1048 | } | ||
1049 | |||
1050 | void client_shutdown(void) | ||
1051 | { | ||
1052 | usbmuxd_log(LL_DEBUG, "client_shutdown"); | ||
1053 | FOREACH(struct mux_client *client, &client_list) { | ||
1054 | client_close(client); | ||
1055 | } ENDFOREACH | ||
1056 | mutex_destroy(&client_list_mutex); | ||
1057 | collection_free(&client_list); | ||
1058 | } | ||