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

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 132ab5d1 was 25a179e, checked in by Jiří Zárevúcky <zarevucky.jiri@…>, 8 years ago

IPC return values are always errno constants. Adjust types to reflect that.

In principle, IPC server is not allowed to return non-errno values via
the "main" return value, because kernel interprets it (e.g. EHANGUP).
It's still possible to return arbitrary additional return values alongside EOK,
which are not interpreted in normal communication.

  • Property mode set to 100644
File size: 19.7 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 <types/common.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(id, INTERFACE_HOUND, 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 *
118 * @param[out] id Return context ID.
119 *
120 * @return EOK on success, Error code on failure.
121 */
122int hound_service_register_context(hound_sess_t *sess,
123 const char *name, bool record, hound_context_id_t *id)
124{
125 assert(sess);
126 assert(name);
127 ipc_call_t call;
128 async_exch_t *exch = async_exchange_begin(sess);
129 aid_t mid =
130 async_send_1(exch, IPC_M_HOUND_CONTEXT_REGISTER, record, &call);
131 int ret = mid ? EOK : EPARTY;
132
133 if (ret == EOK)
134 ret = async_data_write_start(exch, name, str_size(name));
135 else
136 async_forget(mid);
137
138 if (ret == EOK)
139 async_wait_for(mid, &ret);
140
141 async_exchange_end(exch);
142 if (ret == EOK) {
143 *id = (hound_context_id_t)IPC_GET_ARG1(call);
144 }
145
146 return ret;
147}
148
149/**
150 * Remove application context from the server's list.
151 * @param sess Valid audio session.
152 * @param id Valid context id.
153 * @return Error code.
154 */
155int hound_service_unregister_context(hound_sess_t *sess, hound_context_id_t id)
156{
157 assert(sess);
158 async_exch_t *exch = async_exchange_begin(sess);
159 const int ret =
160 async_req_1_0(exch, IPC_M_HOUND_CONTEXT_UNREGISTER, id);
161 async_exchange_end(exch);
162 return ret;
163}
164
165/**
166 * Retrieve a list of server side actors.
167 * @param[in] sess Valid audio session.
168 * @param[out] ids list of string identifiers.
169 * @param[out] count Number of elements int the @p ids list.
170 * @param[in] flags list requirements.
171 * @param[in] connection name of target actor. Used only if the list should
172 * contain connected actors.
173 * @retval Error code.
174 */
175int hound_service_get_list(hound_sess_t *sess, const char ***ids, size_t *count,
176 int flags, const char *connection)
177{
178 assert(sess);
179 assert(ids);
180 assert(count);
181
182 if (connection && !(flags & HOUND_CONNECTED))
183 return EINVAL;
184
185 async_exch_t *exch = async_exchange_begin(sess);
186 if (!exch)
187 return ENOMEM;
188
189 ipc_call_t res_call;
190 aid_t mid = async_send_3(exch, IPC_M_HOUND_GET_LIST, flags, *count,
191 connection != NULL, &res_call);
192
193 int ret = EOK;
194 if (mid && connection)
195 ret = async_data_write_start(exch, connection,
196 str_size(connection));
197
198 if (ret == EOK)
199 async_wait_for(mid, &ret);
200
201 if (ret != EOK) {
202 async_exchange_end(exch);
203 return ret;
204 }
205 unsigned name_count = IPC_GET_ARG1(res_call);
206
207 /* Start receiving names */
208 const char **names = NULL;
209 if (name_count) {
210 size_t *sizes = calloc(name_count, sizeof(size_t));
211 names = calloc(name_count, sizeof(char *));
212 if (!names || !sizes)
213 ret = ENOMEM;
214
215 if (ret == EOK)
216 ret = async_data_read_start(exch, sizes,
217 name_count * sizeof(size_t));
218 for (unsigned i = 0; i < name_count && ret == EOK; ++i) {
219 char *name = malloc(sizes[i] + 1);
220 if (name) {
221 memset(name, 0, sizes[i] + 1);
222 ret = async_data_read_start(exch, name, sizes[i]);
223 names[i] = name;
224 } else {
225 ret = ENOMEM;
226 }
227 }
228 free(sizes);
229 }
230 async_exchange_end(exch);
231 if (ret != EOK) {
232 for (unsigned i = 0; i < name_count; ++i)
233 free(names[i]);
234 free(names);
235 } else {
236 *ids = names;
237 *count = name_count;
238 }
239 return ret;
240}
241
242/**
243 * Create a new connection between a source and a sink.
244 * @param sess Valid audio session.
245 * @param source Source name, valid string.
246 * @param sink Sink name, valid string.
247 * @return Error code.
248 */
249int hound_service_connect_source_sink(hound_sess_t *sess, const char *source,
250 const char *sink)
251{
252 assert(sess);
253 assert(source);
254 assert(sink);
255
256 async_exch_t *exch = async_exchange_begin(sess);
257 if (!exch)
258 return ENOMEM;
259 ipc_call_t call;
260 aid_t id = async_send_0(exch, IPC_M_HOUND_CONNECT, &call);
261 int ret = id ? EOK : EPARTY;
262 if (ret == EOK)
263 ret = async_data_write_start(exch, source, str_size(source));
264 if (ret == EOK)
265 ret = async_data_write_start(exch, sink, str_size(sink));
266 async_wait_for(id, &ret);
267 async_exchange_end(exch);
268 return ret;
269}
270
271/**
272 * Destroy an existing connection between a source and a sink.
273 * @param sess Valid audio session.
274 * @param source Source name, valid string.
275 * @param sink Sink name, valid string.
276 * @return Error code.
277 */
278int hound_service_disconnect_source_sink(hound_sess_t *sess, const char *source,
279 const char *sink)
280{
281 assert(sess);
282 async_exch_t *exch = async_exchange_begin(sess);
283 if (!exch)
284 return ENOMEM;
285 ipc_call_t call;
286 aid_t id = async_send_0(exch, IPC_M_HOUND_DISCONNECT, &call);
287 int ret = id ? EOK : EPARTY;
288 if (ret == EOK)
289 ret = async_data_write_start(exch, source, str_size(source));
290 if (ret == EOK)
291 ret = async_data_write_start(exch, sink, str_size(sink));
292 async_wait_for(id, &ret);
293 async_exchange_end(exch);
294 return ENOTSUP;
295}
296
297/**
298 * Switch IPC exchange to a STREAM mode.
299 * @param exch IPC exchange.
300 * @param id context id this stream should be associated with
301 * @param flags set stream properties
302 * @param format format of the new stream.
303 * @param bsize size of the server side buffer.
304 * @return Error code.
305 */
306int hound_service_stream_enter(async_exch_t *exch, hound_context_id_t id,
307 int flags, pcm_format_t format, size_t bsize)
308{
309 const format_convert_t c = { .f = {
310 .channels = format.channels,
311 .rate = format.sampling_rate / 100,
312 .format = format.sample_format,
313 }};
314 return async_req_4_0(exch, IPC_M_HOUND_STREAM_ENTER, id, flags,
315 c.arg, bsize);
316}
317
318/**
319 * Destroy existing stream and return IPC exchange to general mode.
320 * @param exch IPC exchange.
321 * @return Error code.
322 */
323int hound_service_stream_exit(async_exch_t *exch)
324{
325 return async_req_0_0(exch, IPC_M_HOUND_STREAM_EXIT);
326}
327
328/**
329 * Wait until the server side buffer is empty.
330 * @param exch IPC exchange.
331 * @return Error code.
332 */
333int hound_service_stream_drain(async_exch_t *exch)
334{
335 return async_req_0_0(exch, IPC_M_HOUND_STREAM_DRAIN);
336}
337
338/**
339 * Write audio data to a stream.
340 * @param exch IPC exchange in STREAM MODE.
341 * @param data Audio data buffer.
342 * @size size of the buffer
343 * @return Error code.
344 */
345int hound_service_stream_write(async_exch_t *exch, const void *data, size_t size)
346{
347 return async_data_write_start(exch, data, size);
348}
349
350/**
351 * Read data from a stream.
352 * @param exch IPC exchange in STREAM MODE.
353 * @param data Audio data buffer.
354 * @size size of the buffer
355 * @return Error code.
356 */
357int hound_service_stream_read(async_exch_t *exch, void *data, size_t size)
358{
359 return async_data_read_start(exch, data, size);
360}
361
362/****
363 * SERVER
364 ****/
365
366static void hound_server_read_data(void *stream);
367static void hound_server_write_data(void *stream);
368static const hound_server_iface_t *server_iface;
369
370/**
371 * Set hound server interface implementation.
372 * @param iface Initialized Hound server interface.
373 */
374void hound_service_set_server_iface(const hound_server_iface_t *iface)
375{
376 server_iface = iface;
377}
378
379/**
380 * Server side implementation of the hound protocol. IPC connection handler.
381 * @param iid initial call id
382 * @param icall pointer to initial call structure.
383 * @param arg (unused)
384 */
385void hound_connection_handler(ipc_callid_t iid, ipc_call_t *icall, void *arg)
386{
387 /* Accept connection if there is a valid iface*/
388 if (server_iface) {
389 async_answer_0(iid, EOK);
390 } else {
391 async_answer_0(iid, ENOTSUP);
392 return;
393 }
394
395 while (1) {
396 ipc_call_t call;
397 ipc_callid_t callid = async_get_call(&call);
398 switch (IPC_GET_IMETHOD(call)) {
399 case IPC_M_HOUND_CONTEXT_REGISTER: {
400 /* check interface functions */
401 if (!server_iface || !server_iface->add_context) {
402 async_answer_0(callid, ENOTSUP);
403 break;
404 }
405 bool record = IPC_GET_ARG1(call);
406 void *name;
407
408 /* Get context name */
409 int ret =
410 async_data_write_accept(&name, true, 0, 0, 0, 0);
411 if (ret != EOK) {
412 async_answer_0(callid, ret);
413 break;
414 }
415 hound_context_id_t id = 0;
416 ret = server_iface->add_context(server_iface->server,
417 &id, name, record);
418 /** new context should create a copy */
419 free(name);
420 if (ret != EOK) {
421 async_answer_0(callid, ret);
422 } else {
423 async_answer_1(callid, EOK, id);
424 }
425 break;
426 }
427 case IPC_M_HOUND_CONTEXT_UNREGISTER: {
428 /* check interface functions */
429 if (!server_iface || !server_iface->rem_context) {
430 async_answer_0(callid, ENOTSUP);
431 break;
432 }
433
434 /* get id, 1st param */
435 hound_context_id_t id = IPC_GET_ARG1(call);
436 const int ret =
437 server_iface->rem_context(server_iface->server, id);
438 async_answer_0(callid, ret);
439 break;
440 }
441 case IPC_M_HOUND_GET_LIST: {
442 /* check interface functions */
443 if (!server_iface || !server_iface->get_list) {
444 async_answer_0(callid, ENOTSUP);
445 break;
446 }
447
448 const char **list = NULL;
449 const int flags = IPC_GET_ARG1(call);
450 size_t count = IPC_GET_ARG2(call);
451 const bool conn = IPC_GET_ARG3(call);
452 char *conn_name = NULL;
453 int ret = EOK;
454
455 /* get connected actor name if provided */
456 if (conn)
457 ret = async_data_write_accept(
458 (void**)&conn_name, true, 0, 0, 0, 0);
459
460 if (ret == EOK)
461 ret = server_iface->get_list(
462 server_iface->server, &list, &count,
463 conn_name, flags);
464 free(conn_name);
465
466 /* Alloc string sizes array */
467 size_t *sizes = NULL;
468 if (count)
469 sizes = calloc(count, sizeof(size_t));
470 if (count && !sizes)
471 ret = ENOMEM;
472 async_answer_1(callid, ret, count);
473
474 /* We are done */
475 if (count == 0 || ret != EOK)
476 break;
477
478 /* Prepare sizes table */
479 for (unsigned i = 0; i < count; ++i)
480 sizes[i] = str_size(list[i]);
481
482 /* Send sizes table */
483 ipc_callid_t id;
484 if (async_data_read_receive(&id, NULL)) {
485 ret = async_data_read_finalize(id, sizes,
486 count * sizeof(size_t));
487 }
488 free(sizes);
489
490 /* Proceed to send names */
491 for (unsigned i = 0; i < count; ++i) {
492 size_t size = str_size(list[i]);
493 ipc_callid_t id;
494 if (ret == EOK &&
495 async_data_read_receive(&id, NULL)) {
496 ret = async_data_read_finalize(id,
497 list[i], size);
498 }
499 free(list[i]);
500 }
501 free(list);
502 break;
503 }
504 case IPC_M_HOUND_CONNECT: {
505 /* check interface functions */
506 if (!server_iface || !server_iface->connect) {
507 async_answer_0(callid, ENOTSUP);
508 break;
509 }
510
511 void *source = NULL;
512 void *sink = NULL;
513
514 /* read source name */
515 int ret =
516 async_data_write_accept(&source, true, 0, 0, 0, 0);
517 /* read sink name */
518 if (ret == EOK)
519 ret = async_data_write_accept(&sink,
520 true, 0, 0, 0, 0);
521
522 if (ret == EOK)
523 ret = server_iface->connect(
524 server_iface->server, source, sink);
525 free(source);
526 free(sink);
527 async_answer_0(callid, ret);
528 break;
529 }
530 case IPC_M_HOUND_DISCONNECT: {
531 /* check interface functions */
532 if (!server_iface || !server_iface->disconnect) {
533 async_answer_0(callid, ENOTSUP);
534 break;
535 }
536
537 void *source = NULL;
538 void *sink = NULL;
539
540 /* read source name */
541 int ret =
542 async_data_write_accept(&source, true, 0, 0, 0, 0);
543 /*read sink name */
544 if (ret == EOK)
545 ret = async_data_write_accept(&sink,
546 true, 0, 0, 0, 0);
547 if (ret == EOK)
548 ret = server_iface->connect(
549 server_iface->server, source, sink);
550 free(source);
551 free(sink);
552 async_answer_0(callid, ret);
553 break;
554 }
555 case IPC_M_HOUND_STREAM_ENTER: {
556 /* check interface functions */
557 if (!server_iface || !server_iface->is_record_context
558 || !server_iface->add_stream
559 || !server_iface->rem_stream) {
560 async_answer_0(callid, ENOTSUP);
561 break;
562 }
563
564 /* get parameters */
565 hound_context_id_t id = IPC_GET_ARG1(call);
566 const int flags = IPC_GET_ARG2(call);
567 const format_convert_t c = {.arg = IPC_GET_ARG3(call)};
568 const pcm_format_t f = {
569 .sampling_rate = c.f.rate * 100,
570 .channels = c.f.channels,
571 .sample_format = c.f.format,
572 };
573 size_t bsize = IPC_GET_ARG4(call);
574
575 void *stream;
576 int ret = server_iface->add_stream(server_iface->server,
577 id, flags, f, bsize, &stream);
578 if (ret != EOK) {
579 async_answer_0(callid, ret);
580 break;
581 }
582 const bool rec = server_iface->is_record_context(
583 server_iface->server, id);
584 if (rec) {
585 if(server_iface->stream_data_read) {
586 async_answer_0(callid, EOK);
587 /* start answering read calls */
588 hound_server_write_data(stream);
589 server_iface->rem_stream(
590 server_iface->server, stream);
591 } else {
592 async_answer_0(callid, ENOTSUP);
593 }
594 } else {
595 if (server_iface->stream_data_write) {
596 async_answer_0(callid, EOK);
597 /* accept write calls */
598 hound_server_read_data(stream);
599 server_iface->rem_stream(
600 server_iface->server, stream);
601 } else {
602 async_answer_0(callid, ENOTSUP);
603 }
604 }
605 break;
606 }
607 case IPC_M_HOUND_STREAM_EXIT:
608 case IPC_M_HOUND_STREAM_DRAIN:
609 /* Stream exit/drain is only allowed in stream context*/
610 async_answer_0(callid, EINVAL);
611 break;
612 default:
613 async_answer_0(callid, ENOTSUP);
614 return;
615 }
616 }
617}
618
619/**
620 * Read data and push it to the stream.
621 * @param stream target stream, will push data there.
622 */
623static void hound_server_read_data(void *stream)
624{
625 ipc_callid_t callid;
626 ipc_call_t call;
627 size_t size = 0;
628 int ret_answer = EOK;
629 /* accept data write or drain */
630 while (async_data_write_receive_call(&callid, &call, &size)
631 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
632 /* check drain first */
633 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
634 int ret = ENOTSUP;
635 if (server_iface->drain_stream)
636 ret = server_iface->drain_stream(stream);
637 async_answer_0(callid, ret);
638 continue;
639 }
640
641 /* there was an error last time */
642 if (ret_answer != EOK) {
643 async_answer_0(callid, ret_answer);
644 continue;
645 }
646
647 char *buffer = malloc(size);
648 if (!buffer) {
649 async_answer_0(callid, ENOMEM);
650 continue;
651 }
652 const int ret = async_data_write_finalize(callid, buffer, size);
653 if (ret == EOK) {
654 /* push data to stream */
655 ret_answer = server_iface->stream_data_write(
656 stream, buffer, size);
657 }
658 }
659 const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
660 ? EOK : EINVAL;
661
662 async_answer_0(callid, ret);
663}
664
665/**
666 * Accept reads and pull data from the stream.
667 * @param stream target stream, will pull data from there.
668 */
669static void hound_server_write_data(void *stream)
670{
671
672 ipc_callid_t callid;
673 ipc_call_t call;
674 size_t size = 0;
675 int ret_answer = EOK;
676 /* accept data read and drain */
677 while (async_data_read_receive_call(&callid, &call, &size)
678 || (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN)) {
679 /* drain does not make much sense but it is allowed */
680 if (IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_DRAIN) {
681 int ret = ENOTSUP;
682 if (server_iface->drain_stream)
683 ret = server_iface->drain_stream(stream);
684 async_answer_0(callid, ret);
685 continue;
686 }
687 /* there was an error last time */
688 if (ret_answer != EOK) {
689 async_answer_0(callid, ret_answer);
690 continue;
691 }
692 char *buffer = malloc(size);
693 if (!buffer) {
694 async_answer_0(callid, ENOMEM);
695 continue;
696 }
697 int ret = server_iface->stream_data_read(stream, buffer, size);
698 if (ret == EOK) {
699 ret_answer =
700 async_data_read_finalize(callid, buffer, size);
701 }
702 }
703 const int ret = IPC_GET_IMETHOD(call) == IPC_M_HOUND_STREAM_EXIT
704 ? EOK : EINVAL;
705
706 async_answer_0(callid, ret);
707}
708
709
710/***
711 * SERVER SIDE
712 ***/
713
714/**
715 * Register new hound service to the location service.
716 * @param[in] name server name
717 * @param[out] id assigned service id.
718 * @return Error code.
719 */
720int hound_server_register(const char *name, service_id_t *id)
721{
722 if (!name || !id)
723 return EINVAL;
724
725 int ret = loc_server_register(name);
726 if (ret != EOK)
727 return ret;
728
729 return loc_service_register(HOUND_SERVICE, id);
730}
731
732/**
733 * Unregister server from the location service.
734 * @param id previously assigned service id.
735 */
736void hound_server_unregister(service_id_t id)
737{
738 loc_service_unregister(id);
739}
740
741/**
742 * Set callback on device category change event.
743 * @param cb Callback function.
744 * @return Error code.
745 */
746int hound_server_set_device_change_callback(dev_change_callback_t cb)
747{
748 return loc_register_cat_change_cb(cb);
749}
750
751/**
752 * Walk through all device in the audio-pcm category.
753 * @param callback Function to call on every device.
754 * @return Error code.
755 */
756int hound_server_devices_iterate(device_callback_t callback)
757{
758 if (!callback)
759 return EINVAL;
760 static bool resolved = false;
761 static category_id_t cat_id = 0;
762
763 if (!resolved) {
764 const int ret = loc_category_get_id("audio-pcm", &cat_id,
765 IPC_FLAG_BLOCKING);
766 if (ret != EOK)
767 return ret;
768 resolved = true;
769 }
770
771 service_id_t *svcs = NULL;
772 size_t count = 0;
773 const int ret = loc_category_get_svcs(cat_id, &svcs, &count);
774 if (ret != EOK)
775 return ret;
776
777 for (unsigned i = 0; i < count; ++i) {
778 char *name = NULL;
779 loc_service_get_name(svcs[i], &name);
780 callback(svcs[i], name);
781 free(name);
782 }
783 free(svcs);
784 return EOK;
785}
786/**
787 * @}
788 */
Note: See TracBrowser for help on using the repository browser.