Hello all,

As many of you know, I've been working on a network module for Chapel
that integrates system sockets with the IO library and provides a
simplified interface for establishing socket connections. It's at the
point now that it's fairly usable for writing simple (TCP-based)
applications, so I thought I'd make a Request for Review thread for it
and see if people think it's on the right track.

Specifically, I'm curious if the getaddrinfo reference causes
compilation warnings on Cray machines like it did in the past and if the
functions I added to sys.c (e.g. inet_ntop) are available on platforms
other than Linux.

A few things are still missing before it's ready to commit that I plan
on getting to soon, including datagram support, unit tests, usage
examples, better documentation, and a few bugfixes and usability
improvements. In the meantime, there's an example server attached that
echoes back lines until a blank line is found. Try connecting to
http://127.0.0.1:8080/ in your browser while running it to see some HTTP
headers.

Note that this requires the substring patch I submitted previously to
work, so if you want to try it out you'll need to apply that as well.

Potential commit message, once it's ready:
> This patch adds a new standard library module (Net) for performing
> socket I/O.
> It provides functions for establishing outgoing connections and
> listening for
> incoming connection requests, as well as a number of utilities and
> primitives
> for handling common network tasks (e.g., resolving host names and parsing
> URIs).

-- Brandon
Index: compiler/passes/reservedSymbolNames
===================================================================
--- compiler/passes/reservedSymbolNames	(revision 22391)
+++ compiler/passes/reservedSymbolNames	(working copy)
@@ -246,3 +246,15 @@
   ten23
   ts
   two
+
+  // symbols from sys/socket.h
+  accept
+  accept4
+  bind
+  connect
+  listen
+  recv
+  recvfrom
+  socket
+  send
+  sendto
Index: modules/standard/IO.chpl
===================================================================
--- modules/standard/IO.chpl	(revision 22391)
+++ modules/standard/IO.chpl	(working copy)
@@ -126,6 +126,7 @@
 extern const QIO_HINT_PARALLEL:c_int;
 extern const QIO_HINT_DIRECT:c_int;
 extern const QIO_HINT_NOREUSE:c_int;
+extern const QIO_HINT_OWNED:c_int;
 
 /** NONE means normal operation, nothing special
     to hint. Expect to use NONE most of the time.
Index: modules/standard/Sys.chpl
===================================================================
--- modules/standard/Sys.chpl	(revision 22391)
+++ modules/standard/Sys.chpl	(working copy)
@@ -66,7 +66,16 @@
   extern const AF_ATMPVC:c_int;
   extern const AF_APPLETALK:c_int;
   extern const AF_PACKET:c_int;
+  extern const AF_UNSPEC:c_int;
 
+  // addrinfo flags
+  extern const AI_PASSIVE:c_int;
+  extern const AI_CANONNAME:c_int;
+  extern const AI_NUMERICHOST:c_int;
+  extern const AI_V4MAPPED:c_int;
+  extern const AI_ALL:c_int;
+  extern const AI_ADDRCONFIG:c_int;
+
   // socket types
   extern const SOCK_STREAM:c_int;
   extern const SOCK_DGRAM:c_int;
@@ -107,6 +116,11 @@
   extern const IPPROTO_TCP:c_int;
   extern const IPPROTO_UDP:c_int;
 
+  // general socket options
+  extern const SO_REUSEADDR:c_int;
+  extern const SO_BINDTODEVICE:c_int;
+  extern const SO_BROADCAST:c_int;
+
   // IP socket options
   extern const IP_ADD_MEMBERSHIP:c_int;
   extern const IP_DROP_MEMBERSHIP:c_int;
@@ -168,14 +182,7 @@
 
   /* SOCKET STRUCTURE TYPES */
 
