source: mainline/uspace/lib/hound/src/protocol.c@ e5bc912

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since e5bc912 was d120133, checked in by Jan Vesely <jano.vesely@…>, 12 years ago

Use memset instead of bzero

  • Property mode set to 100644
File size: 19.6 KB
Line 
1/*
2 * Copyright (c) 2012 Jan Vesely
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup libhound
30 * @addtogroup audio
31 * @{
32 */
33/** @file
34 * Common USB functions.
35 */
36#include <adt/list.h>
37#include <errno.h>
38#include <loc.h>
39#include <macros.h>
40#include <str.h>
41#include <stdlib.h>
42#include <stdio.h>
43#include <libarch/types.h>
44
45#include "protocol.h"
46#include "client.h"
47#include "server.h"
48
49enum ipc_methods {
50 /** Create new context representation on the server side */
51 IPC_M_HOUND_CONTEXT_REGISTER = IPC_FIRST_USER_METHOD,
52 /** Release existing context representation on the server side */
53 IPC_M_HOUND_CONTEXT_UNREGISTER,
54 /** Request list of objects */
55 IPC_M_HOUND_GET_LIST,
56 /** Create new connection */
57 IPC_M_HOUND_CONNECT,
58 /** Destroy connection */
59 IPC_M_HOUND_DISCONNECT,
60 /** Switch IPC pipe to stream mode */
61 IPC_M_HOUND_STREAM_ENTER,
62 /** Switch IPC pipe back to general mode */
63 IPC_M_HOUND_STREAM_EXIT,
64 /** Wait until there is no data in the stream */
65 IPC_M_HOUND_STREAM_DRAIN,
66};
67
68
69/** PCM format conversion helper structure */
70typedef union {
71 struct {
72 uint16_t rate;
73 uint8_t channels;
74 uint8_t format;
75 } f __attribute__((packed));
76 sysarg_t arg;
77} format_convert_t;
78
79
80/****
81 * CLIENT
82 ****/
83
84/** Well defined service name */
85const char *HOUND_SERVICE = "audio/hound";
86
87/**
88 * Start a new audio session.
89 * @param service Named service typically 'HOUND_SERVICE' constant.
90 * @return Valid session on success, NULL on failure.
91 */
92hound_sess_t *hound_service_connect(const char *service)
93{
94 service_id_t id = 0;
95 const int ret =
96 loc_service_get_id(service, &id, IPC_FLAG_BLOCKING);
97 if (ret != EOK)
98 return NULL;
99 return loc_service_connect(EXCHANGE_PARALLEL, id, IPC_FLAG_BLOCKING);
100}
101
102/**
103 * End an existing audio session.
104 * @param sess The session.
105 */
106void hound_service_disconnect(hound_sess_t *sess)
107{
108 if (sess)
109 async_hangup(sess);
110}
111
112/**
113 * Register a named application context to the audio server.
114 * @param sess Valid audio session.
115 * @param name Valid string identifier
116 * @param record True if the application context wishes to receive data.
117 * @return Valid ID on success, Error code on failure.
118 */
119hound_context_id_t hound_service_register_context(hound_sess_t *sess,
120 const char *name, bool record)
121{
122 assert(sess);
123 assert(name);
124 ipc_call_t call;
125 async_exch_t *exch = async_exchange_begin(sess);
126 aid_t mid =
127 async_send_1(exch, IPC_M_HOUND_CONTEXT_REGISTER, record, &call);
128 int ret = mid ? EOK : EPARTY;
129
130 if (ret == EOK)
131 ret = async_data_write_start(exch, name, str_size(name));
132 else
133 async_forget(mid);
134
135 if (ret == EOK)
136 async_wait_for(mid, (sysarg_t *)&ret);
137
138 async_exchange_end(exch);
139 return ret == EOK ? (hound_context_id_t)IPC_GET_ARG1(call) : ret;
140}
141
142/**
143 * Remove application context from the server's list.
144 * @param sess Valid audio session.
145 * @param id Valid context id.
146 * @return Error code.
147 */
148int hound_service_unregister_context(hound_sess_t *sess, hound_context_id_t id)
149{
150 assert(sess);
151 async_exch_t *exch = async_exchange_begin(sess);
152 const int ret =
153 async_req_1_0(exch, IPC_M_HOUND_CONTEXT_UNREGISTER, id);
154 async_exchange_end(exch);
155 return ret;
156}
157
158/**
159 * Retrieve a list of server side actors.
160 * @param[in] sess Valid audio session.
161 * @param[out] ids list of string identifiers.
162 * @param[out] count Number of elements int the @p ids list.
163 * @param[in] flags list requirements.
164 * @param[in] connection name of target actor. Used only if the list should
165 * contain connected actors.
166 * @retval Error code.
167 */
168int hound_service_get_list(hound_sess_t *sess, const char ***ids, size_t *count,
169 int flags, const char *connection)
170{
171 assert(sess);
172 assert(ids);
173 assert(count);
174
175 if (connection && !(flags & HOUND_CONNECTED))
176 return EINVAL;
177
178 async_exch_t *exch = async_exchange_begin(sess);
179 if (!exch)
180 return ENOMEM;
181
182 ipc_call_t res_call;
183 aid_t mid = async_send_3(exch, IPC_M_HOUND_GET_LIST, flags, *count,
184 (bool)connection, &res_call);
185
186 int ret = EOK;
187 if (mid && connection)
188 ret = async_data_write_start(exch, connection,
189 str_size(connection));
190
191 if (ret == EOK)
192 async_wait_for(mid, (sysarg_t*)&ret);
193
194 if (ret != EOK) {
195 async_exchange_end(exch);
196 return ret;
197 }
198 unsigned name_count = IPC_GET_ARG1(res_call);
199
200 /* Start receiving names */
201 const char **names = NULL;
202 if (name_count) {
203 size_t *sizes = calloc(name_count, sizeof(size_t));
204 names = calloc(name_count, sizeof(char *));
205 if (!names || !sizes)
206 ret = ENOMEM;
207
208 if (ret == EOK)
209 ret = async_data_read_start(exch, sizes,
210 name_count * sizeof(size_t));
211 for (unsigned i = 0; i < name_count && ret == EOK; ++i) {
212 char *name = malloc(sizes[i] + 1);
213 if (name) {
214 memset(name, 0, sizes[i] + 1);
215 ret = async_data_read_start(exch, name, sizes[i]);
216 names[i] = name;
217 } else {
218 ret = ENOMEM;
219 }
220 }
221 free(sizes);
222 }
223 async_exchange_end(exch);
224 if (ret != EOK) {
225 for (unsigned i = 0; i < name_count; ++i)
226 free(names[i]);
227 free(names);
228 } else {
229 *ids = names;
230 *count = name_count;
231 }
232 return ret;
233}
234
235/**
236 * Create a new connection between a source and a sink.
237 * @param sess Valid audio session.
238 * @param source Source name, valid string.
239 * @param sink Sink name, valid string.
240 * @return Error code.
241 */
242int hound_service_connect_source_sink(hound_sess_t *sess, const char *source,
243 const char *sink)
244{
245 assert(sess);
246 assert(source);
247 assert(sink);
248
249 async_exch_t *exch = async_exchange_begin(sess);
250 if (!exch)
251 return ENOMEM;
252 ipc_call_t call;
253 aid_t id = async_send_0(exch, IPC_M_HOUND_CONNECT, &call);
254 int ret = id ? EOK : EPARTY;
255 if (ret == EOK)
256 ret = async_data_write_start(exch, source, str_size(source));
257 if (ret == EOK)
258 ret = async_data_write_start(exch, sink, str_size(sink));
259 async_wait_for(id, (sysarg_t*)&ret);
260 async_exchange_end(exch);
261 return ret;
262}
263
264/**
265 * Destroy an existing connection between a source and a sink.
266 * @param sess Valid audio session.
267 * @param source Source name, valid string.
268 * @param sink Sink name, valid string.
269 * @return Error code.
270 */
271int hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source,
272 const char *sink)
273{
274 assert(sess);
275 async_exch_t *exch = async_exchange_begin(sess);
276 if (!exch)
277 return ENOMEM;
278 ipc_call_t call;
279 aid_t id = async_send_0(exch, IPC_M_HOUND_DISCONNECT, &call);
280 int ret = id ? EOK : EPARTY;
281 if (ret == EOK)
282 ret = async_data_write_start(exch, source, str_size(source));
283 if (ret == EOK)
284 ret = async_data_write_start(exch, sink, str_size(sink));
285 async_wait_for(id, (sysarg_t*)&ret);
286 async_exchange_end(exch);
287 return ENOTSUP;
288}
289
290/**
291 * Switch IPC exchange to a STREAM mode.
292 * @param exch IPC exchange.
293 * @param id context id this stream should be associated with
294 * @param flags set stream properties
295 * @param format format of the new stream.
296 * @param bsize size of the server side buffer.
297 * @return Error code.
298 */
299int hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id,
300 int flags, pcm_format_t format, size_t bsize)
301{
302 const format_convert_t c = { .f = {
303 .channels = format.channels,
304 .rate = format.sampling_rate / 100,
305 .format = format.sample_format,
306 }};
307 return async_req_4_0(exch, IPC_M_HOUND_STREAM_ENTER, id, flags,
308 c.arg, bsize);
309}
310
311/**
312 * Destroy existing stream and return IPC exchange to general mode.
313 * @param exch IPC exchange.
314 * @return Error code.
315 */
316int hound_service_stream_exit(async_exch_t *exch)
317{
318 return async_req_0_0(exch, IPC_M_HOUND_STREAM_EXIT);
319}
320
321/**
322 * Wait until the server side buffer is empty.
323 * @param exch IPC exchange.
324 * @return Error code.
325 */
326int hound_service_stream_drain(async_exch_t *exch)
327{
328 return async_req_0_0(exch, IPC_M_HOUND_STREAM_DRAIN);
329}
330
331/**
332 * Write audio data to a stream.
333 * @param exch IPC exchange in STREAM MODE.
334 * @param data Audio data buffer.
335 * @size size of the buffer
336 * @return Error code.
337 */
338int hound_service_stream_write(async_exch_t *exch, const void *data, size_t size)
339{
340 return async_data_write_start(exch, data, size);
341}
342
343/**
344 * Read data from a stream.
345 * @param exch IPC exchange in STREAM MODE.
346 * @param data Audio data buffer.
347 * @size size of the buffer
348 * @return Error code.
349 */
350int hound_service_stream_read(async_exch_t *exch, void *data, size_t size)
351{
352 return async_data_read_start(exch, data, size);
353}
354
355/****
356 * SERVER
357 ****/
358
359static void hound_server_read_data(void *stream);
360static void hound_server_write_data(void *stream);
361static const hound_server_iface_t *server_iface;
362
363/**
364 * Set hound server interface implementation.
365 * @param iface Initialized Hound server interface.
366 */
367void hound_service_set_server_iface(const hound_server_iface_t *iface)
368{
369 server_iface = iface;
370}
371
372/**
373 * Server side implementation of the hound protocol. IPC connection handler.
374 * @param iid initial call id
375 * @param icall pointer to initial call structure.
376 * @param arg (unused)
377 */
378void hound_connection_handler(ipc_callid_t iid, ipc_call_t *icall, void *arg)
379{
380 /* Accept connection if there is a valid iface*/
381 if (server_iface) {
382 async_answer_0(iid, EOK);
383 } else {
384 async_answer_0(iid, ENOTSUP);
385 return;
386 }
387
388 while (1) {
389 ipc_call_t call;
390 ipc_callid_t callid = async_get_call(&call);
391 switch (IPC_GET_IMETHOD(call)) {
392 case IPC_M_HOUND_CONTEXT_REGISTER: {
393 /* check interface functions */
394 if (!server_iface || !server_iface->add_context) {
395 async_answer_0(callid, ENOTSUP);
396 break;
397 }
398 bool record = IPC_GET_ARG1(call);
399 void *name;
400
401 /* Get context name */
402 int ret =
403 async_data_write_accept(&name, true, 0, 0, 0, 0);
404 if (ret != EOK) {
405 async_answer_0(callid, ret);
406 break;
407 }
408 hound_context_id_t id = 0;
409 ret = server_iface->add_context(server_iface->server,
410 &id, name, record);
411 /** new context should create a copy */
412 free(name);
413 if (ret != EOK) {
414 async_answer_0(callid, ret);
415 } else {
416 async_answer_1(callid, EOK, id);
417 }
418 break;
419 }
420 case IPC_M_HOUND_CONTEXT_UNREGISTER: {
421 /* check interface functions */
422 if (!server_iface || !server_iface->rem_context) {
423 async_answer_0(callid, ENOTSUP);
424 break;
425 }
426
427 /* get id, 1st param */
428 hound_context_id_t id = IPC_GET_ARG1(call);
429 const int ret =
430 server_iface->rem_context(server_iface->server, id);
431 async_answer_0(callid, ret);
432 break;
433 }
434 case IPC_M_HOUND_GET_LIST: {
435 /* check interface functions */
436 if (!server_iface || !server_iface->get_list) {
437 async_answer_0(callid, ENOTSUP);
438 break;
439 }
440
441 const char **list = NULL;
442 const int flags = IPC_GET_ARG1(call);
443 size_t count = IPC_GET_ARG2(call);
444 const bool conn = IPC_GET_ARG3(call);
445 char *conn_name = NULL;
446 int ret = EOK;
447
448 /* get connected actor name if provided */
449 if (conn)
450 ret = async_data_write_accept(
451 (void**)&conn_name, true, 0, 0, 0, 0);
452
453 if (ret == EOK)
454 ret = server_iface->get_list(
455 server_iface->server, &list, &count,
456 conn_name, flags);
457 free(conn_name);
458
459 /* Alloc string sizes array */
460 size_t *sizes = NULL;
461 if (count)
462 sizes = calloc(count, sizeof(size_t));
463 if (count && !sizes)
464 ret = ENOMEM;
465 async_answer_1(callid, ret, count);
466
467 /* We are done */
468 if (count == 0 || ret != EOK)
469 break;
470
471 /* Prepare sizes table */
472 for (unsigned i = 0; i < count; ++i)
473 sizes[i] = str_size(list[i]);
474
475 /* Send sizes table */
476 ipc_callid_t id;
477 if (async_data_read_receive(&id, NULL)) {
478 ret = async_data_read_finalize(id, sizes,
479 count * sizeof(size_t));
480 }
481 free(sizes);
482
483 /* Proceed to send names */
484 for (unsigned i = 0; i < count; ++i) {
485 size_t size = str_size(list[i]);
486 ipc_callid_t id;
487 if (ret == EOK &&
488 async_data_read_receive(&id, NULL)) {
489 ret = async_data_read_finalize(id,
490 list[i], size);
491 }
492 free(list[i]);
493 }
494 free(list);
495 break;
496 }
497 case IPC_M_HOUND_CONNECT: {
498 /* check interface functions */
499 if (!server_iface || !server_iface->connect) {
500 async_answer_0(callid, ENOTSUP);
501 break;
502 }
503
504 void *source = NULL;
505 void *sink = NULL;
506
507 /* read source name */
508 int ret =
509 async_data_write_accept(&source, true, 0, 0, 0, 0);
510 /* read sink name */
511 if (ret == EOK)
512 ret = async_data_write_accept(&sink,
513 true, 0, 0, 0, 0);
514
515 if (ret == EOK)
516 ret = server_iface->connect(
517 server_iface->server, source, sink);
518 free(source);
519 free(sink);
520 async_answer_0(callid, ret);
521 break;
522 }
523 case IPC_M_HOUND_DISCONNECT: {
524 /* check interface functions */
525 if (!server_iface || !server_iface->disconnect) {
526 async_answer_0(callid, ENOTSUP);
527 break;
528 }
529
530 void *source = NULL;
531 void *sink = NULL;
532
533 /* read source name */
534 int ret =
535 async_data_write_accept(&source, true, 0, 0, 0, 0);
536 /*read sink name */
537 if (ret == EOK)
538 ret = async_data_write_accept(&sink,
539 true, 0, 0, 0, 0);
540 if (ret == EOK)
541 ret = server_iface->connect(
542 server_iface->server, source, sink);
543 free(source);
544 free(sink);
545 async_answer_0(callid, ret);
546 break;
547 }
548 case IPC_M_HOUND_STREAM_ENTER: {
549 /* check interface functions */
550 if (!server_iface || !server_iface->is_record_context
551 || !server_iface->add_stream
552 || !server_iface->rem_stream) {
553 async_answer_0(callid, ENOTSUP);
554 break;
555 }
556
557 /* get parameters */
558 hound_context_id_t id = IPC_GET_ARG1(call);
559 const int flags = IPC_GET_ARG2(call);
560 const format_convert_t c = {.arg = IPC_GET_ARG3(call)};
561 const pcm_format_t f = {
562 .sampling_rate = c.f.rate * 100,
563 .channels = c.f.channels,
564 .sample_format = c.f.format,
565 };
566 size_t bsize = IPC_GET_ARG4(call);
567
568 void *stream;
569 int ret = server_iface->add_stream(server_iface->server,
570 id, flags, f, bsize, &stream);
571 if (ret != EOK) {
572 async_answer_0(callid, ret);
573 break;
574 }
575 const bool rec = server_iface->is_record_context(
576 server_iface->server, id);
577 if (rec) {
578 if(server_iface->stream_data_read) {
579 async_answer_0(callid, EOK);
580 /* start answering read calls */
581 hound_server_write_data(stream);
582 server_iface->rem_stream(
583 server_iface->server, stream);
584 } else {
585 async_answer_0(callid, ENOTSUP);
586 }
587 } else {
588 if (server_iface->stream_data_write) {
589 async_answer_0(callid, EOK);
590 /* accept write calls */
591 hound_server_read_data(stream);
592 server_iface->rem_stream(
593 server_iface->server, stream);
594 } else {
595 async_answer_0(callid, ENOTSUP);
596 }
597 }
598 break;
599 }
600 case IPC_M_HOUND_STREAM_EXIT:
601 case IPC_M_HOUND_STREAM_DRAIN:
602 /* Stream exit/drain is only allowed in stream context*/
603 async_answer_0(callid, EINVAL);
604 break;
605 default:
606 async_answer_0(callid, ENOTSUP);
607 return;
608 }
609 }
610}
611
612/**
613 * Read data and push it to the stream.
614 * @param stream target stream, will push data there.
615 */
616static void hound_server_read_data(void *stream)
617{
618 ipc_callid_t callid;
619 ipc_call_t call;
620 size_t size = 0;
621 int ret_answer = EOK;
622 /* accept data write or drain */
623 while (async_data_write_receive_call(&callid, &call, &size)
624 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
625 /* check drain first */
626 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
627 int ret = ENOTSUP;
628 if (server_iface->drain_stream)
629 ret = server_iface->drain_stream(stream);
630 async_answer_0(callid, ret);
631 continue;
632 }
633
634 /* there was an error last time */
635 if (ret_answer != EOK) {
636 async_answer_0(callid, ret_answer);
637 continue;
638 }
639
640 char *buffer = malloc(size);
641 if (!buffer) {
642 async_answer_0(callid, ENOMEM);
643 continue;
644 }
645 const int ret = async_data_write_finalize(callid, buffer, size);
646 if (ret == EOK) {
647 /* push data to stream */
648 ret_answer = server_iface->stream_data_write(
649 stream, buffer, size);
650 }
651 }
652 const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
653 ? EOK : EINVAL;
654
655 async_answer_0(callid, ret);
656}
657
658/**
659 * Accept reads and pull data from the stream.
660 * @param stream target stream, will pull data from there.
661 */
662static void hound_server_write_data(void *stream)
663{
664
665 ipc_callid_t callid;
666 ipc_call_t call;
667 size_t size = 0;
668 int ret_answer = EOK;
669 /* accept data read and drain */
670 while (async_data_read_receive_call(&callid, &call, &size)
671 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
672 /* drain does not make much sense but it is allowed */
673 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
674 int ret = ENOTSUP;
675 if (server_iface->drain_stream)
676 ret = server_iface->drain_stream(stream);
677 async_answer_0(callid, ret);
678 continue;
679 }
680 /* there was an error last time */
681 if (ret_answer != EOK) {
682 async_answer_0(callid, ret_answer);
683 continue;
684 }
685 char *buffer = malloc(size);
686 if (!buffer) {
687 async_answer_0(callid, ENOMEM);
688 continue;
689 }
690 int ret = server_iface->stream_data_read(stream, buffer, size);
691 if (ret == EOK) {
692 ret_answer =
693 async_data_read_finalize(callid, buffer, size);
694 }
695 }
696 const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
697 ? EOK : EINVAL;
698
699 async_answer_0(callid, ret);
700}
701
702
703/***
704 * SERVER SIDE
705 ***/
706
707/**
708 * Register new hound service to the location service.
709 * @param[in] name server name
710 * @param[out] id assigned service id.
711 * @return Error code.
712 */
713int hound_server_register(const char *name, service_id_t *id)
714{
715 if (!name || !id)
716 return EINVAL;
717
718 int ret = loc_server_register(name);
719 if (ret != EOK)
720 return ret;
721
722 return loc_service_register(HOUND_SERVICE, id);
723}
724
725/**
726 * Unregister server from the location service.
727 * @param id previously assigned service id.
728 */
729void hound_server_unregister(service_id_t id)
730{
731 loc_service_unregister(id);
732}
733
734/**
735 * Set callback on device category change event.
736 * @param cb Callback function.
737 * @return Error code.
738 */
739int hound_server_set_device_change_callback(dev_change_callback_t cb)
740{
741 return loc_register_cat_change_cb(cb);
742}
743
744/**
745 * Walk through all device in the audio-pcm category.
746 * @param callback Function to call on every device.
747 * @return Error code.
748 */
749int hound_server_devices_iterate(device_callback_t callback)
750{
751 if (!callback)
752 return EINVAL;
753 static bool resolved = false;
754 static category_id_t cat_id = 0;
755
756 if (!resolved) {
757 const int ret = loc_category_get_id("audio-pcm", &cat_id,
758 IPC_FLAG_BLOCKING);
759 if (ret != EOK)
760 return ret;
761 resolved = true;
762 }
763
764 service_id_t *svcs = NULL;
765 size_t count = 0;
766 const int ret = loc_category_get_svcs(cat_id, &svcs, &count);
767 if (ret != EOK)
768 return ret;
769
770 for (unsigned i = 0; i < count; ++i) {
771 char *name = NULL;
772 loc_service_get_name(svcs[i], &name);
773 callback(svcs[i], name);
774 free(name);
775 }
776 free(svcs);
777 return EOK;
778}
779/**
780 * @}
781 */
Note: See TracBrowser for help on using the repository browser.