1 | /* MiniDLNA project |
---|
2 | * http://minidlna.sourceforge.net/ |
---|
3 | * |
---|
4 | * MiniDLNA media server |
---|
5 | * Copyright (C) 2008-2009 Justin Maggard |
---|
6 | * |
---|
7 | * This file is part of MiniDLNA. |
---|
8 | * |
---|
9 | * MiniDLNA is free software; you can redistribute it and/or modify |
---|
10 | * it under the terms of the GNU General Public License version 2 as |
---|
11 | * published by the Free Software Foundation. |
---|
12 | * |
---|
13 | * MiniDLNA is distributed in the hope that it will be useful, |
---|
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
16 | * GNU General Public License for more details. |
---|
17 | * |
---|
18 | * You should have received a copy of the GNU General Public License |
---|
19 | * along with MiniDLNA. If not, see <http://www.gnu.org/licenses/>. |
---|
20 | * |
---|
21 | * Portions of the code from the MiniUPnP project: |
---|
22 | * |
---|
23 | * Copyright (c) 2006-2007, Thomas Bernard |
---|
24 | * All rights reserved. |
---|
25 | * |
---|
26 | * Redistribution and use in source and binary forms, with or without |
---|
27 | * modification, are permitted provided that the following conditions are met: |
---|
28 | * * Redistributions of source code must retain the above copyright |
---|
29 | * notice, this list of conditions and the following disclaimer. |
---|
30 | * * Redistributions in binary form must reproduce the above copyright |
---|
31 | * notice, this list of conditions and the following disclaimer in the |
---|
32 | * documentation and/or other materials provided with the distribution. |
---|
33 | * * The name of the author may not be used to endorse or promote products |
---|
34 | * derived from this software without specific prior written permission. |
---|
35 | * |
---|
36 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" |
---|
37 | * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
---|
38 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
---|
39 | * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE |
---|
40 | * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR |
---|
41 | * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF |
---|
42 | * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS |
---|
43 | * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN |
---|
44 | * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
---|
45 | * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE |
---|
46 | * POSSIBILITY OF SUCH DAMAGE. |
---|
47 | */ |
---|
48 | #include <stdio.h> |
---|
49 | #include <string.h> |
---|
50 | #include <errno.h> |
---|
51 | #include <sys/queue.h> |
---|
52 | #include <stdlib.h> |
---|
53 | #include <unistd.h> |
---|
54 | #include <time.h> |
---|
55 | #include <sys/types.h> |
---|
56 | #include <sys/socket.h> |
---|
57 | #include <netinet/in.h> |
---|
58 | #include <arpa/inet.h> |
---|
59 | #include <fcntl.h> |
---|
60 | #include <errno.h> |
---|
61 | |
---|
62 | #include "config.h" |
---|
63 | #include "upnpevents.h" |
---|
64 | #include "minidlnapath.h" |
---|
65 | #include "upnpglobalvars.h" |
---|
66 | #include "upnpdescgen.h" |
---|
67 | #include "uuid.h" |
---|
68 | #include "log.h" |
---|
69 | |
---|
70 | /* stuctures definitions */ |
---|
71 | struct subscriber { |
---|
72 | LIST_ENTRY(subscriber) entries; |
---|
73 | struct upnp_event_notify * notify; |
---|
74 | time_t timeout; |
---|
75 | uint32_t seq; |
---|
76 | /*enum { EWanCFG = 1, EWanIPC, EL3F } service;*/ |
---|
77 | enum subscriber_service_enum service; |
---|
78 | char uuid[42]; |
---|
79 | char callback[]; |
---|
80 | }; |
---|
81 | |
---|
82 | struct upnp_event_notify { |
---|
83 | LIST_ENTRY(upnp_event_notify) entries; |
---|
84 | int s; /* socket */ |
---|
85 | enum { ECreated=1, |
---|
86 | EConnecting, |
---|
87 | ESending, |
---|
88 | EWaitingForResponse, |
---|
89 | EFinished, |
---|
90 | EError } state; |
---|
91 | struct subscriber * sub; |
---|
92 | char * buffer; |
---|
93 | int buffersize; |
---|
94 | int tosend; |
---|
95 | int sent; |
---|
96 | const char * path; |
---|
97 | char addrstr[16]; |
---|
98 | char portstr[8]; |
---|
99 | }; |
---|
100 | |
---|
101 | /* prototypes */ |
---|
102 | static void |
---|
103 | upnp_event_create_notify(struct subscriber * sub); |
---|
104 | |
---|
105 | /* Subscriber list */ |
---|
106 | LIST_HEAD(listhead, subscriber) subscriberlist = { NULL }; |
---|
107 | |
---|
108 | /* notify list */ |
---|
109 | LIST_HEAD(listheadnotif, upnp_event_notify) notifylist = { NULL }; |
---|
110 | |
---|
111 | /* create a new subscriber */ |
---|
112 | static struct subscriber * |
---|
113 | newSubscriber(const char * eventurl, const char * callback, int callbacklen) |
---|
114 | { |
---|
115 | struct subscriber * tmp; |
---|
116 | if(!eventurl || !callback || !callbacklen) |
---|
117 | return NULL; |
---|
118 | tmp = calloc(1, sizeof(struct subscriber)+callbacklen+1); |
---|
119 | if(strcmp(eventurl, CONTENTDIRECTORY_EVENTURL)==0) |
---|
120 | tmp->service = EContentDirectory; |
---|
121 | else if(strcmp(eventurl, CONNECTIONMGR_EVENTURL)==0) |
---|
122 | tmp->service = EConnectionManager; |
---|
123 | else if(strcmp(eventurl, X_MS_MEDIARECEIVERREGISTRAR_EVENTURL)==0) |
---|
124 | tmp->service = EMSMediaReceiverRegistrar; |
---|
125 | else { |
---|
126 | free(tmp); |
---|
127 | return NULL; |
---|
128 | } |
---|
129 | memcpy(tmp->callback, callback, callbacklen); |
---|
130 | tmp->callback[callbacklen] = '\0'; |
---|
131 | /* make a dummy uuid */ |
---|
132 | strncpy(tmp->uuid, uuidvalue, sizeof(tmp->uuid)); |
---|
133 | if( get_uuid_string(tmp->uuid+5) != 0 ) |
---|
134 | { |
---|
135 | tmp->uuid[sizeof(tmp->uuid)-1] = '\0'; |
---|
136 | snprintf(tmp->uuid+37, 5, "%04lx", random() & 0xffff); |
---|
137 | } |
---|
138 | |
---|
139 | return tmp; |
---|
140 | } |
---|
141 | |
---|
142 | /* creates a new subscriber and adds it to the subscriber list |
---|
143 | * also initiate 1st notify */ |
---|
144 | const char * |
---|
145 | upnpevents_addSubscriber(const char * eventurl, |
---|
146 | const char * callback, int callbacklen, |
---|
147 | int timeout) |
---|
148 | { |
---|
149 | struct subscriber * tmp; |
---|
150 | /*static char uuid[42];*/ |
---|
151 | /* "uuid:00000000-0000-0000-0000-000000000000"; 5+36+1=42bytes */ |
---|
152 | DPRINTF(E_DEBUG, L_HTTP, "addSubscriber(%s, %.*s, %d)\n", |
---|
153 | eventurl, callbacklen, callback, timeout); |
---|
154 | /*strncpy(uuid, uuidvalue, sizeof(uuid)); |
---|
155 | uuid[sizeof(uuid)-1] = '\0';*/ |
---|
156 | tmp = newSubscriber(eventurl, callback, callbacklen); |
---|
157 | if(!tmp) |
---|
158 | return NULL; |
---|
159 | if(timeout) |
---|
160 | tmp->timeout = time(NULL) + timeout; |
---|
161 | LIST_INSERT_HEAD(&subscriberlist, tmp, entries); |
---|
162 | upnp_event_create_notify(tmp); |
---|
163 | return tmp->uuid; |
---|
164 | } |
---|
165 | |
---|
166 | /* renew a subscription (update the timeout) */ |
---|
167 | int |
---|
168 | renewSubscription(const char * sid, int sidlen, int timeout) |
---|
169 | { |
---|
170 | struct subscriber * sub; |
---|
171 | for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { |
---|
172 | if(memcmp(sid, sub->uuid, 41) == 0) { |
---|
173 | sub->timeout = (timeout ? time(NULL) + timeout : 0); |
---|
174 | return 0; |
---|
175 | } |
---|
176 | } |
---|
177 | return -1; |
---|
178 | } |
---|
179 | |
---|
180 | int |
---|
181 | upnpevents_removeSubscriber(const char * sid, int sidlen) |
---|
182 | { |
---|
183 | struct subscriber * sub; |
---|
184 | if(!sid) |
---|
185 | return -1; |
---|
186 | DPRINTF(E_DEBUG, L_HTTP, "removeSubscriber(%.*s)\n", |
---|
187 | sidlen, sid); |
---|
188 | for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { |
---|
189 | if(memcmp(sid, sub->uuid, 41) == 0) { |
---|
190 | if(sub->notify) { |
---|
191 | sub->notify->sub = NULL; |
---|
192 | } |
---|
193 | LIST_REMOVE(sub, entries); |
---|
194 | free(sub); |
---|
195 | return 0; |
---|
196 | } |
---|
197 | } |
---|
198 | return -1; |
---|
199 | } |
---|
200 | |
---|
201 | /* notifies all subscriber of a number of port mapping change |
---|
202 | * or external ip address change */ |
---|
203 | void |
---|
204 | upnp_event_var_change_notify(enum subscriber_service_enum service) |
---|
205 | { |
---|
206 | struct subscriber * sub; |
---|
207 | for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { |
---|
208 | if(sub->service == service && sub->notify == NULL) |
---|
209 | upnp_event_create_notify(sub); |
---|
210 | } |
---|
211 | } |
---|
212 | |
---|
213 | /* create and add the notify object to the list */ |
---|
214 | static void |
---|
215 | upnp_event_create_notify(struct subscriber * sub) |
---|
216 | { |
---|
217 | struct upnp_event_notify * obj; |
---|
218 | int flags; |
---|
219 | obj = calloc(1, sizeof(struct upnp_event_notify)); |
---|
220 | if(!obj) { |
---|
221 | DPRINTF(E_ERROR, L_HTTP, "%s: calloc(): %s\n", "upnp_event_create_notify", strerror(errno)); |
---|
222 | return; |
---|
223 | } |
---|
224 | obj->sub = sub; |
---|
225 | obj->state = ECreated; |
---|
226 | obj->s = socket(PF_INET, SOCK_STREAM, 0); |
---|
227 | if(obj->s<0) { |
---|
228 | DPRINTF(E_ERROR, L_HTTP, "%s: socket(): %s\n", "upnp_event_create_notify", strerror(errno)); |
---|
229 | goto error; |
---|
230 | } |
---|
231 | if((flags = fcntl(obj->s, F_GETFL, 0)) < 0) { |
---|
232 | DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_GETFL..): %s\n", |
---|
233 | "upnp_event_create_notify", strerror(errno)); |
---|
234 | goto error; |
---|
235 | } |
---|
236 | if(fcntl(obj->s, F_SETFL, flags | O_NONBLOCK) < 0) { |
---|
237 | DPRINTF(E_ERROR, L_HTTP, "%s: fcntl(..F_SETFL..): %s\n", |
---|
238 | "upnp_event_create_notify", strerror(errno)); |
---|
239 | goto error; |
---|
240 | } |
---|
241 | if(sub) |
---|
242 | sub->notify = obj; |
---|
243 | LIST_INSERT_HEAD(¬ifylist, obj, entries); |
---|
244 | return; |
---|
245 | error: |
---|
246 | if(obj->s >= 0) |
---|
247 | close(obj->s); |
---|
248 | free(obj); |
---|
249 | } |
---|
250 | |
---|
251 | static void |
---|
252 | upnp_event_notify_connect(struct upnp_event_notify * obj) |
---|
253 | { |
---|
254 | int i; |
---|
255 | const char * p; |
---|
256 | unsigned short port; |
---|
257 | struct sockaddr_in addr; |
---|
258 | if(!obj) |
---|
259 | return; |
---|
260 | memset(&addr, 0, sizeof(addr)); |
---|
261 | i = 0; |
---|
262 | if(obj->sub == NULL) { |
---|
263 | obj->state = EError; |
---|
264 | return; |
---|
265 | } |
---|
266 | p = obj->sub->callback; |
---|
267 | p += 7; /* http:// */ |
---|
268 | while(*p != '/' && *p != ':') |
---|
269 | obj->addrstr[i++] = *(p++); |
---|
270 | obj->addrstr[i] = '\0'; |
---|
271 | if(*p == ':') { |
---|
272 | obj->portstr[0] = *p; |
---|
273 | i = 1; |
---|
274 | p++; |
---|
275 | port = (unsigned short)atoi(p); |
---|
276 | while(*p != '/' && *p != '\0') { |
---|
277 | if(i<7) obj->portstr[i++] = *p; |
---|
278 | p++; |
---|
279 | } |
---|
280 | obj->portstr[i] = 0; |
---|
281 | } else { |
---|
282 | port = 80; |
---|
283 | obj->portstr[0] = '\0'; |
---|
284 | } |
---|
285 | if( *p ) |
---|
286 | obj->path = p; |
---|
287 | else |
---|
288 | obj->path = "/"; |
---|
289 | addr.sin_family = AF_INET; |
---|
290 | inet_aton(obj->addrstr, &addr.sin_addr); |
---|
291 | addr.sin_port = htons(port); |
---|
292 | DPRINTF(E_DEBUG, L_HTTP, "%s: '%s' %hu '%s'\n", "upnp_event_notify_connect", |
---|
293 | obj->addrstr, port, obj->path); |
---|
294 | obj->state = EConnecting; |
---|
295 | if(connect(obj->s, (struct sockaddr *)&addr, sizeof(addr)) < 0) { |
---|
296 | if(errno != EINPROGRESS && errno != EWOULDBLOCK) { |
---|
297 | DPRINTF(E_ERROR, L_HTTP, "%s: connect(): %s\n", "upnp_event_notify_connect", strerror(errno)); |
---|
298 | obj->state = EError; |
---|
299 | } |
---|
300 | } |
---|
301 | } |
---|
302 | |
---|
303 | static void upnp_event_prepare(struct upnp_event_notify * obj) |
---|
304 | { |
---|
305 | static const char notifymsg[] = |
---|
306 | "NOTIFY %s HTTP/1.1\r\n" |
---|
307 | "Host: %s%s\r\n" |
---|
308 | "Content-Type: text/xml; charset=\"utf-8\"\r\n" |
---|
309 | "Content-Length: %d\r\n" |
---|
310 | "NT: upnp:event\r\n" |
---|
311 | "NTS: upnp:propchange\r\n" |
---|
312 | "SID: %s\r\n" |
---|
313 | "SEQ: %u\r\n" |
---|
314 | "Connection: close\r\n" |
---|
315 | "Cache-Control: no-cache\r\n" |
---|
316 | "\r\n" |
---|
317 | "%.*s\r\n"; |
---|
318 | char * xml; |
---|
319 | int l; |
---|
320 | if(obj->sub == NULL) { |
---|
321 | obj->state = EError; |
---|
322 | return; |
---|
323 | } |
---|
324 | switch(obj->sub->service) { |
---|
325 | case EContentDirectory: |
---|
326 | xml = getVarsContentDirectory(&l); |
---|
327 | break; |
---|
328 | case EConnectionManager: |
---|
329 | xml = getVarsConnectionManager(&l); |
---|
330 | break; |
---|
331 | case EMSMediaReceiverRegistrar: |
---|
332 | xml = getVarsX_MS_MediaReceiverRegistrar(&l); |
---|
333 | break; |
---|
334 | default: |
---|
335 | xml = NULL; |
---|
336 | l = 0; |
---|
337 | } |
---|
338 | obj->tosend = asprintf(&(obj->buffer), notifymsg, |
---|
339 | obj->path, obj->addrstr, obj->portstr, l+2, |
---|
340 | obj->sub->uuid, obj->sub->seq, |
---|
341 | l, xml); |
---|
342 | obj->buffersize = obj->tosend; |
---|
343 | if(xml) { |
---|
344 | free(xml); |
---|
345 | xml = NULL; |
---|
346 | } |
---|
347 | DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event response:\n%s\n", obj->buffer); |
---|
348 | obj->state = ESending; |
---|
349 | } |
---|
350 | |
---|
351 | static void upnp_event_send(struct upnp_event_notify * obj) |
---|
352 | { |
---|
353 | int i; |
---|
354 | //DEBUG DPRINTF(E_DEBUG, L_HTTP, "Sending UPnP Event:\n%s", obj->buffer+obj->sent); |
---|
355 | i = send(obj->s, obj->buffer + obj->sent, obj->tosend - obj->sent, 0); |
---|
356 | if(i<0) { |
---|
357 | DPRINTF(E_WARN, L_HTTP, "%s: send(): %s\n", "upnp_event_send", strerror(errno)); |
---|
358 | obj->state = EError; |
---|
359 | return; |
---|
360 | } |
---|
361 | else if(i != (obj->tosend - obj->sent)) |
---|
362 | DPRINTF(E_WARN, L_HTTP, "%s: %d bytes send out of %d\n", |
---|
363 | "upnp_event_send", i, obj->tosend - obj->sent); |
---|
364 | obj->sent += i; |
---|
365 | if(obj->sent == obj->tosend) |
---|
366 | obj->state = EWaitingForResponse; |
---|
367 | } |
---|
368 | |
---|
369 | static void upnp_event_recv(struct upnp_event_notify * obj) |
---|
370 | { |
---|
371 | int n; |
---|
372 | n = recv(obj->s, obj->buffer, obj->buffersize, 0); |
---|
373 | if(n<0) { |
---|
374 | DPRINTF(E_ERROR, L_HTTP, "%s: recv(): %s\n", "upnp_event_recv", strerror(errno)); |
---|
375 | obj->state = EError; |
---|
376 | return; |
---|
377 | } |
---|
378 | DPRINTF(E_DEBUG, L_HTTP, "%s: (%dbytes) %.*s\n", "upnp_event_recv", |
---|
379 | n, n, obj->buffer); |
---|
380 | obj->state = EFinished; |
---|
381 | if(obj->sub) |
---|
382 | obj->sub->seq++; |
---|
383 | } |
---|
384 | |
---|
385 | static void |
---|
386 | upnp_event_process_notify(struct upnp_event_notify * obj) |
---|
387 | { |
---|
388 | switch(obj->state) { |
---|
389 | case EConnecting: |
---|
390 | /* now connected or failed to connect */ |
---|
391 | upnp_event_prepare(obj); |
---|
392 | upnp_event_send(obj); |
---|
393 | break; |
---|
394 | case ESending: |
---|
395 | upnp_event_send(obj); |
---|
396 | break; |
---|
397 | case EWaitingForResponse: |
---|
398 | upnp_event_recv(obj); |
---|
399 | break; |
---|
400 | case EFinished: |
---|
401 | close(obj->s); |
---|
402 | obj->s = -1; |
---|
403 | break; |
---|
404 | default: |
---|
405 | DPRINTF(E_ERROR, L_HTTP, "upnp_event_process_notify: unknown state\n"); |
---|
406 | } |
---|
407 | } |
---|
408 | |
---|
409 | void upnpevents_selectfds(fd_set *readset, fd_set *writeset, int * max_fd) |
---|
410 | { |
---|
411 | struct upnp_event_notify * obj; |
---|
412 | for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { |
---|
413 | DPRINTF(E_DEBUG, L_HTTP, "upnpevents_selectfds: %p %d %d\n", |
---|
414 | obj, obj->state, obj->s); |
---|
415 | if(obj->s >= 0) { |
---|
416 | switch(obj->state) { |
---|
417 | case ECreated: |
---|
418 | upnp_event_notify_connect(obj); |
---|
419 | if(obj->state != EConnecting) |
---|
420 | break; |
---|
421 | case EConnecting: |
---|
422 | case ESending: |
---|
423 | FD_SET(obj->s, writeset); |
---|
424 | if(obj->s > *max_fd) |
---|
425 | *max_fd = obj->s; |
---|
426 | break; |
---|
427 | case EFinished: |
---|
428 | case EError: |
---|
429 | case EWaitingForResponse: |
---|
430 | FD_SET(obj->s, readset); |
---|
431 | if(obj->s > *max_fd) |
---|
432 | *max_fd = obj->s; |
---|
433 | break; |
---|
434 | } |
---|
435 | } |
---|
436 | } |
---|
437 | } |
---|
438 | |
---|
439 | void upnpevents_processfds(fd_set *readset, fd_set *writeset) |
---|
440 | { |
---|
441 | struct upnp_event_notify * obj; |
---|
442 | struct upnp_event_notify * next; |
---|
443 | struct subscriber * sub; |
---|
444 | struct subscriber * subnext; |
---|
445 | time_t curtime; |
---|
446 | for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { |
---|
447 | DPRINTF(E_DEBUG, L_HTTP, "%s: %p %d %d %d %d\n", |
---|
448 | "upnpevents_processfds", obj, obj->state, obj->s, |
---|
449 | FD_ISSET(obj->s, readset), FD_ISSET(obj->s, writeset)); |
---|
450 | if(obj->s >= 0) { |
---|
451 | if(FD_ISSET(obj->s, readset) || FD_ISSET(obj->s, writeset)) |
---|
452 | upnp_event_process_notify(obj); |
---|
453 | } |
---|
454 | } |
---|
455 | obj = notifylist.lh_first; |
---|
456 | while(obj != NULL) { |
---|
457 | next = obj->entries.le_next; |
---|
458 | if(obj->state == EError || obj->state == EFinished) { |
---|
459 | if(obj->s >= 0) { |
---|
460 | close(obj->s); |
---|
461 | } |
---|
462 | if(obj->sub) |
---|
463 | obj->sub->notify = NULL; |
---|
464 | #if 0 /* Just let it time out instead of explicitly removing the subscriber */ |
---|
465 | /* remove also the subscriber from the list if there was an error */ |
---|
466 | if(obj->state == EError && obj->sub) { |
---|
467 | LIST_REMOVE(obj->sub, entries); |
---|
468 | free(obj->sub); |
---|
469 | } |
---|
470 | #endif |
---|
471 | if(obj->buffer) { |
---|
472 | free(obj->buffer); |
---|
473 | } |
---|
474 | LIST_REMOVE(obj, entries); |
---|
475 | free(obj); |
---|
476 | } |
---|
477 | obj = next; |
---|
478 | } |
---|
479 | /* remove timeouted subscribers */ |
---|
480 | curtime = time(NULL); |
---|
481 | for(sub = subscriberlist.lh_first; sub != NULL; ) { |
---|
482 | subnext = sub->entries.le_next; |
---|
483 | if(sub->timeout && curtime > sub->timeout && sub->notify == NULL) { |
---|
484 | LIST_REMOVE(sub, entries); |
---|
485 | free(sub); |
---|
486 | } |
---|
487 | sub = subnext; |
---|
488 | } |
---|
489 | } |
---|
490 | |
---|
491 | #ifdef USE_MINIDLNACTL |
---|
492 | void write_events_details(int s) { |
---|
493 | int n; |
---|
494 | char buff[80]; |
---|
495 | struct upnp_event_notify * obj; |
---|
496 | struct subscriber * sub; |
---|
497 | write(s, "Events details\n", 15); |
---|
498 | for(obj = notifylist.lh_first; obj != NULL; obj = obj->entries.le_next) { |
---|
499 | n = snprintf(buff, sizeof(buff), " %p sub=%p state=%d s=%d\n", |
---|
500 | obj, obj->sub, obj->state, obj->s); |
---|
501 | write(s, buff, n); |
---|
502 | } |
---|
503 | write(s, "Subscribers :\n", 14); |
---|
504 | for(sub = subscriberlist.lh_first; sub != NULL; sub = sub->entries.le_next) { |
---|
505 | n = snprintf(buff, sizeof(buff), " %p timeout=%d seq=%u service=%d\n", |
---|
506 | sub, sub->timeout, sub->seq, sub->service); |
---|
507 | write(s, buff, n); |
---|
508 | n = snprintf(buff, sizeof(buff), " notify=%p %s\n", |
---|
509 | sub->notify, sub->uuid); |
---|
510 | write(s, buff, n); |
---|
511 | n = snprintf(buff, sizeof(buff), " %s\n", |
---|
512 | sub->callback); |
---|
513 | write(s, buff, n); |
---|
514 | } |
---|
515 | } |
---|
516 | #endif |
---|