-  extern type sys_sockaddr_storage_t;
-  extern record sys_sockaddr_t {
-    var addr:sys_sockaddr_storage_t;
-    var len:socklen_t;
-    proc sys_sockaddr_t() {
-      sys_init_sys_sockaddr_t(this);
-    }
-  }
+  extern type sys_sockaddr_t;
 
   extern record sys_addrinfo_t {
     var ai_flags: c_int;
@@ -194,6 +201,9 @@
   proc sys_addrinfo_ptr_t.canonname:string { return sys_getaddrinfo_cannonname(this); }
   proc sys_addrinfo_ptr_t.next:sys_addrinfo_ptr_t { return sys_getaddrinfo_next(this); }
 
+  proc sys_sockaddr_t.family:c_int { return sys_sockaddr_family(this); }
+  proc sys_sockaddr_t.port:c_int { return sys_sockaddr_port(this); }
+
   extern proc sys_init_sys_sockaddr(inout addr:sys_sockaddr_t);
   extern proc sys_strerror(error:err_t, inout string_out:string):err_t;
 
@@ -217,6 +227,7 @@
   extern proc sys_accept(sockfd:fd_t, inout add_out:sys_sockaddr_t, inout fd_out:fd_t):err_t;
   extern proc sys_bind(sockfd:fd_t, inout addr:sys_sockaddr_t):err_t;
   extern proc sys_connect(sockfd:fd_t, inout addr:sys_sockaddr_t):err_t;
+
   extern proc sys_getaddrinfo(node:string, service:string, inout hints:sys_addrinfo_t, inout res_out:sys_addrinfo_ptr_t):err_t;
   extern proc sys_getaddrinfo_flags(res:sys_addrinfo_ptr_t):c_int;
   extern proc sys_getaddrinfo_family(res:sys_addrinfo_ptr_t):c_int;
@@ -224,8 +235,11 @@
   extern proc sys_getaddrinfo_protocol(res:sys_addrinfo_ptr_t):c_int;
   extern proc sys_getaddrinfo_addr(res:sys_addrinfo_ptr_t):sys_sockaddr_t;
   extern proc sys_getaddrinfo_next(res:sys_addrinfo_ptr_t):sys_addrinfo_ptr_t;
-  extern proc sys_freeaddrinfo(res:sys_addrinfo_ptr_t);
+  extern proc sys_freeaddrinfo(inout res:sys_addrinfo_ptr_t);
 
+  extern proc sys_sockaddr_family(sa:sys_sockaddr_t):c_int;
+  extern proc sys_sockaddr_port(sa:sys_sockaddr_t):c_int;
+
   extern proc sys_getnameinfo(inout addr:sys_sockaddr_t, inout host_out:string, inout serv_out:string, flags:c_int):err_t;
   extern proc sys_getpeername(sockfd:fd_t, inout addr:sys_sockaddr_t):err_t;
   extern proc sys_getsockname(sockfd:fd_t, inout addr:sys_sockaddr_t):err_t;
@@ -233,13 +247,16 @@
   // TODO -- these should be generic, assuming caller knows what they
   // are doing.
   extern proc sys_getsockopt(sockfd:fd_t, level:c_int, optname:c_int, optval:c_void_ptr, inout optlen:socklen_t):err_t;
-  extern proc sys_setsockopt(sockfd:fd_t, level:c_int, optname:c_int, optval:c_void_ptr, optlen:socklen_t):err_t;
+  extern proc sys_setsockopt(sockfd:fd_t, level:c_int, optname:c_int, ref optval:?, optlen:socklen_t):err_t;
 
   extern proc sys_listen(sockfd:fd_t, backlog:c_int):err_t;
   extern proc sys_shutdown(sockfd:fd_t, how:c_int):err_t;
   extern proc sys_socket(_domain:c_int, _type:c_int, protocol:c_int, inout sockfd_out:fd_t):err_t;
   extern proc sys_socketpair(_domain:c_int, _type:c_int, protocol:c_int, inout sockfd_out_a:fd_t, inout sockfd_out_b:fd_t):err_t;
 
+  // arpa/inet.h
+  extern proc sys_inet_ntop(family:c_int, ref addr:sys_sockaddr_t, ref str:string):err_t;
+
   // recv, recvfrom, recvmsg, send, sendto, sendmsg are in io
 }
 
Index: runtime/include/qio/sys.h
===================================================================
--- runtime/include/qio/sys.h	(revision 22391)
+++ runtime/include/qio/sys.h	(working copy)
@@ -17,6 +17,7 @@
 #include <netdb.h>
 #include <unistd.h>
 #include <stdio.h>
+#include <arpa/inet.h>
 
 typedef int fd_t;
 
@@ -25,15 +26,8 @@
 #define HAS_GETADDRINFO
 #endif
 
+typedef struct sockaddr_storage sys_sockaddr_t;
 
