source: roaraudio/libroar/vio_socket.c @ 6080:c9c0c0e4653b

Last change on this file since 6080:c9c0c0e4653b was 6080:c9c0c0e4653b, checked in by phi, 9 years ago

Added listen={single|listener} mode to socket DSTRs

File size: 15.7 KB
RevLine 
[1331]1//vio_socket.c:
2
3/*
[6052]4 *      Copyright (C) Philipp 'ph3-der-loewe' Schafft - 2009-2015
[1331]5 *
6 *  This file is part of libroar 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 *  libroar 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
[3517]21 *  the Free Software Foundation, 51 Franklin Street, Fifth Floor,
22 *  Boston, MA 02110-1301, USA.
[1331]23 *
24 *  NOTE for everyone want's to change something and send patches:
25 *  read README and HACKING! There a addition information on
26 *  the license of this document you need to read before you send
27 *  any patches.
28 *
29 *  NOTE for uses of non-GPL (LGPL,...) software using libesd, libartsc
30 *  or libpulse*:
31 *  The libs libroaresd, libroararts and libroarpulse link this lib
32 *  and are therefore GPL. Because of this it may be illigal to use
33 *  them with any software that uses libesd, libartsc or libpulse*.
34 */
35
36#include "libroar.h"
37
[1464]38#ifdef ROAR_HAVE_BSDSOCKETS
39#define _CAN_OPERATE
40#endif
41
[3293]42int     roar_vio_open_def_socket          (struct roar_vio_calls * calls, struct roar_vio_defaults * def, char * opts) {
[1464]43#ifdef _CAN_OPERATE
[1333]44 int       fh  = -1;
45 socklen_t len =  0;
[3293]46 int       listening  = 0;
47 int       one_client = 0;
48 int       client;
[3998]49 int       connection_less = 0;
[1333]50
51 if ( calls == NULL || def == NULL )
52  return -1;
53
[3293]54 if ( opts != NULL ) {
[6080]55  if ( strstr(opts, "listen=listener") != NULL ) {
56   listening  = 1;
57   one_client = 0;
58  } else if ( strstr(opts, "listen=single") != NULL || strstr(opts, "listen") != NULL ) {
[3293]59   listening  = 1;
60   one_client = 1;
61  }
62 }
63
[1333]64 if ( def->type != ROAR_VIO_DEF_TYPE_SOCKET )
65  return -1;
66
67 switch (def->d.socket.domain) {
[1358]68#ifdef ROAR_HAVE_IPV4
[1333]69  case AF_INET:
70    len = sizeof(struct sockaddr_in);
71
[1335]72    if ( roar_vio_socket_init_inet4host_def(def) == -1 )
73     return -1;
74
[1333]75    switch (def->d.socket.type) {
76     case SOCK_STREAM:
[5248]77       fh = roar_socket_new(ROAR_SOCKET_TYPE_TCP);
[1333]78      break;
79     case SOCK_DGRAM:
[5248]80       fh = roar_socket_new(ROAR_SOCKET_TYPE_UDP);
[3998]81       connection_less = 1;
[1333]82      break;
83     default:
84       return -1;
85    }
86   break;
[1358]87#endif
[1333]88#ifdef ROAR_HAVE_UNIX
89  case AF_UNIX:
90    len = sizeof(struct sockaddr_un);
91
92    switch (def->d.socket.type) {
93     case SOCK_STREAM:
[5248]94       fh = roar_socket_new(ROAR_SOCKET_TYPE_UNIX);
[1333]95      break;
96     case SOCK_DGRAM:
[3998]97       connection_less = 1;
[1333]98       return -1;
99      break;
100     default:
101       return -1;
102    }
103   break;
104#endif
105#ifdef ROAR_HAVE_LIBDNET
106  case AF_DECnet:
107    len = sizeof(struct sockaddr_dn);
108
[1354]109    if ( roar_vio_socket_init_decnetnode_def(def) == -1 )
110     return -1;
111
112    switch (def->d.socket.type) {
113     case SOCK_STREAM:
[5248]114       fh = roar_socket_new(ROAR_SOCKET_TYPE_DECNET);
[1354]115      break;
116     default:
117       return -1;
118    }
[1333]119   break;
120#endif
121#ifdef ROAR_HAVE_IPV6
122  case AF_INET6:
123    len = sizeof(struct sockaddr_in6);
[1332]124
[1333]125    switch (def->d.socket.type) {
126     case SOCK_STREAM:
[5248]127       fh = roar_socket_new(ROAR_SOCKET_TYPE_TCP6);
[1333]128      break;
129     case SOCK_DGRAM:
[5248]130       fh = roar_socket_new(ROAR_SOCKET_TYPE_UDP6);
[3998]131       connection_less = 1;
[1333]132      break;
133     default:
134       return -1;
135    }
136   break;
137#endif
138#ifdef ROAR_HAVE_IPX
139  case AF_IPX:
140    len = sizeof(struct sockaddr_ipx);
141
142    return -1;
143   break;
144#endif
145  default:
146    return -1;
147 }
148
149 if ( fh == -1 )
150  return -1;
151
[3293]152 if ( listening ) {
153  if ( bind(fh, &(def->d.socket.sa.sa), len) == -1 ) {
154   close(fh);
155   return -1;
156  }
157
[3998]158  if ( !connection_less ) {
159   if ( listen(fh, one_client ? 1 : 16) == -1 ) {
160    close(fh);
[3293]161    return -1;
162   }
163
[3998]164   if ( one_client ) {
165    client = accept(fh, NULL, NULL);
166    close(fh);
167
168    if ( client == -1 ) {
169     return -1;
170    }
171
172    fh = client;
173   }
[3293]174  }
175 } else {
[5396]176  if ( def->o_flags & ROAR_VIOF_NONBLOCK ) {
177   if ( roar_socket_nonblock(fh, ROAR_SOCKET_NONBLOCK) == -1 ) {
178    close(fh);
179    return -1;
180   }
181  }
182
[3293]183  if ( connect(fh, &(def->d.socket.sa.sa), len) == -1 ) {
[5406]184#ifdef EINPROGRESS
[5396]185   if ( errno != EINPROGRESS ) {
[5406]186#endif
[5396]187    roar_err_from_errno();
188    close(fh);
189    return -1;
[5406]190#ifdef EINPROGRESS
[5396]191   }
[5406]192#endif
[3293]193  }
[1333]194 }
195
196 if ( roar_vio_open_fh_socket(calls, fh) == -1 ) {
197  close(fh);
198  return -1;
199 }
200
[1338]201 // there is no problem if the shutdown()s fail.
202 // some socket domains don't support unidirectional connections
203 // this just free()s some kernel buffers :)
204 switch (def->o_flags & (O_RDONLY|O_WRONLY|O_RDWR)) {
205  case O_RDONLY:
[1470]206    ROAR_SHUTDOWN(fh, SHUT_WR);
[1338]207   break;
208  case O_WRONLY:
[1470]209    ROAR_SHUTDOWN(fh, SHUT_RD);
[1338]210   break;
211 }
212
[1333]213 return 0;
[1464]214#else
215 return -1;
216#endif
[1333]217}
218
219int     roar_vio_socket_init_socket_def   (struct roar_vio_defaults * def, int domain, int type) {
[1464]220#ifdef _CAN_OPERATE
[1333]221 if ( def == NULL || domain == -1 || type == -1 )
222  return -1;
223
224 // we do not memset(def, 0, sizeof(...)) here
225 // because this is allready done in roar_vio_dstr_init_defaults()
226 // if we would be would override o_flags/o_mode and maybe others
227
228 memset(&(def->d.socket.sa), 0, sizeof(def->d.socket.sa));
229
230 def->type                     = ROAR_VIO_DEF_TYPE_SOCKET;
231 def->d.socket.domain          = domain;
232 def->d.socket.type            = type;
233 def->d.socket.sa.sa.sa_family = domain;
234
235 return 0;
[1464]236#else
237 return -1;
238#endif
[1333]239}
240
[1335]241int     roar_vio_socket_init_dstr_def     (struct roar_vio_defaults * def, char * dstr, int hint, int type,
242                                           struct roar_vio_defaults * odef) {
[1464]243#ifdef _CAN_OPERATE
[1335]244 char * host;
[1358]245#if defined(ROAR_HAVE_IPV4) || defined(ROAR_HAVE_IPV6)
[1335]246 int    port;
[1358]247#endif
[1650]248#if defined(ROAR_HAVE_IPV4)
249 int ret;
250#endif
[1335]251
[1337]252 if ( def == NULL )
[1335]253  return -1;
254
[1337]255 if ( dstr == NULL && odef == NULL )
256  return -1;
257
258 if ( dstr == NULL )
259  dstr = "";
260
[1616]261 ROAR_DBG("roar_vio_socket_init_dstr_def(def=%p, dstr='%s', hint=%i, type=%i, odef=%p) = ?", def, dstr, hint, type, odef);
[1335]262
263 if ( hint == -1 ) {
264  if ( 0 ) { // this is needed to keep the syntx ok, compiler will throw it away
265#ifdef ROAR_HAVE_IPV6
266  } else if ( strstr(dstr, "[") != NULL ) { // [ip]:service
267   hint = AF_INET6;
268#endif
269#ifdef ROAR_HAVE_LIBDNET
270  } else if ( strstr(dstr, "::") != NULL ) { // node::object
271   hint = AF_DECnet;
272#endif
273#ifdef ROAR_HAVE_IPX
274  } else if ( strstr(dstr, "(") != NULL ) { // net:mac(service)
275   hint = AF_IPX;
276#endif
277#ifdef ROAR_HAVE_UNIX
278  } else if ( strstr(dstr, "/") != NULL ) { // /path/to/sock
279   hint = AF_UNIX;
280#endif
[1358]281#ifdef ROAR_HAVE_IPV4
[1335]282  } else if ( strstr(dstr, ":") != NULL ) { // host:port
283   hint = AF_INET;
[1358]284#endif
[1335]285  }
286 }
287
288 if ( hint == -1 && odef != NULL ) { // if we still don't know what this is we try
289                                     // to use the parent objects request
[1350]290  ROAR_DBG("roar_vio_socket_init_dstr_def(*): hint=-1 && odef!=NULL");
[1335]291  if ( odef->type == ROAR_VIO_DEF_TYPE_SOCKET ) {
[1350]292   ROAR_DBG("roar_vio_socket_init_dstr_def(*): hint=-1 && odef!=NULL, using hint from odef");
[1335]293   hint = odef->d.socket.domain;
294  }
295 }
296
297 if ( hint == -1 ) /* we really have no glue what this is... */
298  return -1;
299
300#ifdef ROAR_HAVE_UNIX
301 if ( hint == AF_UNIX ) {
302  if ( *dstr != 0 && strcmp(dstr, "//") != 0 ) {
303   return roar_vio_socket_init_unix_def(def, dstr);
304  } else {
305   if ( roar_vio_socket_conv_def(odef, AF_UNIX) == -1 )
306    return -1;
307
308   return roar_vio_socket_init_unix_def(def, odef->d.socket.sa.un.sun_path);
309  }
310 }
311#endif
312
[1354]313 ROAR_DBG("roar_vio_socket_init_dstr_def(*) = ?");
314
[1337]315 if ( *dstr == 0 ) {
316  if ( roar_vio_socket_conv_def(odef, hint) == -1 )
317   return -1;
318
319  if ( odef->d.socket.type != type )
320   return -1;
321
[1353]322  if ( def != odef )
323   memcpy(def, odef, sizeof(struct roar_vio_defaults));
324
[1337]325  return 0;
326 }
327
[1335]328 for (; *dstr == '/'; dstr++);
329
[1354]330 ROAR_DBG("roar_vio_socket_init_dstr_def(*) = ?");
331
[1335]332 switch (hint) {
[1358]333#ifdef ROAR_HAVE_IPV4
[1335]334  case AF_INET:
335    host = dstr;
336    for (; *dstr != 0 && *dstr != ':'; dstr++);
337
338    if ( *dstr == ':' ) { // we have a port :)
339     *dstr++ = 0;
340     if ( (port = roar_vio_socket_get_port(dstr, AF_INET, type)) == -1 )
341      return -1;
342
[1650]343     ret = roar_vio_socket_init_inet4_def(def, host, port, type);
344
345     *(dstr-1) = ':';
346
347     return ret;
[1335]348    } else {
349     if ( roar_vio_socket_conv_def(odef, AF_INET) == -1 )
350      return -1;
351
352     return roar_vio_socket_init_inet4_def(def, host, ROAR_NET2HOST16(odef->d.socket.sa.in.sin_port), type);
353    }
354   break;
[1358]355#endif
[1335]356#ifdef ROAR_HAVE_LIBDNET
357  case AF_DECnet:
[1354]358    ROAR_DBG("roar_vio_socket_init_dstr_def(*) = ?");
359    host = dstr;
360
361    if ( type != SOCK_STREAM )
362     return -1;
363
364    if ( (dstr = strstr(dstr, "::")) == NULL ) {
365     if ( roar_vio_socket_conv_def(odef, AF_DECnet) == -1 )
366      return -1;
367
368     return -1;
369//     return roar_vio_socket_init_decnet_def(def, host, -1, dstr);
370    } else {
371     *dstr  = 0;
372      dstr += 2;
373     return roar_vio_socket_init_decnet_def(def, host, -1, dstr);
374    }
[1335]375   break;
376#endif
377#ifdef ROAR_HAVE_IPV6
378  case AF_INET6:
379    return -1;
380   break;
381#endif
382#ifdef ROAR_HAVE_IPX
383  case AF_IPX:
384    return -1;
385   break;
386#endif
387  default:
388    return -1;
389 }
390
391 return 0;
[1464]392#else
393 return -1;
394#endif
[1335]395}
396
[1464]397#ifdef _CAN_OPERATE
[1335]398int     roar_vio_socket_conv_def          (struct roar_vio_defaults * def, int domain) {
[1337]399 if ( def == NULL || domain == -1 )
400  return -1;
401
402#ifdef ROAR_HAVE_UNIX
403 if ( domain == AF_UNIX ) {
404  if ( def->type == ROAR_VIO_DEF_TYPE_SOCKET ) {
405   if ( def->d.socket.domain == AF_UNIX )
406    return 0;
407
408   return -1;
409  } else {
[1338]410   if ( def->type == ROAR_VIO_DEF_TYPE_FILE )
411    return roar_vio_socket_init_unix_def(def, def->d.file);
412
[1337]413   return -1;
414  }
415 }
416#endif
417
418 if ( def->type != ROAR_VIO_DEF_TYPE_SOCKET )
419  return -1;
420
421 if ( def->d.socket.domain == domain )
422  return 0;
423
[1339]424 // we sould add support to convert IPv4 <-> IPv6 here
425
[1335]426 return -1;
427}
428
429int     roar_vio_socket_get_port          (char * service, int domain, int type) {
[1461]430#ifdef ROAR_HAVE_GETSERVBYNAME
[1341]431 struct servent * serv  = NULL;
[5745]432 char           * proto = NULL;
[1461]433#endif
[1341]434 int              port;
[1348]435 char           * ts;
[1341]436
[5033]437 if ( service == NULL || domain == -1 || type == -1 ) {
438  roar_err_set(ROAR_ERROR_FAULT);
[1335]439  return -1;
[5033]440 }
[1335]441
[1348]442 if ( (ts = strstr(service, "/")) != NULL )
443  *ts = 0;
[1342]444
[1341]445 if ( sscanf(service, "%i", &port) == 1 )
446  return port;
447
[1348]448 if ( ts != NULL )
449  *ts = '/';
450
[1341]451 switch (domain) {
452#ifdef ROAR_HAVE_IPV6
453  case AF_INET6:
454#endif
[1358]455#ifdef ROAR_HAVE_IPV4
[1341]456  case AF_INET:
[1358]457#endif
458#if defined(ROAR_HAVE_IPV6) || defined(ROAR_HAVE_IPV4)
[1341]459    switch (type) {
[5745]460     case SOCK_STREAM:
461#ifdef ROAR_HAVE_GETSERVBYNAME
462       proto = "tcp";
463#endif
464      break;
465     case SOCK_DGRAM:
466#ifdef ROAR_HAVE_GETSERVBYNAME
467       proto = "udp";
468#endif
469      break;
[1341]470     default:
471      return -1;
472    }
473   break;
[1358]474#endif
[1341]475#ifdef ROAR_HAVE_LIBDNET
476  case AF_DECnet:
477#ifdef ROAR_HAVE_GETOBJECTBYNAME
478    return getobjectbyname(service);
479#else
480    if ( !strcmp(service, "roar") )
481     return 0;
482
[5033]483    roar_err_set(ROAR_ERROR_NOENT);
[1341]484    return -1;
485#endif
486   break;
487#endif
488  default:
[5033]489    roar_err_set(ROAR_ERROR_NOTSUP);
[1341]490    return -1;
491 }
492
[1461]493#ifdef ROAR_HAVE_GETSERVBYNAME
[1348]494 if ( ts != NULL )
495  *ts = 0;
496
[1341]497 if ( (serv = getservbyname(service, proto)) == NULL ) {
[5033]498  roar_err_from_errno();
499  ROAR_DBG("roar_vio_socket_get_port(*): Unknown service: %s/%s: %s", service, proto, strerror(errno));
[1348]500
501  if ( ts != NULL )
502   *ts = '/';
503
[1341]504  return -1;
505 }
506
[1348]507 if ( ts != NULL )
508  *ts = '/';
509
[1342]510 return ROAR_NET2HOST16(serv->s_port);
[1461]511#endif
512
[5033]513
514 roar_err_set(ROAR_ERROR_NOTSUP);
[1461]515 return -1;
[1335]516}
[1464]517#endif
[1335]518
519// AF_UNIX:
[5253]520int     roar_vio_socket_init_unix_def     (struct roar_vio_defaults * def, const char * path) {
[1464]521#if defined(ROAR_HAVE_UNIX) && defined(_CAN_OPERATE)
[1337]522 if ( def == NULL || path == NULL )
523  return -1;
524
[1333]525 if ( roar_vio_socket_init_socket_def(def, AF_UNIX, SOCK_STREAM) == -1 )
526  return -1;
527
528 strncpy(def->d.socket.sa.un.sun_path, path, sizeof(def->d.socket.sa.un.sun_path) - 1);
529
530 return 0;
[1335]531#else
532 return -1;
533#endif
[1333]534}
535
[1335]536// AF_DECnet:
[1354]537int     roar_vio_socket_init_decnetnode_def(struct roar_vio_defaults * def) {
[1464]538#if defined(ROAR_HAVE_LIBDNET) && defined(_CAN_OPERATE)
[1354]539 char               * node;
540 char               * ed;
541 struct nodeent     * ne;
542
543 if ( def == NULL )
544  return -1;
545
[5253]546 if ( def->d.socket.host == NULL )
547  return -1;
548
549 if ( (node = roar_mm_strdup(def->d.socket.host)) == NULL )
[1354]550  return -1;
551
552 if ( (ed = strstr(node, "/")) != NULL )
553  *ed = 0;
554
[5253]555 ne = getnodebyname(node);
556
557 roar_mm_free(ed);
558
559 if ( ne == NULL ) {
[1354]560  ROAR_ERR("roar_vio_socket_init_decnetnode_def(*): Can\'t resolve node name '%s'", node);
561  return -1;
562 }
563
564 memcpy(&(def->d.socket.sa.dn.sdn_add.a_addr), ne->n_addr, 2);
565
566 return 0;
[1355]567#else
568 return -1;
569#endif
[1354]570}
571
[5253]572int     roar_vio_socket_init_decnet_def   (struct roar_vio_defaults * def, const char * node, int object, char * objname) {
[1464]573#if defined(ROAR_HAVE_LIBDNET) && defined(_CAN_OPERATE)
[1354]574 struct sockaddr_dn * dn;
575
[1345]576 if ( def == NULL )
577  return -1;
578
579 if ( object < 1 && objname == NULL )
580  return -1;
581
582 if ( object == -1 )
[1348]583  object = roar_vio_socket_get_port(objname, AF_DECnet, SOCK_STREAM);
[1345]584
[1354]585 if ( object == -1 ) {
586  if ( objname == NULL ) {
587   return -1;
588  } else {
589   object = 0;
590  }
591 }
592
593 if ( roar_vio_socket_init_socket_def(def, AF_DECnet, SOCK_STREAM) == -1 )
[1345]594  return -1;
595
[1354]596 def->d.socket.host = node;
597 dn                 = &(def->d.socket.sa.dn);
598 dn->sdn_flags      = 0;
599 dn->sdn_objnum     = object;
600 dn->sdn_nodeaddrl  = 2;
601
602 if ( objname == NULL ) {
603  dn->sdn_objnamel   = 0;
604 } else {
605  dn->sdn_objnamel   = strlen(objname);
606  if ( dn->sdn_objnamel > DN_MAXOBJL )
607   dn->sdn_objnamel  = DN_MAXOBJL;
608
609  memcpy(&(dn->sdn_objname), objname, dn->sdn_objnamel);
610 }
611
612 return 0;
[1345]613#else
614 return -1;
615#endif
616}
[1332]617
[1335]618
619// AF_INET:
620int     roar_vio_socket_init_inet4host_def(struct roar_vio_defaults * def) {
[1464]621#if defined(ROAR_HAVE_IPV4) && defined(_CAN_OPERATE)
[1333]622 struct hostent     * he;
[1650]623 char               * ed, * pd;
[1333]624
[1335]625 if ( def == NULL )
[1333]626  return -1;
627
[1335]628 if ( def->d.socket.host == NULL )
629  return -1;
630
[1349]631 if ( (ed = strstr(def->d.socket.host, "/")) != NULL )
632  *ed = 0;
633
[1650]634 if ( (pd = strstr(def->d.socket.host, ":")) != NULL )
635  *pd = 0;
636
[1335]637 if ( (he = gethostbyname(def->d.socket.host)) == NULL ) {
[1333]638  ROAR_ERR("roar_vio_socket_init_inet4host_def(*): Can\'t resolve host name '%s'",
[1335]639                    def->d.socket.host);
[1349]640  if ( ed != NULL ) *ed = '/';
[1333]641  return -1;
642 }
643
[1650]644 if ( pd != NULL ) *pd = ':';
645
[1349]646 if ( ed != NULL ) *ed = '/';
647
[1333]648 memcpy((struct in_addr *)&def->d.socket.sa.in.sin_addr, he->h_addr, sizeof(struct in_addr));
649
650 return 0;
[1358]651#else
652 return -1;
653#endif
[1333]654}
655
[5253]656int     roar_vio_socket_init_inet4_def    (struct roar_vio_defaults * def, const char * host, int port, int type) {
[1464]657#if defined(ROAR_HAVE_IPV4) && defined(_CAN_OPERATE)
[1335]658 if ( roar_vio_socket_init_socket_def(def, AF_INET, type) == -1 )
[1333]659  return -1;
660
[1335]661 def->d.socket.host             = host;
[1333]662
663 def->d.socket.sa.in.sin_port   = ROAR_HOST2NET16(port);
664
665 return 0;
[1358]666#else
667 return -1;
668#endif
[1333]669}
670
[5253]671int     roar_vio_socket_init_tcp4_def     (struct roar_vio_defaults * def, const char * host, int port) {
[1335]672 return roar_vio_socket_init_inet4_def(def, host, port, SOCK_STREAM);
673}
674
[5253]675int     roar_vio_socket_init_udp4_def     (struct roar_vio_defaults * def, const char * host, int port) {
[1335]676 return roar_vio_socket_init_inet4_def(def, host, port, SOCK_DGRAM);
677}
678
679
680// AF_INET6:
681int     roar_vio_socket_init_inet6host_def(struct roar_vio_defaults * def);
[5253]682int     roar_vio_socket_init_inet6_def    (struct roar_vio_defaults * def, const char * host, int port, int type) {
[1335]683 return -1;
684}
685
[5253]686int     roar_vio_socket_init_tcp6_def     (struct roar_vio_defaults * def, const char * host, int port) {
[1335]687 return roar_vio_socket_init_inet6_def(def, host, port, SOCK_STREAM);
688}
689
[5253]690int     roar_vio_socket_init_udp6_def     (struct roar_vio_defaults * def, const char * host, int port) {
[1335]691 return roar_vio_socket_init_inet6_def(def, host, port, SOCK_DGRAM);
692}
[1332]693
[1331]694//ll
Note: See TracBrowser for help on using the repository browser.