source: roaraudio/roard/clients.c @ 5739:2a1671d592b9

Last change on this file since 5739:2a1671d592b9 was 5739:2a1671d592b9, checked in by phi, 11 years ago

commit 0: make protocol emulations for esd, gopher and rplay more independed so they can be moved into plugins easily (See: #311)

File size: 33.6 KB
Line 
1//clients.c:
2
3/*
4 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2008-2012
5 *
6 *  This file is part of roard a part of RoarAudio,
7 *  a cross-platform sound system for both, home and professional use.
8 *  See README for details.
9 *
10 *  This file is free software; you can redistribute it and/or modify
11 *  it under the terms of the GNU General Public License version 3
12 *  as published by the Free Software Foundation.
13 *
14 *  RoarAudio is distributed in the hope that it will be useful,
15 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
16 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 *  GNU General Public License for more details.
18 *
19 *  You should have received a copy of the GNU General Public License
20 *  along with this software; see the file COPYING.  If not, write to
21 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
22 *  Boston, MA 02110-1301, USA.
23 *
24 */
25
26/* ckport options:
27 * ckport: ignore-symbol: roar_event_to_blob of target libroar0
28 */
29
30#include "roard.h"
31
32// declared 'extern'
33struct roar_client_server * g_clients[ROAR_CLIENTS_MAX];
34
35extern struct roar_dl_proto __proto_common_rplay;
36extern struct roar_dl_proto __proto_common_gopher;
37extern struct roar_dl_proto __proto_common_esd;
38
39static struct roard_proto_handle __protos[MAX_PROTOS] = {
40 {.proto = ROAR_PROTO_ROARAUDIO, .lhandle = NULL, .type = ROARD_PROTO_TYPE_BUILDIN,
41 .impl = {.buildin = 0}},
42#if !defined(ROAR_WITHOUT_DCOMP_EMUL_ESD) && defined(ROAR_HAVE_H_ESD)
43 {.proto = ROAR_PROTO_ESOUND, .lhandle = NULL, .type = ROARD_PROTO_TYPE_COMMON,
44 .impl = {.common = &__proto_common_esd}},
45#endif
46#ifndef ROAR_WITHOUT_DCOMP_EMUL_RPLAY
47 {.proto = ROAR_PROTO_RPLAY, .lhandle = NULL, .type = ROARD_PROTO_TYPE_COMMON,
48 .impl = {.common = &__proto_common_rplay}},
49#endif
50#ifndef ROAR_WITHOUT_DCOMP_EMUL_GOPHER
51 {.proto = ROAR_PROTO_GOPHER, .lhandle = NULL, .type = ROARD_PROTO_TYPE_COMMON,
52 .impl = {.common = &__proto_common_gopher}},
53#endif
54 {.proto = -1}
55};
56
57#define _CHECK_CID_RET(id,ret) if ( (id) < 0 || (id) > ROAR_CLIENTS_MAX || g_clients[(id)] == NULL ) return (ret)
58#define _CHECK_CID(id)         _CHECK_CID_RET((id), -1)
59
60int clients_init (void) {
61 int i;
62
63 for (i = 0; i < ROAR_CLIENTS_MAX; i++)
64  g_clients[i] = NULL;
65
66 for (i = 0; __protos[i].proto != -1; i++);
67
68 for (; i < MAX_PROTOS; i++)
69  __protos[i].proto = -1;
70
71 return 0;
72}
73
74int clients_free (void) {
75 int i;
76
77 for (i = 0; i < ROAR_CLIENTS_MAX; i++)
78  if ( g_clients[i] != NULL )
79   clients_delete(i);
80
81 return 0;
82}
83
84int clients_new (void) {
85 int i;
86 int s;
87 struct roar_client_server * ns;
88 struct roar_client * n;
89
90 for (i = 0; i < ROAR_CLIENTS_MAX; i++) {
91  if ( g_clients[i] == NULL ) {
92   ns = roar_mm_malloc(sizeof(struct roar_client_server));
93   n = ROAR_CLIENT(ns);
94
95   memset(ns, 0, sizeof(struct roar_client_server));
96
97   if ( n != NULL ) {
98    n->pid    = -1;
99    n->uid    = -1;
100    n->gid    = -1;
101    n->fh     = -1;
102
103    *n->name = 0;
104
105    n->proto     = ROAR_PROTO_ROARAUDIO;
106    n->byteorder = ROAR_BYTEORDER_NETWORK;
107
108    n->acl   = NULL;
109
110    n->execed = -1;
111    for (s = 0; s < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; s++)
112     n->streams[s] = -1;
113
114    if ( roar_nnode_new(&(n->nnode), ROAR_SOCKET_TYPE_UNKNOWN) == -1 ) {
115     roar_mm_free(n);
116     return -1;
117    }
118
119    ns->blockc   = 0;
120    ns->waits    = NULL;
121    ns->acclev   = ACCLEV_NONE;
122
123    g_clients[i] = ns;
124
125    counters_inc(clients, 1);
126    roar_notify_core_emit_snoargs(ROAR_OE_BASICS_NEW, -1, i, ROAR_OT_CLIENT);
127    ROAR_DBG("clients_new(void) = %i", i);
128    return i;
129   } else {
130    ROAR_ERR("clients_new(void): Can not alloc memory for new client: %s", strerror(errno));
131    ROAR_ERR("clients_new(void) = -1");
132    return -1;
133   }
134  }
135 }
136
137 return -1;
138}
139
140int clients_new_from_fh(int fh, int proto, int byteorder, int update_nnode) {
141 return clients_new_from_fh2(fh, proto, byteorder, update_nnode, NULL, NULL, 0);
142}
143
144int clients_new_from_fh2(int fh, int proto, int byteorder,
145                         int update_nnode,
146                         struct roard_listen * lsock, struct sockaddr * sockaddr, socklen_t addrlen) {
147 const struct roard_proto_handle * protohandle;
148 struct roar_dl_librarypara * pluginpara;
149 struct roar_vio_calls    vio;
150 struct roar_client_server * cs;
151 int supported = 0;
152 int client;
153
154 if ( fh == -1 ) {
155  roar_err_set(ROAR_ERROR_BADFH);
156  return -1;
157 }
158
159 if ( lsock == NULL ) {
160  switch (proto) {
161   case ROAR_PROTO_RSOUND:
162   case ROAR_PROTO_SIMPLE:
163     roar_err_set(ROAR_ERROR_FAULT);
164     return -1;
165    break;
166  }
167 }
168
169 if ( byteorder != ROAR_BYTEORDER_NETWORK )
170  return -1;
171
172
173 if ( proto == ROAR_PROTO_RSOUND ) {
174#ifndef ROAR_WITHOUT_DCOMP_EMUL_RSOUND
175  client = emul_rsound_on_connect(fh, lsock);
176  switch (client) {
177   case -1: return -1; break;
178   case -2: return  0; break;
179   default: // TODO: write error handling
180     fh = ROAR_CLIENT(cs)->fh;
181    break;
182  }
183#else
184  roar_err_set(ROAR_ERROR_PROTONOSUP);
185  return -1;
186#endif
187 } else {
188  client = clients_new();
189
190  if ( client == -1 ) {
191   ROAR_DBG("clients_new_from_fh2(*) = -1 // can not create new client");
192   return -1;
193  }
194
195  if ( clients_set_fh(client, fh) == -1 ) {
196   ROAR_ERR("clients_new_from_fh2(*): Can not set client's fh");
197
198   clients_delete(client);
199
200   ROAR_DBG("clients_new_from_fh2(*) = -1");
201   return -1;
202  }
203 }
204
205 if ( clients_get_server(client, &cs) == -1 ) {
206  clients_delete(client);
207  return -1;
208 }
209
210 if ( update_nnode ) {
211  if ( roar_nnode_free(&(ROAR_CLIENT(cs)->nnode)) != -1 ) {
212   if ( sockaddr != NULL && addrlen != 0 ) {
213    roar_nnode_new_from_sockaddr(&(ROAR_CLIENT(cs)->nnode), sockaddr, addrlen);
214   } else {
215    roar_nnode_new_from_fh(&(ROAR_CLIENT(cs)->nnode), fh, 1);
216   }
217  }
218 }
219
220 if ( clients_set_proto(client, proto) == -1 ) {
221  ROAR_WARN("clients_new_from_fh2(*): Setting proto(0x%.4x) of client %i failed.", proto, client);
222  clients_delete(client);
223  return -1;
224 }
225
226 switch (proto) {
227  case ROAR_PROTO_ROARAUDIO:
228    // nothing needed to be done here
229   break;
230#ifndef ROAR_WITHOUT_DCOMP_EMUL_SIMPLE
231  case ROAR_PROTO_SIMPLE:
232    if ( emul_simple_on_connect(client, lsock) == -1 )
233     return -1;
234   break;
235#endif
236#ifndef ROAR_WITHOUT_DCOMP_EMUL_RSOUND
237  case ROAR_PROTO_RSOUND: // nothing to do here.
238   break;
239#endif
240  default:
241    // OS independiend code to close the socket:
242    if ( roar_vio_open_fh_socket(&vio, fh) == -1 )
243     return -1;
244
245    protohandle = clients_get_protohandle(proto);
246    supported = 0;
247    if ( protohandle != NULL ) {
248     if ( protohandle->lhandle != NULL )
249      roar_dl_context_restore(protohandle->lhandle);
250     switch (protohandle->type) {
251      case ROARD_PROTO_TYPE_BUILDIN:
252        //this should not end up here.
253        ROAR_WARN("net_get_new_client(lsock=%p): proto(%i) marked as buildin but isn't. BAD.", lsock, proto);
254        supported = 0;
255       break;
256      case ROARD_PROTO_TYPE_COMMON:
257        supported = 1;
258        if ( protohandle->impl.common->set_proto != NULL ) {
259         pluginpara = roar_dl_getpara(protohandle->lhandle); // will return NULL in case protohandle->lhandle is NULL.
260         if ( protohandle->impl.common->set_proto(client, &vio, &(cs->outbuf), &(cs->protoinst), protohandle->para, protohandle->paralen, pluginpara) == -1 ) {
261          supported = 0;
262         }
263         if ( pluginpara != NULL )
264          roar_dl_para_unref(pluginpara);
265        }
266       break;
267     }
268     if ( protohandle->lhandle != NULL )
269      roar_dl_context_store(protohandle->lhandle);
270    }
271
272    if ( !supported ) {
273     clients_delete(client);
274     //roar_vio_close(&vio);
275     return -1;
276    }
277   break;
278 }
279
280
281 return 0;
282}
283
284int clients_delete (int id) {
285 struct roar_client_server * cs;
286 const struct roard_proto_handle * proto;
287 struct roar_dl_librarypara * pluginpara;
288 struct roar_vio_calls vio;
289 int i;
290 int close_client_fh = 1;
291
292 ROAR_DBG("clients_delete(id=%i) = ?", id);
293
294 _CHECK_CID(id);
295
296 cs = g_clients[id];
297
298 proto = clients_get_protohandle(ROAR_CLIENT(cs)->proto);
299 if ( proto != NULL ) {
300  if ( proto->lhandle != NULL )
301   roar_dl_context_restore(proto->lhandle);
302  switch (proto->type) {
303   case ROARD_PROTO_TYPE_BUILDIN:
304     /* noop */
305    break;
306   case ROARD_PROTO_TYPE_COMMON:
307     if ( proto->impl.common->unset_proto != NULL ) {
308      roar_vio_open_fh_socket(&vio, clients_get_fh(id));
309      pluginpara = roar_dl_getpara(proto->lhandle); // will return NULL in case protohandle->lhandle is NULL.
310      proto->impl.common->unset_proto(id, &vio, &(cs->outbuf), &(cs->protoinst), proto->para, proto->paralen, pluginpara);
311      if ( pluginpara != NULL )
312       roar_dl_para_unref(pluginpara);
313     }
314    break;
315  }
316  if ( proto->lhandle != NULL )
317   roar_dl_context_store(proto->lhandle);
318 }
319
320 if ( cs->waits != NULL ) {
321  for (i = 0; cs->waits[i] != NULL; i++)
322   roar_notify_core_unsubscribe(NULL, cs->waits[i]);
323
324  roar_mm_free(cs->waits);
325  cs->waits = NULL;
326 }
327
328 roar_notify_core_emit_snoargs(ROAR_OE_BASICS_DELETE, -1, id, ROAR_OT_CLIENT);
329
330 counters_inc(clients, -1);
331
332 if (ROAR_CLIENT(cs)->execed != -1) {
333//  return streams_delete(g_clients[id]->execed);
334  ROAR_CLIENT(cs)->execed = -1;
335  close_client_fh = 0;
336 }
337
338 for (i = 0; i < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; i++) {
339  streams_delete(ROAR_CLIENT(cs)->streams[i]);
340 }
341
342 if ( ROAR_CLIENT(cs)->fh != -1 && close_client_fh )
343  close(ROAR_CLIENT(cs)->fh);
344
345 roar_nnode_free(&(ROAR_CLIENT(cs)->nnode));
346
347 if ( cs->inbuf != NULL )
348  roar_buffer_free(cs->inbuf);
349
350 if ( cs->outbuf != NULL )
351  roar_buffer_free(cs->outbuf);
352
353 if ( cs->protoinst != NULL )
354  roar_mm_free(cs->protoinst);
355
356 roar_mm_free(cs);
357 g_clients[id] = NULL;
358
359 ROAR_DBG("clients_delete(id=%i) = 0", id);
360 return 0;
361}
362
363int clients_close      (int id, int nocheck_exec) {
364 struct roar_client * c;
365
366 ROAR_DBG("clients_close(id=%i) = ?", id);
367
368 _CHECK_CID(id);
369
370 c = ROAR_CLIENT(g_clients[id]);
371
372 if ( c->fh == -1 ) {
373  ROAR_DBG("clients_delete(id=%i) = 0", id);
374  return 0;
375 }
376
377 if (nocheck_exec || c->execed != -1) {
378  close(c->fh);
379  c->fh = -1;
380 }
381
382 ROAR_DBG("clients_delete(id=%i) = 0", id);
383 return 0;
384}
385
386int clients_get       (int id, struct roar_client ** client) {
387 _CHECK_CID(id);
388
389 *client = ROAR_CLIENT(g_clients[id]);
390
391 if ( *client == NULL )
392  return -1;
393
394 return 0;
395}
396
397int clients_get_server (int id, struct roar_client_server ** client) {
398 _CHECK_CID(id);
399
400 *client = g_clients[id];
401
402 if ( *client == NULL )
403  return -1;
404
405 return 0;
406}
407
408int clients_set_fh    (int id, int    fh) {
409 struct roar_client * c;
410#ifdef SO_PEERCRED
411 struct ucred cred;
412 socklen_t cred_len = sizeof(cred);
413#endif
414
415 _CHECK_CID(id);
416
417 if ( (c = ROAR_CLIENT(g_clients[id])) == NULL )
418  return -1;
419
420 c->fh = fh;
421
422 if ( fh == -1 )
423  return 0;
424
425#ifdef SO_PEERCRED
426 if (getsockopt(fh, SOL_SOCKET, SO_PEERCRED, &cred, &cred_len) != -1) {
427  if ( cred.pid != 0 ) {
428   c->pid = cred.pid;
429   c->uid = cred.uid;
430   c->gid = cred.gid;
431  }
432 } else {
433  ROAR_DBG("req_on_identify(): Can't get creds via SO_PEERCRED: %s", strerror(errno));
434 }
435#elif defined(ROAR_HAVE_GETPEEREID)
436 if (getpeereid(fh, &(c->uid), &(c->gid)) == -1) {
437  ROAR_DBG("req_on_identify(): Can't get creds via getpeereid(): %s", strerror(errno));
438 }
439#endif
440
441 return 0;
442}
443
444int clients_get_fh    (int id) {
445 _CHECK_CID(id);
446
447 return ROAR_CLIENT(g_clients[id])->fh;
448}
449
450int clients_set_pid   (int id, int    pid) {
451 _CHECK_CID(id);
452
453 ROAR_CLIENT(g_clients[id])->pid = pid;
454
455 return 0;
456}
457
458int clients_set_uid   (int id, int    uid) {
459 _CHECK_CID(id);
460
461 ROAR_CLIENT(g_clients[id])->uid = uid;
462
463 return 0;
464}
465
466int clients_set_gid   (int id, int    gid) {
467 _CHECK_CID(id);
468
469 ROAR_CLIENT(g_clients[id])->gid = gid;
470
471 return 0;
472}
473
474int clients_set_name   (int id, const char * name) {
475 _CHECK_CID(id);
476
477 strncpy(ROAR_CLIENT(g_clients[id])->name, name, ROAR_BUFFER_NAME-1);
478 ROAR_CLIENT(g_clients[id])->name[ROAR_BUFFER_NAME-1] = 0;
479
480 return 0;
481}
482
483int clients_set_proto (int id, int    proto) {
484 int byteorder = ROAR_BYTEORDER_UNKNOWN;
485
486 _CHECK_CID(id);
487
488 switch (proto) {
489  case ROAR_PROTO_ROARAUDIO:
490  case ROAR_PROTO_ESOUND:
491  case ROAR_PROTO_RPLAY:
492  case ROAR_PROTO_SIMPLE:
493    byteorder = ROAR_BYTEORDER_NETWORK;
494   break;
495 }
496
497 ROAR_CLIENT(g_clients[id])->proto     = proto;
498 ROAR_CLIENT(g_clients[id])->byteorder = byteorder;
499
500 return 0;
501}
502
503int clients_block      (int id, int unblock) {
504 _CHECK_CID(id);
505
506 if ( unblock ) {
507  g_clients[id]->blockc--;
508 } else {
509  g_clients[id]->blockc++;
510 }
511
512 return 0;
513}
514
515
516#define MAX_STREAMLESS 8
517
518int clients_check_all (void) {
519#ifdef ROAR_HAVE_SELECT
520 struct roar_client * c;
521 struct timeval tv;
522 fd_set r, w, e;
523 int i, j;
524 int ret;
525 int fh;
526 int max_fh = -1;
527 int have = 0;
528 struct {
529  int id;
530  int fh;
531 } streamless[MAX_STREAMLESS];
532 int have_streamless = 0;
533 int have_stream;
534
535 ROAR_DBG("clients_check_all(void) = ?");
536
537 FD_ZERO(&r);
538 FD_ZERO(&w);
539 FD_ZERO(&e);
540
541 tv.tv_sec  = 0;
542 tv.tv_usec = 1;
543
544 for (i = 0; i < ROAR_CLIENTS_MAX; i++) {
545  if ( (c = ROAR_CLIENT(g_clients[i])) == NULL )
546   continue;
547
548  ROAR_DBG("clients_check_all(void): i(client)=%i", i);
549
550  if ( (fh = c->fh) != -1 ) {
551   have++;
552
553   ROAR_DBG("clients_check_all(*): fh=%i", fh);
554
555   FD_SET(fh, &r);
556   FD_SET(fh, &e);
557
558   if ( g_clients[i]->outbuf != NULL ) {
559    FD_SET(fh, &w);
560   }
561
562   if ( fh > max_fh )
563    max_fh = fh;
564  }
565
566  have_stream = 0;
567
568  for (j = 0; j < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; j++) {
569   ROAR_DBG("clients_check_all(void): i(client)=%i, j(stream)=%i", i, j);
570   if ( (fh = streams_get_fh(c->streams[j])) != -1 ) {
571    ROAR_DBG("clients_check_all(*): g_clients[i=%i]->streams[j=%i] = %i, fh = %i", i, j, c->streams[j], fh);
572    if ( fh > -1 ) {
573     FD_SET(fh, &r);
574
575     if ( fh > max_fh )
576      max_fh = fh;
577    } else if ( fh == -2 ) {
578     streams_check(c->streams[j]);
579    }
580
581    have_stream = 1;
582   }
583   //printf("D: client=%i, stream=%i, fh=%i\n", i, j, fh);
584  }
585
586  if ( !have_stream && have_streamless < MAX_STREAMLESS ) {
587   streamless[have_streamless  ].id = i;
588   if ( (streamless[have_streamless++].fh = c->fh) == -1 )
589    have_streamless--;
590  }
591 }
592
593 ROAR_DBG("clients_check_all(void): max_fh=%i", max_fh);
594
595 if ( max_fh == -1 )
596  return 0;
597
598 if ( (ret = select(max_fh + 1, &r, &w, &e, &tv)) < 1 ) {
599  return ret < 0 ? ret : have;
600 }
601
602 for (i = 0; i < ROAR_CLIENTS_MAX; i++) {
603  if ( (c = ROAR_CLIENT(g_clients[i])) == NULL )
604   continue;
605
606  if ( (fh = c->fh) != -1 ) {
607   if ( FD_ISSET(fh, &r) ) {
608    if ( c->execed == -1 ) {
609     clients_check(i);
610     if ( g_clients[i] != NULL && ROAR_CLIENT(g_clients[i])->execed != -1 ) {
611      FD_CLR(fh, &r);
612     }
613/*
614    } else {
615     streams_check(g_clients[i]->execed);
616*/
617    }
618   }
619   if ( FD_ISSET(fh, &w) ) {
620    clients_flush(i);
621   }
622
623   if ( FD_ISSET(fh, &e) ) {
624    clients_delete(i);
625    continue;
626   }
627  }
628
629  if ( (c = ROAR_CLIENT(g_clients[i])) == NULL )
630   continue;
631
632  for (j = 0; j < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; j++) {
633   ROAR_DBG("clients_check_all(*): D: client=%i, stream=%i, g_clients[i=%i] = %p", i, j, i, g_clients[i]);
634   if ( g_clients[i] == NULL ) // streams_check() bellow can delete our client (why?)
635    break;
636
637   //ROAR_WARN("clients_check_all(*): client=%i: client exists", i);
638   ROAR_DBG("clients_check_all(*): client=%i, stream=%i: id=%i", i, j, c->streams[j]);
639
640   if ( (fh = streams_get_fh(c->streams[j])) != -1 ) {
641    ROAR_DBG("clients_check_all(*): client=%i, stream=%i: fh=%i", i, j, fh);
642    if ( fh > -1 && FD_ISSET(fh, &r) ) {
643     streams_check(c->streams[j]);
644    }
645   }
646  }
647 }
648
649 if ( have_streamless ) {
650   FD_ZERO(&r);
651   FD_ZERO(&w);
652
653   tv.tv_sec  = 0;
654   tv.tv_usec = 1;
655
656   max_fh = -1;
657
658   for (i = 0; i < have_streamless; i++) {
659    if ( g_clients[j = streamless[i].id] == NULL )
660     continue;
661
662    if ( ROAR_CLIENT(g_clients[j])->execed != -1 )
663     continue;
664
665    fh = streamless[i].fh;
666
667    ROAR_DBG("clients_check_all(void): fh=%i", fh);
668    FD_SET(fh, &r);
669
670    if ( g_clients[j]->outbuf != NULL ) {
671     FD_SET(fh, &w);
672    }
673
674    if ( fh > max_fh )
675     max_fh = fh;
676   }
677
678   if ( (ret = select(max_fh + 1, &r, &w, NULL, &tv)) < 0 ) {
679    return ret;
680   }
681
682   for (i = 0; i < have_streamless; i++) {
683    if ( FD_ISSET(streamless[i].fh, &r) ) {
684     clients_check(streamless[i].id);
685    }
686    if ( FD_ISSET(streamless[i].fh, &w) ) {
687     clients_flush(streamless[i].id);
688    }
689   }
690 }
691
692 ROAR_DBG("clients_check_all(void) = %i // have value", have);
693 return have;
694#else
695 ROAR_DBG("clients_check_all(void) = -1");
696 return -1;
697#endif
698}
699
700int clients_check     (int id) {
701 struct roar_client_server * cs;
702 struct roar_message    m;
703 struct roar_connection con;
704 struct roar_vio_calls  vio;
705 struct roar_error_state errstate;
706 const struct roard_proto_handle * proto;
707 struct roar_dl_librarypara * pluginpara;
708 int command_error;
709 char * data = NULL;
710 int oldcmd;
711 int r;
712 int rv = 0;
713 uint32_t flags[2] = {COMMAND_FLAG_NONE, COMMAND_FLAG_NONE};
714 uint32_t event;
715
716 ROAR_DBG("clients_check(id=%i) = ?", id);
717
718 _CHECK_CID(id);
719
720 cs = g_clients[id];
721
722 if ( ROAR_CLIENT(cs)->fh == -1 )
723  return -1;
724
725 if ( roar_connect_fh(&con, ROAR_CLIENT(cs)->fh) == -1 ) {
726  ROAR_WARN("clients_check(id=%i): Can not create con object for client: %s", id, roar_errorstring);
727 }
728
729 ROAR_DBG("clients_check(id=%i): c->proto=%i", id, ROAR_CLIENT(cs)->proto);
730
731 switch (ROAR_CLIENT(cs)->proto) {
732  case ROAR_PROTO_ROARAUDIO:
733    r = roar_recv_message(&con, &m, &data);
734
735    if ( r == -1 ) { // should we drop the client?
736     clients_delete(id);
737     return -1;
738    }
739
740    event = ROAR_NOTIFY_CMD2EVENT(m.cmd);
741
742    oldcmd = m.cmd;
743
744    roar_err_store(&errstate);
745    roar_err_clear_all();
746    r = command_exec(id, &m, &data, flags);
747    roar_err_update();
748    command_error = roar_error;
749    roar_err_restore(&errstate);
750
751    if ( r == -1 ) {
752     // use command_error to create an error frame here as soon as this is supported.
753     m.cmd     = ROAR_CMD_ERROR;
754     m.datalen = 0;
755     ROAR_DBG("clients_check(*): Exec of command faild!");
756    } else {
757     if ( m.cmd == oldcmd ) {
758      m.cmd     = ROAR_CMD_OK;
759      m.datalen = 0;
760     } else if ( m.cmd == ROAR_CMD_OK_STOP ) {
761      m.cmd     = ROAR_CMD_OK;
762      rv        = 1;
763     }
764    }
765    ROAR_DBG("clients_check(*): data=%p", data);
766
767    if ( flags[1] & COMMAND_FLAG_OUT_NOSEND ) {
768     roar_notify_core_emit_simple(event, id, -1, -1, -1, -1, NULL, 0);
769    } else {
770     roar_notify_core_emit_simple(event, id, -1, -1, m.cmd, -1, NULL, 0);
771     if ( roar_send_message(&con, &m, flags[1] & COMMAND_FLAG_OUT_LONGDATA ? data : NULL) == -1 ) {
772      ROAR_WARN("clients_check(id=%i): Can not send answer to client: %s", id, roar_errorstring);
773     }
774    }
775
776    if ( flags[1] & COMMAND_FLAG_OUT_CLOSECON )
777     clients_close(id, 1);
778
779    if ( flags[1] & COMMAND_FLAG_OUT_DELETE )
780     clients_delete(id);
781
782   break;
783#ifndef ROAR_WITHOUT_DCOMP_EMUL_RSOUND
784  case ROAR_PROTO_RSOUND:
785    rv = emul_rsound_check_client(id, NULL);
786    if ( rv == 0 ) { // loop as long as we don't get an error.
787     while (rv == 0)
788      rv = emul_rsound_check_client(id, NULL);
789     rv = 0; // restore
790    } else { // in case of error delete the client
791     if (
792#ifdef EAGAIN
793          errno != EAGAIN      &&
794#endif
795#ifdef EWOULDBLOCK
796          errno != EWOULDBLOCK &&
797#endif
798#ifdef EINTR
799          errno != EINTR       &&
800#endif
801          1 ) {
802      rv = clients_delete(id);
803     } else {
804      rv = 0;
805     }
806    }
807   break;
808#endif
809  default:
810    rv = -1;
811    proto = clients_get_protohandle(ROAR_CLIENT(cs)->proto);
812    if ( proto != NULL ) {
813     roar_vio_open_fh_socket(&vio, clients_get_fh(id));
814     if ( proto->lhandle != NULL )
815      roar_dl_context_restore(proto->lhandle);
816
817     switch (proto->type) {
818      case ROARD_PROTO_TYPE_BUILDIN:
819        ROAR_WARN("clients_check(id=%i): proto(%i) marked as buildin but isn't. BAD.", id, proto->proto);
820       break;
821      case ROARD_PROTO_TYPE_COMMON:
822        if ( proto->impl.common->handle != NULL ) {
823         pluginpara = roar_dl_getpara(proto->lhandle); // will return NULL in case protohandle->lhandle is NULL.
824         rv = proto->impl.common->handle(id, &vio, &(cs->outbuf), &(cs->protoinst), proto->para, proto->paralen, pluginpara);
825         if ( pluginpara != NULL )
826          roar_dl_para_unref(pluginpara);
827        }
828        if ( rv == -1 )
829         rv = clients_delete(id);
830       break;
831     }
832
833     if ( proto->lhandle != NULL )
834      roar_dl_context_store(proto->lhandle);
835    }
836 }
837
838 if ( data != NULL )
839  roar_mm_free(data);
840
841 ROAR_DBG("clients_check(id=%i) = %i", id, rv);
842 return rv;
843}
844
845int clients_flush      (int id) {
846 struct roar_vio_calls             vio;
847 struct roar_client_server       * cs;
848 struct roar_client              * c;
849 const struct roard_proto_handle * p;
850 struct roar_dl_librarypara * pluginpara;
851 size_t len;
852 ssize_t ret;
853 void * buf;
854 int rv;
855
856 _CHECK_CID(id);
857
858 c = ROAR_CLIENT(cs = g_clients[id]);
859
860 p = clients_get_protohandle(c->proto);
861
862 if ( p == NULL )
863  return -1;
864
865 roar_vio_open_fh_socket(&vio, clients_get_fh(id));
866
867 switch (p->type) {
868  case ROARD_PROTO_TYPE_BUILDIN:
869    /* noop */
870   break;
871  case ROARD_PROTO_TYPE_COMMON:
872    if ( p->impl.common->flush != NULL ) {
873     if ( p->lhandle != NULL )
874      roar_dl_context_restore(p->lhandle);
875     pluginpara = roar_dl_getpara(p->lhandle); // will return NULL in case protohandle->lhandle is NULL.
876     rv = p->impl.common->flush(id, &vio, &(cs->outbuf), &(cs->protoinst), p->para, p->paralen, pluginpara);
877     if ( p->lhandle != NULL )
878      roar_dl_context_store(p->lhandle);
879     if ( pluginpara != NULL )
880      roar_dl_para_unref(pluginpara);
881     if ( rv == -1 )
882      rv = clients_delete(id);
883     return rv;
884    }
885   break;
886 }
887
888 if ( roar_buffer_get_len(cs->outbuf, &len) == -1 )
889  return -1;
890
891 if ( roar_buffer_get_data(cs->outbuf, &buf) == -1 )
892  return -1;
893
894 ret = roar_vio_write(&vio, buf, len);
895
896 if ( ret < 1 ) {
897  clients_delete(id);
898  return -1;
899 }
900
901 if ( ret == (ssize_t)len ) {
902  if ( roar_buffer_next(&(cs->outbuf)) == -1 ) {
903   clients_delete(id);
904   return -1;
905  }
906 } else {
907  if ( roar_buffer_set_offset(cs->outbuf, ret) == -1 ) {
908   clients_delete(id);
909   return -1;
910  }
911 }
912
913 if ( cs->outbuf == NULL ) {
914  switch (p->type) {
915   case ROARD_PROTO_TYPE_BUILDIN:
916     /* noop */
917    break;
918   case ROARD_PROTO_TYPE_COMMON:
919     if ( p->impl.common->flushed != NULL ) {
920      if ( p->lhandle != NULL )
921       roar_dl_context_restore(p->lhandle);
922      pluginpara = roar_dl_getpara(p->lhandle); // will return NULL in case protohandle->lhandle is NULL.
923      rv = p->impl.common->flushed(id, &vio, &(cs->outbuf), &(cs->protoinst), p->para, p->paralen, pluginpara);
924      if ( p->lhandle != NULL )
925       roar_dl_context_store(p->lhandle);
926      if ( pluginpara != NULL )
927       roar_dl_para_unref(pluginpara);
928      if ( rv == -1 )
929       rv = clients_delete(id);
930      return rv;
931     }
932    break;
933  }
934 }
935
936 return 0;
937}
938
939int clients_send_mon  (struct roar_audio_info * sa) {
940 int i;
941// int fh;
942 int j;
943 int keep_going;
944
945 for (i = 0; i < ROAR_CLIENTS_MAX; i++) {
946  if ( g_clients[i] == NULL )
947   continue;
948
949  keep_going = 1;
950
951/*
952  if ( (fh = g_clients[i]->fh) == -1 )
953   continue;
954*/
955
956  ROAR_DBG("clients_send_mon(*): client=%i, execed=%i", i, ROAR_CLIENT(g_clients[i])->execed);
957
958/*
959  if ( g_clients[i]->execed == -1 ) {
960   // TODO: add some code to send a message to the client insetd of the raw data.
961*/
962   for (j = 0; keep_going && j < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; j++) {
963    //if ( (fh = streams_get_fh(g_clients[i]->streams[j])) != -1 ) {
964    ROAR_DBG("clients_send_mon(*): client=%i, stream=%i -> ?", i, j);
965    if ( ROAR_CLIENT(g_clients[i])->streams[j] != -1 ) {
966     ROAR_DBG("clients_send_mon(*): client=%i, stream=%i -> %i", i, j, ROAR_CLIENT(g_clients[i])->streams[j]);
967     streams_send_mon(ROAR_CLIENT(g_clients[i])->streams[j]);
968
969     // the client may be deleted here, check if it still exists:
970     if ( g_clients[i] == NULL )
971      keep_going = 0;
972    }
973   }
974/*
975  } else {
976//   streams_check(g_clients[i]->execed);
977   streams_send_mon(g_clients[i]->execed);
978//   if ( streams_send_mon(g_clients[i]->execed) == -1 )
979//    clients_delete(i); // delete client in case we could not write
980  }
981*/
982 }
983
984 // TODO: FIXME: should this really be -1?
985 return -1;
986}
987
988int clients_send_filter(struct roar_audio_info * sa) {
989 struct roar_client * c;
990 int i;
991 int fh;
992
993 for (i = 0; i < ROAR_CLIENTS_MAX; i++) {
994  if ( (c = ROAR_CLIENT(g_clients[i])) == NULL )
995   continue;
996
997  if ( (fh = c->fh) == -1 )
998   continue;
999
1000  if ( c->execed == -1 ) {
1001   // TODO: add some code to send a message to the client insetd of the raw data.
1002  } else {
1003//   streams_check(g_clients[i]->execed);
1004   streams_send_filter(c->execed);
1005//   if ( streams_send_mon(g_clients[i]->execed) == -1 )
1006//    clients_delete(i); // delete client in case we could not write
1007  }
1008 }
1009
1010 return -1;
1011}
1012
1013int clients_add_output (int id, struct roar_buffer ** buf) {
1014 struct roar_client_server   * cs;
1015
1016 _CHECK_CID(id);
1017 cs = g_clients[id];
1018
1019 if ( cs->outbuf == NULL ) {
1020  cs->outbuf = *buf;
1021 } else {
1022  return roar_buffer_moveinto(cs->outbuf, buf);
1023 }
1024
1025 return 0;
1026}
1027
1028// proto support
1029const struct roard_proto_handle * clients_get_protohandle(const int proto) {
1030 size_t i;
1031
1032 if ( proto < 0 ) {
1033  roar_err_set(ROAR_ERROR_INVAL);
1034  return NULL;
1035 }
1036
1037 for (i = 0; i < (sizeof(__protos)/sizeof(*__protos)); i++)
1038  if ( __protos[i].proto == proto )
1039   return &(__protos[i]);
1040
1041 roar_err_set(ROAR_ERROR_NOENT);
1042 return NULL;
1043}
1044
1045int clients_register_proto_common(const struct roar_dl_proto * proto, struct roar_dl_lhandle * lhandle) {
1046 const size_t len = sizeof(__protos)/sizeof(*__protos);
1047 size_t i;
1048
1049 ROAR_DBG("clients_register_proto_common(proto=%p, lhandle=%p) = ?", proto, lhandle);
1050
1051 if ( proto == NULL )
1052  return -1;
1053
1054 for (i = 0; __protos[i].proto != -1; i++);
1055
1056 // i is now at pos of current EOS entry.
1057
1058 // test if we have space for one more entry:
1059 if ( (i+1) >= len )
1060  return -1;
1061
1062 __protos[i].impl.common = proto;
1063
1064 __protos[i].proto   = proto->proto;
1065 __protos[i].type    = ROARD_PROTO_TYPE_COMMON;
1066 __protos[i].lhandle = lhandle;
1067
1068 return 0;
1069}
1070
1071int clients_unregister_proto(int proto) {
1072 size_t i;
1073
1074 if ( proto < 0 ) {
1075  roar_err_set(ROAR_ERROR_RANGE);
1076  return -1;
1077 }
1078
1079 for (i = 0; i < (sizeof(__protos)/sizeof(*__protos)); i++) {
1080  if ( __protos[i].proto == proto ) {
1081   memset(&(__protos[i]), 0, sizeof(*__protos));
1082   __protos[i].proto = -1;
1083   return 0;
1084  }
1085 }
1086
1087 roar_err_set(ROAR_ERROR_NOENT);
1088 return -1;
1089}
1090
1091void print_protolist        (enum output_format format) {
1092 struct roard_proto_handle * p;
1093 char flags[5] = "    ";
1094 char subsys[7] = "      ";
1095 const char * description;
1096 size_t i;
1097
1098 switch (format) {
1099  case FORMAT_NATIVE:
1100    printf("  Protocol Flag Subsys - Description\n");
1101    printf("------------------------------------------------------\n");
1102    printf("  roar     bb   WM LRX - RoarAudio native protocol\n");
1103#ifndef ROAR_WITHOUT_DCOMP_EMUL_SIMPLE
1104    printf("  simple   bb   WM LRX - PulseAudio simple protocol\n");
1105#endif
1106#ifndef ROAR_WITHOUT_DCOMP_EMUL_RSOUND
1107    printf("  rsound   bb   W      - RSound emulation\n");
1108#endif
1109   break;
1110  case FORMAT_WIKI:
1111    printf("||=Protocol =||=Flag =||=Subsys =||=Description              =||\n");
1112    printf("||roar       ||bb     ||WM LRX   ||RoarAudio native protocol  ||\n");
1113#ifndef ROAR_WITHOUT_DCOMP_EMUL_SIMPLE
1114    printf("||simple     ||bb     ||WM LRX   ||PulseAudio simple protocol ||\n");
1115#endif
1116#ifndef ROAR_WITHOUT_DCOMP_EMUL_RSOUND
1117    printf("||rsound     ||bb     ||W        ||RSound emulation           ||\n");
1118#endif
1119   break;
1120  case FORMAT_CSV:
1121    printf("Protocol,Flag,Subsys,Description\n");
1122    printf("roar,bb,WM LRX,RoarAudio native protocol\n");
1123#ifndef ROAR_WITHOUT_DCOMP_EMUL_SIMPLE
1124    printf("simple,bb,WM LRX,PulseAudio simple protocol\n");
1125#endif
1126#ifndef ROAR_WITHOUT_DCOMP_EMUL_RSOUND
1127    printf("rsound,bb,W,RSound emulation\n");
1128#endif
1129   break;
1130  default:
1131    roar_err_set(ROAR_ERROR_NOTSUP);
1132    return;
1133 }
1134
1135 for (i = 0; i < (sizeof(__protos)/sizeof(*__protos)); i++) {
1136  p = &(__protos[i]);
1137  if ( p->proto == -1 )
1138   continue;
1139
1140  strncpy(flags, "    ", 4);
1141  strncpy(subsys, "      ", 6);
1142  description = "(none)";
1143
1144  if ( p->lhandle != NULL ) {
1145   flags[0] = 'P';
1146  } else {
1147   flags[0] = 'b';
1148  }
1149
1150  switch (p->type) {
1151   case ROARD_PROTO_TYPE_BUILDIN:
1152     continue;
1153    break;
1154   case ROARD_PROTO_TYPE_COMMON:
1155     flags[1] = 'C';
1156     description = p->impl.common->description;
1157    break;
1158  }
1159
1160  switch (format) {
1161   case FORMAT_NATIVE:
1162     printf("  %-8s %s %s - %s\n", roar_proto2str(p->proto), flags, subsys, description);
1163    break;
1164   case FORMAT_WIKI:
1165     printf("||%s ||%s ||%s ||%s ||\n", roar_proto2str(p->proto), flags, subsys, description);
1166    break;
1167   case FORMAT_CSV:
1168     printf("%s,%s,%s,%s\n", roar_proto2str(p->proto), flags, subsys, description);
1169    break;
1170  }
1171 }
1172}
1173
1174int client_stream_exec   (int client, int stream) {
1175 struct roar_client * c;
1176 int i;
1177 int fh;
1178
1179 _CHECK_CID(client);
1180
1181#if 0
1182 if ( g_clients[client] == NULL ) {
1183  ROAR_WARN("client_stream_exec(client=%i, stream=%i) = -1 // client does not exist", client, stream);
1184  return -1;
1185 }
1186#endif
1187
1188 c = ROAR_CLIENT(g_clients[client]);
1189
1190 for (i = 0; i < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; i++) {
1191  if ( c->streams[i] == stream ) {
1192   c->execed = stream;
1193   if ( streams_is_ready(stream) == 0 ) {
1194    streams_set_fh(stream, c->fh);
1195    streams_set_socktype(stream, ROAR_SOCKET_TYPE_GENSTR);
1196   } else {
1197    ROAR_DBG("client_stream_exec(client=%i, stream=%i): fh=?", client, stream);
1198    if ( (fh = c->fh) != -1 ) {
1199     close(fh);
1200     c->fh = -1;
1201    }
1202   }
1203   ROAR_DBG("client_stream_exec(client=%i, stream=%i) = 0", client, stream);
1204   return 0;
1205  }
1206 }
1207
1208 ROAR_WARN("client_stream_exec(client=%i, stream=%i) = -1 // client does not own stream", client, stream);
1209 return -1;
1210}
1211
1212int client_stream_set_fh (int client, int stream, int fh) {
1213 int i;
1214
1215 ROAR_DBG("client_stream_set_fh(client=%i, stream=%i, fh=%i) = ?", client, stream, fh);
1216
1217 _CHECK_CID(client);
1218
1219 for (i = 0; i < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; i++) {
1220  if ( ROAR_CLIENT(g_clients[client])->streams[i] == stream ) {
1221   ROAR_DBG("client_stream_set_fh(client=%i, stream=%i, fh=%i): stream found, index %i", client, stream, fh, i);
1222   return streams_set_fh(stream, fh);
1223  }
1224 }
1225
1226 ROAR_WARN("client_stream_set_fh(client=%i, stream=%i, fh=%i) = -1 // client does not own stream", client, stream, fh);
1227 return -1;
1228}
1229
1230int client_stream_add    (int client, int stream) {
1231 int i;
1232
1233 _CHECK_CID(client);
1234
1235 for (i = 0; i < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; i++) {
1236  if ( ROAR_CLIENT(g_clients[client])->streams[i] == -1 ) {
1237   ROAR_CLIENT(g_clients[client])->streams[i] = stream;
1238   streams_set_client(stream, client);
1239   return 0;
1240  }
1241 }
1242
1243 return -1;
1244}
1245
1246int client_stream_delete (int client, int stream) {
1247 int i;
1248
1249 _CHECK_CID(client);
1250
1251 for (i = 0; i < ROAR_CLIENTS_MAX_STREAMS_PER_CLIENT; i++) {
1252  if ( ROAR_CLIENT(g_clients[client])->streams[i] == stream ) {
1253   ROAR_CLIENT(g_clients[client])->streams[i] = -1;
1254
1255   if ( stream == ROAR_CLIENT(g_clients[client])->execed ) {
1256    ROAR_DBG("client_stream_delete(client=%i, stream=%i): stream is execed one, deleting client!", client, stream);
1257    clients_delete(client);
1258   }
1259
1260   ROAR_DBG("client_stream_delete(client=%i, stream=%i) = 0", client, stream);
1261   return 0;
1262  }
1263 }
1264
1265 ROAR_DBG("client_stream_delete(client=%i, stream=%i) = -1", client, stream);
1266 return -1;
1267}
1268
1269int client_stream_move   (int client, int stream) {
1270 int old_client = streams_get_client(stream);
1271
1272 ROAR_DBG("client_stream_move(client=%i, stream=%i): old_client = %i", client, stream, old_client);
1273
1274 if ( old_client != -1 )
1275  if ( client_stream_delete(old_client, stream) == -1 )
1276   return -1;
1277
1278 return client_stream_add(client, stream);
1279}
1280
1281
1282// notify thingys
1283int clients_wait    (int client, struct roar_event * events, size_t num) {
1284 struct roar_client_server * cs;
1285 size_t i, c;
1286
1287 ROAR_DBG("clients_wait(client=%i, events=%p, num=%llu) = ?", client, events, (long long unsigned int)num);
1288
1289 _CHECK_CID(client);
1290
1291 cs = g_clients[client];
1292
1293 if ( cs->waits != NULL )
1294  return -1;
1295
1296 cs->waits = roar_mm_malloc((num+1) * sizeof(struct roar_subscriber *));
1297
1298 if ( cs->waits == NULL )
1299  return -1;
1300
1301 if ( clients_block(client, 0) != 0 )
1302  return -1;
1303
1304 for (i = 0; i < num; i++) {
1305#if defined(DEBUG) && 0
1306  dbg_notify_cb(NULL, &(events[i]), cs);
1307#endif
1308  cs->waits[i] = roar_notify_core_subscribe(NULL, &(events[i]), clients_ncb_wait, cs);
1309  if ( cs->waits[i] == NULL ) {
1310   for (c = 0; c < i; c++)
1311    roar_notify_core_unsubscribe(NULL, cs->waits[c]);
1312   roar_mm_free(cs->waits);
1313   cs->waits = NULL;
1314   clients_block(client, 1);
1315   return -1;
1316  }
1317 }
1318
1319 cs->waits[num] = NULL;
1320
1321 ROAR_DBG("clients_wait(client=%i, events=%p, num=%llu) = 0", client, events, (long long unsigned int)num);
1322 return 0;
1323}
1324
1325void clients_ncb_wait(struct roar_notify_core * core, struct roar_event * event, void * userdata) {
1326 struct roar_client_server * cs = userdata;
1327 struct roar_message m;
1328 struct roar_connection con;
1329 uint16_t * u16 = (uint16_t *) m.data;
1330 size_t tmp;
1331 size_t i;
1332
1333 ROAR_DBG("clients_ncb_wait(core=%p, event=%p, userdata=%p) = ?", core, event, userdata);
1334
1335 for (i = 0; cs->waits[i] != NULL; i++)
1336  roar_notify_core_unsubscribe(NULL, cs->waits[i]);
1337
1338 roar_mm_free(cs->waits);
1339 cs->waits = NULL;
1340
1341 // protocol depended handling...
1342 memset(&m, 0, sizeof(m));
1343 m.cmd = ROAR_CMD_OK;
1344 u16[0] = ROAR_HOST2NET16(0); // Version
1345 u16[1] = ROAR_HOST2NET16(0); // flags
1346
1347 tmp = sizeof(m.data) - 4;
1348
1349 roar_event_to_blob(event, m.data + 4, &tmp);
1350
1351 m.datalen = tmp + 4;
1352
1353 roar_connect_fh(&con, ROAR_CLIENT(cs)->fh);
1354 roar_send_message(&con, &m, NULL);
1355 // ...end of protocol depended handling.
1356
1357// clients_block(, 1);
1358 // TODO: FIXME: bad hack...
1359 cs->blockc--;
1360}
1361
1362
1363// acclev:
1364static struct {
1365 const enum roard_client_acclev acclev;
1366 const char *                   name;
1367} _g_acclevs[] = {
1368 {ACCLEV_NONE,    "none"   },
1369 {ACCLEV_IDENTED, "idented"},
1370 {ACCLEV_CONCTL,  "conctl" },
1371 {ACCLEV_GUEST,   "guest"  },
1372 {ACCLEV_USER,    "user"   },
1373 {ACCLEV_PWRUSER, "pwruser"},
1374 {ACCLEV_ALL,     "all"    },
1375 {-1, NULL}
1376};
1377
1378enum roard_client_acclev clients_str2acclev(const char * acclev) {
1379 int i;
1380
1381 for (i = 0; _g_acclevs[i].name != NULL; i++)
1382  if ( !strcasecmp(_g_acclevs[i].name, acclev) )
1383   return _g_acclevs[i].acclev;
1384
1385 return ACCLEV_ERROR;
1386}
1387
1388const char * clients_acclev2str(const enum roard_client_acclev acclev) {
1389 int i;
1390
1391 for (i = 0; _g_acclevs[i].name != NULL; i++)
1392  if ( _g_acclevs[i].acclev == acclev )
1393   return _g_acclevs[i].name;
1394
1395 return NULL;
1396}
1397
1398//ll
Note: See TracBrowser for help on using the repository browser.