-typedef struct sockaddr_storage sys_sockaddr_storage_t;
-
-
-typedef struct sys_sockaddr_s {
-  sys_sockaddr_storage_t addr;
-  socklen_t len;
-} sys_sockaddr_t;
-
 #ifdef HAS_GETADDRINFO
 typedef struct addrinfo sys_addrinfo_t;
 typedef struct addrinfo* sys_addrinfo_ptr_t;
@@ -170,12 +164,16 @@
 sys_sockaddr_t sys_getaddrinfo_addr(sys_addrinfo_ptr_t a);
 sys_addrinfo_ptr_t sys_getaddrinfo_next(sys_addrinfo_ptr_t a);
 
-void sys_freeaddr_info(sys_addrinfo_ptr_t* p);
+void sys_freeaddrinfo(sys_addrinfo_ptr_t* p);
 
+sys_addrinfo_t sys_init_addrinfo(void);
 
 err_t sys_getnameinfo(const sys_sockaddr_t* addr, char** host_out, char** serv_out, int flags);
 #endif
 
+int sys_sockaddr_family(sys_sockaddr_t addr);
+int sys_sockaddr_port(sys_sockaddr_t addr);
+
 err_t sys_getpeername(fd_t sockfd, sys_sockaddr_t* addr);
 
 
@@ -222,6 +220,9 @@
 // Allocates a string to store the current directory which must be freed.
 err_t sys_getcwd(const char** path_out);
 
+// arpa/inet.h
+err_t sys_inet_ntop(int family, sys_sockaddr_t *addr, const char **str_out);
+
 #ifdef __cplusplus
 } // end extern "C"
 #endif
Index: runtime/src/qio/qio.c
===================================================================
--- runtime/src/qio/qio.c	(revision 22391)
+++ runtime/src/qio/qio.c	(working copy)
@@ -402,8 +402,8 @@
 
   memset(&msg, 0, sizeof(struct msghdr));
   if( src_addr_out ) {
-    msg.msg_name = (void*) &src_addr_out->addr;
-    msg.msg_namelen = src_addr_out->len;
+    msg.msg_name = (void*) src_addr_out;
+    msg.msg_namelen = sizeof(sys_sockaddr_t);
   }
   msg.msg_iov = iov;
   msg.msg_iovlen = num_parts;
@@ -414,7 +414,6 @@
 
   err = sys_recvmsg(sockfd, &msg, flags, &nrecvd);
   if( ! err ) {
-    if( src_addr_out ) src_addr_out->len = msg.msg_namelen;
     if( ancillary_out && ancillary_len_inout ) *ancillary_len_inout = msg.msg_controllen;
   }
 
@@ -461,8 +460,8 @@
 
   memset(&msg, 0, sizeof(struct msghdr));
   if( dst_addr ) {
-    msg.msg_name = (void*) &dst_addr->addr;
-    msg.msg_namelen = dst_addr->len;
+    msg.msg_name = (void*) dst_addr;
+    msg.msg_namelen = sizeof(sys_sockaddr_t);
   }
   msg.msg_iov = iov;
   msg.msg_iovlen = num_parts;
Index: runtime/src/qio/sys.c
===================================================================
--- runtime/src/qio/sys.c	(revision 22391)
+++ runtime/src/qio/sys.c	(working copy)
@@ -42,7 +42,6 @@
 void sys_init_sys_sockaddr(sys_sockaddr_t* addr)
 {
   memset(addr, 0, sizeof(sys_sockaddr_t));
-  addr->len = sizeof(sys_sockaddr_storage_t);
 }
 
 // -------------------  system call wrappers -----------------------------
