Changeset 5567:6ecf012d7063 in roaraudio


Ignore:
Timestamp:
07/16/12 17:02:17 (12 years ago)
Author:
phi
Branch:
default
Phase:
public
Message:

roard now tries to auto load missing protocols as plugins (Closes: #275)

Files:
6 edited

Legend:

Unmodified
Added
Removed
  • ChangeLog

    r5565 r5567  
    22        * Stop building roard with -rdynamic on win32 (Closes: #269) 
    33        * Renamed protocol plugins for roard to protocol-* (Closes: #258) 
     4        * roard now tries to auto load missing protocols as plugins (Closes: #275) 
    45 
    56v. 1.0beta3 - Sun Jul 15 2012 26:08 CEST 
  • roard/clients.c

    r5452 r5567  
    3232// declared 'extern' 
    3333struct roar_client_server * g_clients[ROAR_CLIENTS_MAX]; 
    34 struct roard_proto g_proto[MAX_PROTOS]; 
    35  
    36  
    37 struct roard_proto g_proto[MAX_PROTOS] = { 
    38 #ifndef ROAR_WITHOUT_DCOMP_EMUL_ESD 
    39 #ifdef ROAR_HAVE_H_ESD 
    40  {ROAR_PROTO_ESOUND, ROAR_SUBSYS_WAVEFORM, "EsounD emulation", NULL, NULL, NULL, emul_esd_check_client, NULL, NULL}, 
    41 #endif 
     34 
     35static struct roard_proto_handle __protos[MAX_PROTOS] = { 
     36 {.proto = ROAR_PROTO_ROARAUDIO, .lhandle = NULL, .type = ROARD_PROTO_TYPE_BUILDIN, 
     37 .impl = {.buildin = 0}}, 
     38#if !defined(ROAR_WITHOUT_DCOMP_EMUL_ESD) && defined(ROAR_HAVE_H_ESD) 
     39 {.proto = ROAR_PROTO_ESOUND, .lhandle = NULL, .type = ROARD_PROTO_TYPE_ROARDPROTO, 
     40 .impl = {.roardproto = {ROAR_PROTO_ESOUND, ROAR_SUBSYS_WAVEFORM, "EsounD emulation", NULL, NULL, NULL, emul_esd_check_client, NULL, NULL}}}, 
    4241#endif 
    4342#ifndef ROAR_WITHOUT_DCOMP_EMUL_RPLAY 
    44  {ROAR_PROTO_RPLAY, ROAR_SUBSYS_WAVEFORM, "RPlay emulation", NULL, NULL, NULL, emul_rplay_check_client, NULL, NULL}, 
     43 {.proto = ROAR_PROTO_RPLAY, .lhandle = NULL, .type = ROARD_PROTO_TYPE_ROARDPROTO, 
     44 .impl = {.roardproto = {ROAR_PROTO_RPLAY, ROAR_SUBSYS_WAVEFORM, "RPlay emulation", NULL, NULL, NULL, emul_rplay_check_client, NULL, NULL}}}, 
    4545#endif 
    4646#ifndef ROAR_WITHOUT_DCOMP_EMUL_GOPHER 
    47  {ROAR_PROTO_GOPHER, ROAR_SUBSYS_WAVEFORM, "The Internet Gopher Protocol", NULL, NULL, NULL, emul_gopher_check_client, NULL, emul_gopher_flushed_client}, 
    48 #endif 
    49  {-1, 0, NULL, NULL, NULL, NULL, NULL, NULL} 
     47 {.proto = ROAR_PROTO_GOPHER, .lhandle = NULL, .type = ROARD_PROTO_TYPE_ROARDPROTO, 
     48 .impl = {.roardproto = {ROAR_PROTO_GOPHER, ROAR_SUBSYS_WAVEFORM, "The Internet Gopher Protocol", NULL, NULL, NULL, emul_gopher_check_client, NULL, 
     49 emul_gopher_flushed_client}}}, 
     50#endif 
     51 {.proto = -1} 
    5052}; 
    5153 
     
    5961  g_clients[i] = NULL; 
    6062 
    61  for (i = 0; g_proto[i].proto != -1; i++); 
     63 for (i = 0; __protos[i].proto != -1; i++); 
    6264 
    6365 for (; i < MAX_PROTOS; i++) 
    64   g_proto[i].proto = -1; 
     66  __protos[i].proto = -1; 
    6567 
    6668 return 0; 
     
    164166int clients_delete (int id) { 
    165167 struct roar_client_server * cs; 
     168 const struct roard_proto_handle * proto; 
    166169 int i; 
    167170 int close_client_fh = 1; 
     
    173176 cs = g_clients[id]; 
    174177 
    175  for (i = 0; g_proto[i].proto != -1; i++) { 
    176   if ( g_proto[i].proto == ROAR_CLIENT(cs)->proto ) { 
    177    if ( g_proto[i].delete_client != NULL ) { 
    178     g_proto[i].delete_client(id); 
    179    } 
     178 proto = clients_get_protohandle(ROAR_CLIENT(cs)->proto); 
     179 if ( proto != NULL ) { 
     180  switch (proto->type) { 
     181   case ROARD_PROTO_TYPE_BUILDIN: 
     182     /* noop */ 
     183    break; 
     184   case ROARD_PROTO_TYPE_ROARDPROTO: 
     185     if ( proto->impl.roardproto.delete_client != NULL ) 
     186      proto->impl.roardproto.delete_client(id); 
     187    break; 
    180188  } 
    181189 } 
     
    558566 struct roar_vio_calls  vio; 
    559567 struct roar_error_state errstate; 
     568 const struct roard_proto_handle * proto; 
    560569 int command_error; 
    561570 char * data = NULL; 
     
    565574 uint32_t flags[2] = {COMMAND_FLAG_NONE, COMMAND_FLAG_NONE}; 
    566575 uint32_t event; 
    567  size_t i; 
    568576 
    569577 ROAR_DBG("clients_check(id=%i) = ?", id); 
     
    655663  default: 
    656664    rv = -1; 
    657     for (i = 0; g_proto[i].proto != -1; i++) { 
    658      if ( g_proto[i].proto == c->proto ) { 
    659       roar_vio_open_fh_socket(&vio, clients_get_fh(id)); 
    660       if ( g_proto[i].lhandle != NULL ) 
    661        roar_dl_context_restore(g_proto[i].lhandle); 
    662       rv = g_proto[i].check_client(id, &vio); 
    663       if ( g_proto[i].lhandle != NULL ) 
    664        roar_dl_context_store(g_proto[i].lhandle); 
     665    proto = clients_get_protohandle(c->proto); 
     666    if ( proto != NULL ) { 
     667     roar_vio_open_fh_socket(&vio, clients_get_fh(id)); 
     668     if ( proto->lhandle != NULL ) 
     669      roar_dl_context_restore(proto->lhandle); 
     670 
     671     switch (proto->type) { 
     672      case ROARD_PROTO_TYPE_BUILDIN: 
     673        ROAR_WARN("clients_check(id=%i): proto(%i) marked as buildin but isn't. BAD.", id, proto->proto); 
     674       break; 
     675      case ROARD_PROTO_TYPE_ROARDPROTO: 
     676        if ( proto->impl.roardproto.check_client != NULL ) 
     677         rv = proto->impl.roardproto.check_client(id, &vio); 
     678       break; 
    665679     } 
     680 
     681     if ( proto->lhandle != NULL ) 
     682      roar_dl_context_store(proto->lhandle); 
    666683    } 
    667684 } 
     
    675692 
    676693int clients_flush      (int id) { 
    677  struct roar_vio_calls         vio; 
    678  struct roar_client_server   * cs; 
    679  struct roar_client          * c; 
    680  struct roard_proto          * p = NULL; 
    681  size_t i; 
     694 struct roar_vio_calls             vio; 
     695 struct roar_client_server       * cs; 
     696 struct roar_client              * c; 
     697 const struct roard_proto_handle * p; 
    682698 size_t len; 
    683699 ssize_t ret; 
    684700 void * buf; 
     701 int rv; 
    685702 
    686703 _CHECK_CID(id); 
     
    688705 c = ROAR_CLIENT(cs = g_clients[id]); 
    689706 
    690  for (i = 0; g_proto[i].proto != -1; i++) { 
    691   if ( g_proto[i].proto == c->proto ) { 
    692    p = &(g_proto[i]); 
     707 p = clients_get_protohandle(c->proto); 
     708 
     709 if ( p == NULL ) 
     710  return -1; 
     711 
     712 roar_vio_open_fh_socket(&vio, clients_get_fh(id)); 
     713 
     714 switch (p->type) { 
     715  case ROARD_PROTO_TYPE_BUILDIN: 
     716    /* noop */ 
    693717   break; 
    694   } 
    695  } 
    696  
    697  if ( p == NULL ) 
    698   return -1; 
    699  
    700  roar_vio_open_fh_socket(&vio, clients_get_fh(id)); 
    701  
    702  if ( p->flush_client != NULL ) { 
    703   if ( p->lhandle != NULL ) 
    704    roar_dl_context_restore(p->lhandle); 
    705   return p->flush_client(id, &vio); 
    706   if ( p->lhandle != NULL ) 
    707    roar_dl_context_store(p->lhandle); 
     718  case ROARD_PROTO_TYPE_ROARDPROTO: 
     719    if ( p->impl.roardproto.flush_client != NULL ) { 
     720     if ( p->lhandle != NULL ) 
     721      roar_dl_context_restore(p->lhandle); 
     722     rv = p->impl.roardproto.flush_client(id, &vio); 
     723     if ( p->lhandle != NULL ) 
     724      roar_dl_context_store(p->lhandle); 
     725     return rv; 
     726    } 
     727   break; 
    708728 } 
    709729 
     
    734754 
    735755 if ( cs->outbuf == NULL ) { 
    736   if ( p->flushed_client != NULL ) { 
    737    if ( p->lhandle != NULL ) 
    738     roar_dl_context_restore(p->lhandle); 
    739    return p->flushed_client(id, &vio); 
    740    if ( p->lhandle != NULL ) 
    741     roar_dl_context_store(p->lhandle); 
     756  switch (p->type) { 
     757   case ROARD_PROTO_TYPE_BUILDIN: 
     758     /* noop */ 
     759    break; 
     760   case ROARD_PROTO_TYPE_ROARDPROTO: 
     761     if ( p->impl.roardproto.flushed_client != NULL ) { 
     762      if ( p->lhandle != NULL ) 
     763       roar_dl_context_restore(p->lhandle); 
     764      rv = p->impl.roardproto.flushed_client(id, &vio); 
     765      if ( p->lhandle != NULL ) 
     766       roar_dl_context_store(p->lhandle); 
     767      return rv; 
     768     } 
     769    break; 
    742770  } 
    743771 } 
     
    836864 
    837865// proto support 
     866const struct roard_proto_handle * clients_get_protohandle(const int proto) { 
     867 size_t i; 
     868 
     869 if ( proto < 0 ) { 
     870  roar_err_set(ROAR_ERROR_INVAL); 
     871  return NULL; 
     872 } 
     873 
     874 for (i = 0; i < (sizeof(__protos)/sizeof(*__protos)); i++) 
     875  if ( __protos[i].proto == proto ) 
     876   return &(__protos[i]); 
     877 
     878 roar_err_set(ROAR_ERROR_NOENT); 
     879 return NULL; 
     880} 
     881 
    838882int clients_register_proto(struct roard_proto * proto, struct roar_dl_lhandle * lhandle) { 
    839  const size_t len = sizeof(g_proto)/sizeof(*g_proto); 
     883 const size_t len = sizeof(__protos)/sizeof(*__protos); 
    840884 size_t i; 
    841885 
     
    843887  return -1; 
    844888 
    845  for (i = 0; g_proto[i].proto != -1; i++); 
     889 for (i = 0; __protos[i].proto != -1; i++); 
    846890 
    847891 // i is now at pos of current EOS entry. 
     
    851895  return -1; 
    852896 
    853  memcpy(&(g_proto[i]), proto, sizeof(*g_proto)); 
    854  
    855  g_proto[i].lhandle = lhandle; 
     897 memcpy(&(__protos[i].impl.roardproto), proto, sizeof(__protos[i].impl.roardproto)); 
     898 
     899 __protos[i].impl.roardproto.lhandle = lhandle; 
     900 
     901 __protos[i].proto   = proto->proto; 
     902 __protos[i].type    = ROARD_PROTO_TYPE_ROARDPROTO; 
     903 __protos[i].lhandle = lhandle; 
    856904 
    857905 return 0; 
     
    859907 
    860908int clients_unregister_proto(int proto) { 
    861  const size_t len = sizeof(g_proto)/sizeof(*g_proto); 
    862909 size_t i; 
    863910 
     
    867914 } 
    868915 
    869  for (i = 0; i < len; i++) { 
    870   if ( g_proto[i].proto == proto ) { 
    871    memset(&(g_proto[i]), 0, sizeof(*g_proto)); 
    872    g_proto[i].proto = -1; 
     916 for (i = 0; i < (sizeof(__protos)/sizeof(*__protos)); i++) { 
     917  if ( __protos[i].proto == proto ) { 
     918   memset(&(__protos[i]), 0, sizeof(*__protos)); 
     919   __protos[i].proto = -1; 
    873920   return 0; 
    874921  } 
     
    880927 
    881928void print_protolist        (enum output_format format) { 
    882  const size_t len = sizeof(g_proto)/sizeof(*g_proto); 
    883  struct roard_proto * p; 
     929 struct roard_proto_handle * p; 
    884930 char subsys[7] = "      "; 
     931 const char * description; 
    885932 size_t i; 
    886933 
     
    922969 } 
    923970 
    924  for (i = 0; i < len; i++) { 
    925   p = &(g_proto[i]); 
     971 for (i = 0; i < (sizeof(__protos)/sizeof(*__protos)); i++) { 
     972  p = &(__protos[i]); 
    926973  if ( p->proto == -1 ) 
    927974   continue; 
    928975 
    929976  strncpy(subsys, "      ", 6); 
    930  
    931   if ( p->subsystems & ROAR_SUBSYS_WAVEFORM ) 
    932    subsys[0] = 'W'; 
    933   if ( p->subsystems & ROAR_SUBSYS_MIDI ) 
    934    subsys[1] = 'M'; 
    935   if ( p->subsystems & ROAR_SUBSYS_CB ) 
    936    subsys[2] = 'C'; 
    937   if ( p->subsystems & ROAR_SUBSYS_LIGHT ) 
    938    subsys[3] = 'L'; 
    939   if ( p->subsystems & ROAR_SUBSYS_RAW ) 
    940    subsys[4] = 'R'; 
    941   if ( p->subsystems & ROAR_SUBSYS_COMPLEX ) 
    942    subsys[5] = 'X'; 
     977  description = "(none)"; 
     978 
     979  if ( p->type == ROARD_PROTO_TYPE_ROARDPROTO ) { 
     980   if ( p->impl.roardproto.subsystems & ROAR_SUBSYS_WAVEFORM ) 
     981    subsys[0] = 'W'; 
     982   if ( p->impl.roardproto.subsystems & ROAR_SUBSYS_MIDI ) 
     983    subsys[1] = 'M'; 
     984   if ( p->impl.roardproto.subsystems & ROAR_SUBSYS_CB ) 
     985    subsys[2] = 'C'; 
     986   if ( p->impl.roardproto.subsystems & ROAR_SUBSYS_LIGHT ) 
     987    subsys[3] = 'L'; 
     988   if ( p->impl.roardproto.subsystems & ROAR_SUBSYS_RAW ) 
     989    subsys[4] = 'R'; 
     990   if ( p->impl.roardproto.subsystems & ROAR_SUBSYS_COMPLEX ) 
     991    subsys[5] = 'X'; 
     992 
     993   description = p->impl.roardproto.description; 
     994  } 
    943995 
    944996  switch (format) { 
    945997   case FORMAT_NATIVE: 
    946      printf("  %-13s %s - %s\n", roar_proto2str(p->proto), subsys, p->description); 
     998     printf("  %-13s %s - %s\n", roar_proto2str(p->proto), subsys, description); 
    947999    break; 
    9481000   case FORMAT_WIKI: 
    949      printf("||%s || ||%s ||%s ||\n", roar_proto2str(p->proto), subsys, p->description); 
     1001     printf("||%s || ||%s ||%s ||\n", roar_proto2str(p->proto), subsys, description); 
    9501002    break; 
    9511003   case FORMAT_CSV: 
    952      printf("%s,,%s,%s\n", roar_proto2str(p->proto), subsys, p->description); 
     1004     printf("%s,,%s,%s\n", roar_proto2str(p->proto), subsys, description); 
    9531005    break; 
    9541006  } 
  • roard/include/client.h

    r5448 r5567  
    8787}; 
    8888 
     89enum roard_proto_type { 
     90 ROARD_PROTO_TYPE_BUILDIN = 0, 
     91 ROARD_PROTO_TYPE_ROARDPROTO = 1, 
     92 ROARD_PROTO_TYPE_COMMON = 2 
     93}; 
     94 
     95struct roard_proto_handle { 
     96 int proto; 
     97 struct roar_dl_lhandle * lhandle; 
     98 enum roard_proto_type type; 
     99 union { 
     100  int buildin; // dummy 
     101  struct roard_proto roardproto; 
     102  // add common here when ready. 
     103 } impl; 
     104}; 
     105 
    89106#define MAX_PROTOS 8 
    90 extern struct roard_proto g_proto[MAX_PROTOS]; 
    91107 
    92108// basic functions 
     
    119135 
    120136// proto support 
     137const struct roard_proto_handle * clients_get_protohandle(const int proto); 
    121138int clients_register_proto  (struct roard_proto * proto, struct roar_dl_lhandle * lhandle); 
    122139int clients_unregister_proto(int proto); 
  • roard/network.c

    r5381 r5567  
    7070 
    7171int net_get_new_client (struct roard_listen * lsock) { 
     72 const struct roard_proto_handle * proto; 
    7273 int fh; 
    7374 int client; 
     
    7778 struct sockaddr_storage  addr; 
    7879 socklen_t                addrlen = sizeof(addr); 
    79  size_t i; 
    8080 int supported = 0; 
    8181 
     
    191191     return -1; 
    192192 
    193     for (i = 0; g_proto[i].proto != -1; i++) { 
    194      //printf("g_proto[i=%i].proto=%i, lsock->proto=%i\n", (int)i, g_proto[i].proto, lsock->proto); 
    195      if ( g_proto[i].proto == lsock->proto ) { 
    196       supported = 1; 
    197       if ( g_proto[i].new_client != NULL ) { 
    198        if ( g_proto[i].new_client(client, &vio, lsock) == -1 ) { 
     193    proto = clients_get_protohandle(lsock->proto); 
     194    supported = 0; 
     195    if ( proto != NULL ) { 
     196     switch (proto->type) { 
     197      case ROARD_PROTO_TYPE_BUILDIN: 
     198        //this should not end up here. 
     199        ROAR_WARN("net_get_new_client(lsock=%p): proto(%i) marked as buildin but isn't. BAD.", lsock, lsock->proto); 
    199200        supported = 0; 
    200        } 
    201       } 
     201       break; 
     202      case ROARD_PROTO_TYPE_ROARDPROTO: 
     203        supported = 1; 
     204        if ( proto->impl.roardproto.new_client != NULL ) { 
     205         if ( proto->impl.roardproto.new_client(client, &vio, lsock) == -1 ) { 
     206          supported = 0; 
     207         } 
     208        } 
     209       break; 
    202210     } 
    203211    } 
  • roard/plugins.c

    r5427 r5567  
    3535static struct _roard_plugin * _pp = NULL; 
    3636 
     37static int _plugins_inited = 0; 
     38 
    3739static struct _roard_plugin * _find_free(void) { 
    3840 int i; 
     
    7880} 
    7981 
     82static int plugins_init_one(struct _roard_plugin * plugin) { 
     83 if ( plugin == NULL ) 
     84  return -1; 
     85 
     86 _pp = plugin; 
     87 _pp->sched = NULL; 
     88 
     89 if ( roar_dl_ra_init(plugin->lhandle, NULL, NULL) == -1 ) { 
     90  ROAR_WARN("plugins_init(void): Can not RA init lib at %p: %s", plugin->lhandle, roar_error2str(roar_error)); 
     91  plugins_delete(plugin); 
     92  return -1; 
     93 } 
     94 
     95 if ( plugin->sched != NULL ) { 
     96  if ( plugin->sched->init != NULL ) { 
     97   roar_dl_context_restore(plugin->lhandle); 
     98   plugin->sched->init(); 
     99   roar_dl_context_store(plugin->lhandle); 
     100  } 
     101 } 
     102 
     103 roar_dl_appsched_trigger(plugin->lhandle, ROAR_DL_APPSCHED_INIT); 
     104 
     105 _pp = NULL; 
     106 
     107 return 0; 
     108} 
     109 
    80110int plugins_init  (void) { 
    81  int i; 
     111 size_t i; 
     112 
     113 if ( _plugins_inited ) { 
     114  roar_err_set(ROAR_ERROR_BUSY); 
     115  return -1; 
     116 } 
    82117 
    83118 for (i = 0; i < MAX_PLUGINS; i++) { 
    84119  if ( g_plugins[i].lhandle != NULL ) { 
    85    _pp = &(g_plugins[i]); 
    86  
    87    _pp->sched = NULL; 
    88  
    89    if ( roar_dl_ra_init(g_plugins[i].lhandle, NULL, NULL) == -1 ) { 
    90     ROAR_WARN("plugins_init(void): Can not RA init lib at %p: %s", g_plugins[i].lhandle, roar_error2str(roar_error)); 
    91     plugins_delete(&(g_plugins[i])); 
    92     continue; 
    93    } 
    94  
    95    if ( g_plugins[i].sched != NULL ) { 
    96     if ( g_plugins[i].sched->init != NULL ) { 
    97      roar_dl_context_restore(g_plugins[i].lhandle); 
    98      g_plugins[i].sched->init(); 
    99      roar_dl_context_store(g_plugins[i].lhandle); 
    100     } 
    101    } 
    102  
    103    roar_dl_appsched_trigger(g_plugins[i].lhandle, ROAR_DL_APPSCHED_INIT); 
    104  
    105    _pp = NULL; 
    106   } 
    107  } 
     120   plugins_init_one(&(g_plugins[i])); 
     121  } 
     122 } 
     123 
     124 _plugins_inited = 1; 
    108125 
    109126 return 0; 
     
    169186 } 
    170187 
     188 if ( _plugins_inited ) 
     189  return plugins_init_one(next); 
     190 
    171191 return 0; 
    172192} 
  • roard/roard.c

    r5566 r5567  
    953953 return 0; 
    954954} 
     955 
     956static int check_listen(void) { 
     957 const struct roard_proto_handle * proto; 
     958 const char * protoname; 
     959 char buffer[80]; 
     960 size_t i, j; 
     961 
     962 for (i = 0; i < ROAR_MAX_LISTEN_SOCKETS; i++) { 
     963  if ( g_listen[i].used ) { 
     964   proto = clients_get_protohandle(g_listen[i].proto); 
     965   if ( proto != NULL ) 
     966    continue; 
     967   protoname = roar_proto2str(g_listen[i].proto); 
     968   if ( protoname == NULL ) { 
     969    ROAR_ERR("check_listen(void): protocol %i is unknown (protocol ID not assigned?)."); 
     970   } else { 
     971    ROAR_DBG("check_listen(void): Unknown protocol %s(%i) is used.", protoname, g_listen[i].proto); 
     972    snprintf(buffer, sizeof(buffer), "protocol-%s", protoname); 
     973    for (j = 0; buffer[j]; j++) 
     974     buffer[j] = tolower(buffer[j]); 
     975 
     976    ROAR_DBG("check_listen(void): Trying to load plugin \"%s\"", buffer); 
     977    if ( plugins_load(buffer, NULL) == -1 ) { 
     978     ROAR_WARN("check_listen(void): unabled to load plugin: %s: %s", buffer, roar_errorstring); 
     979    } 
     980   } 
     981 
     982   // recheck: 
     983   proto = clients_get_protohandle(g_listen[i].proto); 
     984   if ( proto == NULL ) { 
     985    ROAR_WARN("check_listen(void): Protocol %s(%i) is still unknown. Deleting listen socket.", protoname, g_listen[i].proto); 
     986    roar_vio_close(&(g_listen[i].sock)); 
     987#ifdef ROAR_HAVE_UNIX 
     988    if ( server[i] != NULL ) 
     989     if ( server[i][0] == '/' ) 
     990      unlink(server[i]); 
     991#endif 
     992    g_listen[i].used = 0; 
     993    server[i] = NULL; 
     994   } 
     995  } 
     996 } 
     997 
     998 return 0; 
     999} 
    9551000#endif 
    9561001 
     
    24002445 } 
    24012446 
     2447 if ( check_listen() == -1 ) { 
     2448  ROAR_ERR("Can not check listen sockets. BAD."); 
     2449  return 1; 
     2450 } 
    24022451 
    24032452 // we should handle this on microcontrollers, too. 
Note: See TracChangeset for help on using the changeset viewer.