@@ -1064,16 +1063,15 @@
 {
   int got;
   err_t err_out;
-  socklen_t addr_len = sizeof(sys_sockaddr_storage_t);
+  socklen_t addr_len = sizeof(sys_sockaddr_t);
 
   STARTING_SLOW_SYSCALL;
 
-  got = accept(sockfd, (struct sockaddr*) & addr_out->addr, &addr_len);
+  got = accept(sockfd, (struct sockaddr*) addr_out, &addr_len);
   if( got != -1 ) {
-    if( addr_len > sizeof(sys_sockaddr_storage_t) ) {
+    if( addr_len > sizeof(sys_sockaddr_t) ) {
       fprintf(stderr, "Warning: address truncated in sys_accept\n");
     }
-    addr_out->len = addr_len;
     err_out = 0;
     *fd_out = got;
   } else {
@@ -1091,11 +1089,7 @@
   int got;
   err_t err_out;
 
-  if( addr->len == 0 ) {
-    return EINVAL;
-  }
-
-  got = bind(sockfd, (const struct sockaddr*) & addr->addr, addr->len);
+  got = bind(sockfd, (struct sockaddr*) addr, sizeof(sys_sockaddr_t));
   if( got != -1 ) {
     err_out = 0;
   } else {
@@ -1112,7 +1106,7 @@
 
   STARTING_SLOW_SYSCALL;
 
-  got = connect(sockfd, (const struct sockaddr*) & addr->addr, addr->len);
+  got = connect(sockfd, (struct sockaddr*) addr, sizeof(sys_sockaddr_t));
   if( got != -1 ) {
     err_out = 0;
   } else {
@@ -1163,14 +1157,13 @@
 int sys_getaddrinfo_socktype(sys_addrinfo_ptr_t a) {return a->ai_socktype;}
 int sys_getaddrinfo_protocol(sys_addrinfo_ptr_t a) {return a->ai_protocol;}
 sys_sockaddr_t sys_getaddrinfo_addr(sys_addrinfo_ptr_t a) {
-  sys_sockaddr_t ret;
-  memcpy(&ret.addr, a->ai_addr, a->ai_addrlen);
-  ret.len = a->ai_addrlen;
+  sys_sockaddr_t ret = {0};
+  memcpy(&ret, a->ai_addr, a->ai_addrlen);
   return ret;
 }
 sys_addrinfo_ptr_t sys_getaddrinfo_next(sys_addrinfo_ptr_t a) {return a->ai_next;}
 
-void sys_freeaddr_info(sys_addrinfo_ptr_t *p)
+void sys_freeaddrinfo(sys_addrinfo_ptr_t *p)
 {
   freeaddrinfo(*p);
   *p = NULL;
@@ -1201,7 +1194,7 @@
     host_buf = new_host_buf;
     serv_buf = new_serv_buf;
 
-    got = getnameinfo((const struct sockaddr*) & addr->addr, addr->len, 
+    got = getnameinfo((struct sockaddr*) addr, sizeof(sys_sockaddr_t),
                       host_buf, host_buf_sz,
                       serv_buf, serv_buf_sz,
                       flags);
@@ -1235,12 +1228,26 @@
 
 #endif
 
+int sys_sockaddr_family(sys_sockaddr_t addr)
+{
+  return addr.ss_family;
+}
+
+int sys_sockaddr_port(sys_sockaddr_t addr)
+{
+  switch (addr.ss_family) {
+    case AF_INET:  return ntohs(((struct sockaddr_in*)  &addr)->sin_port);
+    case AF_INET6: return ntohs(((struct sockaddr_in6*) &addr)->sin6_port);
+    default:       return 0;
+  }
+}
+
 err_t sys_getpeername(fd_t sockfd, sys_sockaddr_t* addr)
 {
-  int got;
+  int got, addr_len = sizeof(sys_sockaddr_t);
   err_t err_out;
 
-  got = getpeername(sockfd, (struct sockaddr*) & addr->addr, & addr->len);
+  got = getpeername(sockfd, (struct sockaddr*) addr, & addr_len);
   if( got != -1 ) {
     err_out = 0;
   } else {
@@ -1252,10 +1259,10 @@
 
 err_t sys_getsockname(fd_t sockfd, sys_sockaddr_t* addr)
 {
-  int got;
+  int got, addr_len = sizeof(sys_sockaddr_t);
   err_t err_out;
 
-  got = getsockname(sockfd, (struct sockaddr*) & addr->addr, & addr->len);
+  got = getsockname(sockfd, (struct sockaddr*) addr, & addr_len);
   if( got != -1 ) {
     err_out = 0;
   } else {
@@ -1333,11 +1340,11 @@
 
 err_t sys_recvfrom(fd_t sockfd, void* buf, size_t len, int flags, sys_sockaddr_t* src_addr_out, ssize_t* num_recvd_out)
 {
-  ssize_t got;
+  ssize_t got, addr_len = sizeof(sys_sockaddr_t);
   err_t err_out;
 
   STARTING_SLOW_SYSCALL;
-  got = recvfrom(sockfd, buf, len, flags, (struct sockaddr*) &src_addr_out->addr, & src_addr_out->len);
+  got = recvfrom(sockfd, buf, len, flags, (struct sockaddr*) src_addr_out, & addr_len);
   if( got != -1 ) {
     *num_recvd_out = got;
     err_out = 0;
@@ -1397,7 +1404,7 @@
   err_t err_out;
 
   STARTING_SLOW_SYSCALL;
-  sent = sendto(sockfd, buf, len, flags, (const struct sockaddr*) &dest_addr->addr, dest_addr->len);
+  sent = sendto(sockfd, buf, len, flags, (struct sockaddr*) dest_addr, sizeof(sys_sockaddr_t));
   if( sent != -1 ) {
     *num_sent_out = sent;
     err_out = 0;
@@ -1530,3 +1537,20 @@
   *path_out = buf;
   return err;
 }
+
+err_t sys_inet_ntop(int family, sys_sockaddr_t *addr, const char **str_out)
+{
+  void *addr_ptr;
+
+  switch (family) {
+    case AF_INET : addr_ptr = &((struct sockaddr_in  *) addr)->sin_addr;  break;
+    case AF_INET6: addr_ptr = &((struct sockaddr_in6 *) addr)->sin6_addr; break;
+    default: return EAFNOSUPPORT;
+  }
+
+  char *buf = qio_malloc(INET6_ADDRSTRLEN);
+  if (!buf) return ENOMEM;
+  char *ret = inet_ntop(family, addr_ptr, buf, INET6_ADDRSTRLEN);
+  if (ret) *str_out = ret;
+  return errno;
+}
Index: test/net/bwross/url_parser.chpl
===================================================================
--- test/net/bwross/url_parser.chpl	(revision 0)
+++ test/net/bwross/url_parser.chpl	(working copy)
@@ -0,0 +1,43 @@
+use Net;
+
+proc tryUri(s:string) {
+  var u = new URI(s);
+  writeln(s," => ",new URI(s));
+  if u.scheme != "" then writeln("  scheme = ", u.scheme);
+  if u.user   != "" then writeln("  user   = ", u.user);
+  if u.pass   != "" then writeln("  pass   = ", u.pass);
+  if u.host   != "" then writeln("  host   = ", u.host);
+  if u.port   != "" then writeln("  port   = ", u.port);
+  if u.path   != "" then writeln("  path   = ", u.path);
+  if u.query  != "" then writeln("  query  = ", u.query);
+  if u.fragment!="" then writeln("  frag   = ", u.fragment);
+}
+
+tryUri("http://chapel.cray.com/";);
+tryUri("http://chapel.cray.com/some/page/index.html";);
+tryUri("http://chapel.cray.com:8080/";);
+tryUri("ftp://[feed:2::beef]:123/";);
+tryUri("/some/path/");
+tryUri("http://example.com/?query=something";);
+tryUri("http://example.com/#fragment-is-something";);
+tryUri("http://example.com/?query#fragment";);
+tryUri("http://example.com/#fragment-not?query";);
+tryUri("//lan-host/thing");
+tryUri("mailto:[email protected]");
+tryUri("file:///some/path");
+tryUri("file:/some/path");
+tryUri("file://notdomain/path");
+tryUri("http://domain/path";);
+tryUri("./relative/path");
+tryUri("http://[email protected]/";);
+tryUri("http://user:[email protected]/";);
+tryUri("http://user:pass:with:[email protected]/";);
+tryUri("//user:pass@lan-host:456/");
+tryUri("proto:");
+tryUri("tcp://:80");
+tryUri("http://nopath.com";);
+tryUri("http://yespath.com/";);
+tryUri("http://multipath.com////////////////////";);
+tryUri("http://coloninpath.com/C:/WINDOWS/";);
+tryUri("//coloninpath.com/C:/WINDOWS/");
+tryUri("/C:/WINDOWS/");
Index: url_parser.good
===================================================================
--- url_parser.good	(revision 0)
+++ url_parser.good	(working copy)
@@ -0,0 +1,110 @@
+http://chapel.cray.com/ => http://chapel.cray.com/
+  scheme = http
+  host   = chapel.cray.com
+  path   = /
+http://chapel.cray.com/some/page/index.html => http://chapel.cray.com/some/page/index.html
+  scheme = http
+  host   = chapel.cray.com
+  path   = /some/page/index.html
+http://chapel.cray.com:8080/ => http://chapel.cray.com:8080/
+  scheme = http
+  host   = chapel.cray.com
+  port   = 8080
+  path   = /
+ftp://[feed:2::beef]:123/ => ftp://[feed:2::beef]:123/
+  scheme = ftp
+  host   = feed:2::beef
+  port   = 123
+  path   = /
+/some/path/ => /some/path/
+  path   = /some/path/
+http://example.com/?query=something => http://example.com/?query=something
+  scheme = http
+  host   = example.com
+  path   = /
+  query  = query=something
+http://example.com/#fragment-is-something => http://example.com/#fragment-is-something
+  scheme = http
+  host   = example.com
+  path   = /
+  frag   = fragment-is-something
+http://example.com/?query#fragment => http://example.com/?query#fragment
+  scheme = http
+  host   = example.com
+  path   = /
+  query  = query
+  frag   = fragment
+http://example.com/#fragment-not?query => http://example.com/#fragment-not?query
+  scheme = http
+  host   = example.com
+  path   = /
+  frag   = fragment-not?query
+//lan-host/thing => //lan-host/thing
+  host   = lan-host
+  path   = /thing
+mailto:[email protected] => mailto:[email protected]
+  scheme = mailto
+  path   = [email protected]
+file:///some/path => file:///some/path
+  scheme = file
+  path   = ///some/path
+file:/some/path => file:/some/path
+  scheme = file
+  path   = /some/path
+file://notdomain/path => file://notdomain/path
+  scheme = file
+  path   = //notdomain/path
+http://domain/path => http://domain/path
+  scheme = http
+  host   = domain
+  path   = /path
+./relative/path => ./relative/path
+  path   = ./relative/path
+http://[email protected]/ => http://[email protected]/
+  scheme = http
+  user   = user
+  host   = ftp.example.com
+  path   = /
+http://user:[email protected]/ => http://user:[email protected]/
+  scheme = http
+  user   = user
+  pass   = pass
+  host   = ftp.example.com
+  path   = /
+http://user:pass:with:[email protected]/ => http://user:pass:with:[email protected]/
+  scheme = http
+  user   = user
+  pass   = pass:with:colon
+  host   = ftp.example.com
+  path   = /
+//user:pass@lan-host:456/ => //user:pass@lan-host:456/
+  user   = user
+  pass   = pass
+  host   = lan-host
+  port   = 456
+  path   = /
+proto: => proto:
+  scheme = proto
+tcp://:80 => tcp://:80
+  scheme = tcp
+  port   = 80
+http://nopath.com => http://nopath.com
+  scheme = http
+  host   = nopath.com
+http://yespath.com/ => http://yespath.com/
+  scheme = http
+  host   = yespath.com
+  path   = /
+http://multipath.com//////////////////// => http://multipath.com////////////////////
+  scheme = http
+  host   = multipath.com
+  path   = ////////////////////
+http://coloninpath.com/C:/WINDOWS/ => http://coloninpath.com/C:/WINDOWS/
+  scheme = http
+  host   = coloninpath.com
+  path   = /C:/WINDOWS/
+//coloninpath.com/C:/WINDOWS/ => //coloninpath.com/C:/WINDOWS/
+  host   = coloninpath.com
+  path   = /C:/WINDOWS/
+/C:/WINDOWS/ => /C:/WINDOWS/
+  path   = /C:/WINDOWS/
use Net, Error;

config var port:string = "8080";

writeln("binding to ", port, "...");
var (socket, err) = listen("tcp", "", port);
if !err {
  for (conn, ip, err) in socket do begin {
    writeln("connected: ", ip);
    var r = conn.reader();
    var w = conn.writer();
    var str:string;

    while r.readline(str) && str != "\r\n" && str != "\n" {
      write(ip, ": ", str);
      w.write(str);
      w.flush();
    }

    w.close();
    conn.close();
    writeln("disconnected: ", ip);
  }
} else {
  writeln("could not bind: ", errorToString(err));
}
------------------------------------------------------------------------------
Sponsored by Intel(R) XDK 
Develop, test and display web and hybrid apps with a single code base.
Download it for free now!
http://pubads.g.doubleclick.net/gampad/clk?id=111408631&iu=/4140/ostg.clktrk
_______________________________________________
Chapel-developers mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/chapel-developers

Reply via email to