https://github.com/akkartik/mu/blob/master/091socket.cc

      1 :(before "End Types")
      2 struct socket_t {
      3   int fd;
      4   sockaddr_in addr;
      5   bool polled;
      6   socket_t() {
      7     fd = 0;
      8     polled = false;
      9     bzero(&addr, sizeof(addr));
     10   }
     11 };
     12
     13 :(before "End Primitive Recipe Declarations")
     14 _OPEN_CLIENT_SOCKET,
     15 :(before "End Primitive Recipe Numbers")
     16 put(Recipe_ordinal, "$open-client-socket", _OPEN_CLIENT_SOCKET);
     17 :(before "End Primitive Recipe Checks")
     18 case _OPEN_CLIENT_SOCKET: {
     19   if (SIZE(inst.ingredients) != 2) {
     20     raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly two ingredients, but got '" << to_original_string(inst) << "'\n" << end();
     21     break;
     22   }
     23   if (!is_mu_text(inst.ingredients.at(0))) {
     24     raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-client-socket' should be text (the hostname), but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
     25     break;
     26   }
     27   if (!is_mu_number(inst.ingredients.at(1))) {
     28     raise << maybe(get(Recipe, r).name) << "second ingredient of '$open-client-socket' should be a number (the port of the hostname to connect to), but got '" << to_string(inst.ingredients.at(1)) << "'\n" << end();
     29     break;
     30   }
     31   if (SIZE(inst.products) != 1) {
     32     raise << maybe(get(Recipe, r).name) << "'$open-client-socket' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
     33     break;
     34   }
     35   if (!is_mu_number(inst.products.at(0))) {
     36     raise << maybe(get(Recipe, r).name) << "first product of '$open-client-socket' should be a number (socket handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end();
     37     break;
     38   }
     39   break;
     40 }
     41 :(before "End Primitive Recipe Implementations")
     42 case _OPEN_CLIENT_SOCKET: {
     43   string host = read_mu_text(ingredients.at(0).at(/*skip alloc id*/1));
     44   int port = ingredients.at(1).at(0);
     45   socket_t* client = client_socket(host, port);
     46   products.resize(1);
     47   if (client->fd < 0) {  // error
     48     delete client;
     49     products.at(0).push_back(0);
     50     break;
     51   }
     52   long long int result = reinterpret_cast<long long int>(client);
     53   products.at(0).push_back(static_cast<double>(result));
     54   break;
     55 }
     56 :(code)
     57 socket_t* client_socket(const string& host, int port) {
     58   socket_t* result = new socket_t;
     59   result->fd = socket(AF_INET, SOCK_STREAM, 0);
     60   if (result->fd < 0) {
     61     raise << "Failed to create socket.\n" << end();
     62     return result;
     63   }
     64   result->addr.sin_family = AF_INET;
     65   hostent* tmp = gethostbyname(host.c_str());
     66   bcopy(tmp->h_addr, reinterpret_cast<char*>(&result->addr.sin_addr.s_addr), tmp->h_length);
     67   result->addr.sin_port = htons(port);
     68   if (connect(result->fd, reinterpret_cast<sockaddr*>(&result->addr), sizeof(result->addr)) < 0) {
     69     close(result->fd);
     70     result->fd = -1;
     71     raise << "Failed to connect to " << host << ':' << port << '\n' << end();
     72   }
     73   return result;
     74 }
     75
     76 :(before "End Primitive Recipe Declarations")
     77 _OPEN_SERVER_SOCKET,
     78 :(before "End Primitive Recipe Numbers")
     79 put(Recipe_ordinal, "$open-server-socket", _OPEN_SERVER_SOCKET);
     80 :(before "End Primitive Recipe Checks")
     81 case _OPEN_SERVER_SOCKET: {
     82   if (SIZE(inst.ingredients) != 1) {
     83     raise << maybe(get(Recipe, r).name) << "'$open-server-socket' requires exactly one ingredient (the port to listen for requests on), but got '" << to_original_string(inst) << "'\n" << end();
     84     break;
     85   }
     86   if (!is_mu_number(inst.ingredients.at(0))) {
     87     raise << maybe(get(Recipe, r).name) << "first ingredient of '$open-server-socket' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
     88     break;
     89   }
     90   if (SIZE(inst.products) != 1) {
     91     raise << maybe(get(Recipe, r).name) << "'$open-server-socket' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
     92     break;
     93   }
     94   if (!is_mu_number(inst.products.at(0))) {
     95     raise << maybe(get(Recipe, r).name) << "first product of '$open-server-socket' should be a number (file handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end();
     96     break;
     97   }
     98   break;
     99 }
    100 :(before "End Primitive Recipe Implementations")
    101 case _OPEN_SERVER_SOCKET: {
    102   int port = ingredients.at(0).at(0);
    103   socket_t* server = server_socket(port);
    104   products.resize(1);
    105   if (server->fd < 0) {
    106     delete server;
    107     products.at(0).push_back(0);
    108     break;
    109   }
    110   long long int result = reinterpret_cast<long long int>(server);
    111   products.at(0).push_back(static_cast<double>(result));
    112   break;
    113 }
    114 :(code)
    115 socket_t* server_socket(int port) {
    116   socket_t* result = new socket_t;
    117   result->fd = socket(AF_INET, SOCK_STREAM, 0);
    118   if (result->fd < 0) {
    119     raise << "Failed to create server socket.\n" << end();
    120     return result;
    121   }
    122   int dummy = 0;
    123   setsockopt(result->fd, SOL_SOCKET, SO_REUSEADDR, &dummy, sizeof(dummy));
    124   result->addr.sin_family = AF_INET;
    125   result->addr.sin_addr.s_addr = Current_scenario ? htonl(INADDR_LOOPBACK) : INADDR_ANY;  // run tests without running afoul of any firewall
    126   result->addr.sin_port = htons(port);
    127   if (bind(result->fd, reinterpret_cast<sockaddr*>(&result->addr), sizeof(result->addr)) >= 0) {
    128     listen(result->fd, /*queue length*/5);
    129   }
    130   else {
    131     close(result->fd);
    132     result->fd = -1;
    133     raise << "Failed to bind result socket to port " << port << ". Something's already using that port.\n" << end();
    134   }
    135   return result;
    136 }
    137
    138 :(before "End Primitive Recipe Declarations")
    139 _ACCEPT,
    140 :(before "End Primitive Recipe Numbers")
    141 put(Recipe_ordinal, "$accept", _ACCEPT);
    142 :(before "End Primitive Recipe Checks")
    143 case _ACCEPT: {
    144   if (SIZE(inst.ingredients) != 1) {
    145     raise << maybe(get(Recipe, r).name) << "'$accept' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
    146     break;
    147   }
    148   if (!is_mu_number(inst.ingredients.at(0))) {
    149     raise << maybe(get(Recipe, r).name) << "first ingredient of '$accept' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
    150     break;
    151   }
    152   if (SIZE(inst.products) != 1) {
    153     raise << maybe(get(Recipe, r).name) << "'$accept' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
    154     break;
    155   }
    156   if (!is_mu_number(inst.products.at(0))) {
    157     raise << maybe(get(Recipe, r).name) << "first product of '$accept' should be a number (file handle), but got '" << to_string(inst.products.at(0)) << "'\n" << end();
    158     break;
    159   }
    160   break;
    161 }
    162 :(before "End Primitive Recipe Implementations")
    163 case _ACCEPT: {
    164   products.resize(2);
    165   products.at(1).push_back(ingredients.at(0).at(0));  // indicate it modifies its ingredient
    166   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    167   socket_t* server = reinterpret_cast<socket_t*>(x);
    168   if (server) {
    169     socket_t* session = accept_session(server);
    170     long long int result = reinterpret_cast<long long int>(session);
    171     products.at(0).push_back(static_cast<double>(result));
    172   }
    173   else {
    174     products.at(0).push_back(0);
    175   }
    176   break;
    177 }
    178 :(code)
    179 socket_t* accept_session(socket_t* server) {
    180   if (server->fd == 0) return NULL;
    181   socket_t* result = new socket_t;
    182   socklen_t dummy = sizeof(result->addr);
    183   result->fd = accept(server->fd, reinterpret_cast<sockaddr*>(&result->addr), &dummy);
    184   return result;
    185 }
    186
    187 :(before "End Primitive Recipe Declarations")
    188 _READ_FROM_SOCKET,
    189 :(before "End Primitive Recipe Numbers")
    190 put(Recipe_ordinal, "$read-from-socket", _READ_FROM_SOCKET);
    191 :(before "End Primitive Recipe Checks")
    192 case _READ_FROM_SOCKET: {
    193   if (SIZE(inst.ingredients) != 1) {
    194     raise << maybe(get(Recipe, r).name) << "'$read-from-socket' requires exactly one ingredient, but got '" << to_original_string(inst) << "'\n" << end();
    195     break;
    196   }
    197   if (!is_mu_number(inst.ingredients.at(0))) {
    198     raise << maybe(get(Recipe, r).name) << "first ingredient of '$read-from-socket' should be a number (socket), but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
    199     break;
    200   }
    201   int nprod = SIZE(inst.products);
    202   if (nprod == 0 || nprod > 4) {
    203     raise << maybe(get(Recipe, r).name) << "'$read-from-socket' requires 1-4 products, but got '" << to_original_string(inst) << "'\n" << end();
    204     break;
    205   }
    206   if (!is_mu_character(inst.products.at(0))) {
    207     raise << maybe(get(Recipe, r).name) << "first product of '$read-from-socket' should be a character, but got '" << to_string(inst.products.at(0)) << "'\n" << end();
    208     break;
    209   }
    210   if (nprod > 1 && !is_mu_boolean(inst.products.at(1))) {
    211     raise << maybe(get(Recipe, r).name) << "second product of '$read-from-socket' should be a boolean (data received?), but got '" << to_string(inst.products.at(1)) << "'\n" << end();
    212     break;
    213   }
    214   if (nprod > 2 && !is_mu_boolean(inst.products.at(2))) {
    215     raise << maybe(get(Recipe, r).name) << "third product of '$read-from-socket' should be a boolean (eof?), but got '" << to_string(inst.products.at(2)) << "'\n" << end();
    216     break;
    217   }
    218   if (nprod > 3 && !is_mu_number(inst.products.at(3))) {
    219     raise << maybe(get(Recipe, r).name) << "fourth product of '$read-from-socket' should be a number (error code), but got '" << to_string(inst.products.at(3)) << "'\n" << end();
    220     break;
    221   }
    222   break;
    223 }
    224 :(before "End Primitive Recipe Implementations")
    225 case _READ_FROM_SOCKET: {
    226   products.resize(4);
    227   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    228   socket_t* socket = reinterpret_cast<socket_t*>(x);
    229   // 1. we'd like to simply read() from the socket
    230   // however read() on a socket never returns EOF, so we wouldn't know when to stop
    231   // 2. recv() can signal EOF, but it also signals "no data yet" in the beginning
    232   // so use poll() in the beginning to wait for data before calling recv()
    233   // 3. but poll() will block on EOF, so only use poll() on the very first
    234   // $read-from-socket on a socket
    235   //
    236   // Also, there was an unresolved issue where attempts to read() a small
    237   // number of bytes (less than 447 on Linux and Mac) would cause browsers to
    238   // prematurely close the connection. See commit 3403. That seems to be gone
    239   // after moving to recv()+poll(). It was never observed on OpenBSD.
    240   if (!socket->polled) {
    241     pollfd p;
    242     bzero(&p, sizeof(p));
    243     p.fd = socket->fd;
    244     p.events = POLLIN | POLLHUP;
    245     int poll_result = poll(&p, /*num pollfds*/1, /*timeout*/100/*ms*/);
    246     if (poll_result == 0) {
    247       products.at(0).push_back(/*no data*/0);
    248       products.at(1).push_back(/*found*/false);
    249       products.at(2).push_back(/*eof*/false);
    250       products.at(3).push_back(/*error*/0);
    251       break;
    252     }
    253     else if (poll_result < 0) {
    254       int error_code = errno;
    255       raise << maybe(current_recipe_name()) << "error in $read-from-socket\n" << end();
    256       products.at(0).push_back(/*no data*/0);
    257       products.at(1).push_back(/*found*/false);
    258       products.at(2).push_back(/*eof*/false);
    259       products.at(3).push_back(error_code);
    260       break;
    261     }
    262     socket->polled = true;
    263   }
    264   char c = '\0';
    265   int error_code = 0;
    266   int bytes_read = recv(socket->fd, &c, /*single byte*/1, MSG_DONTWAIT);
    267   if (bytes_read < 0) error_code = errno;
    268 //?   if (error_code) {
    269 //?     ostringstream out;
    270 //?     out << "error in $read-from-socket " << socket->fd;
    271 //?     perror(out.str().c_str());
    272 //?   }
    273   products.at(0).push_back(c);
    274   products.at(1).push_back(/*found*/true);
    275   products.at(2).push_back(/*eof*/bytes_read <= 0);
    276   products.at(3).push_back(error_code);
    277   break;
    278 }
    279
    280 :(before "End Primitive Recipe Declarations")
    281 _WRITE_TO_SOCKET,
    282 :(before "End Primitive Recipe Numbers")
    283 put(Recipe_ordinal, "$write-to-socket", _WRITE_TO_SOCKET);
    284 :(before "End Primitive Recipe Checks")
    285 case _WRITE_TO_SOCKET: {
    286   if (SIZE(inst.ingredients) != 2) {
    287     raise << maybe(get(Recipe, r).name) << "'$write-to-socket' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end();
    288     break;
    289   }
    290   break;
    291 }
    292 :(before "End Primitive Recipe Implementations")
    293 case _WRITE_TO_SOCKET: {
    294   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    295   socket_t* socket = reinterpret_cast<socket_t*>(x);
    296   // write just one character at a time to the socket
    297   long long int y = static_cast<long long int>(ingredients.at(1).at(0));
    298   char c = static_cast<char>(y);
    299   if (write(socket->fd, &c, 1) != 1) {
    300     raise << maybe(current_recipe_name()) << "failed to write to socket\n" << end();
    301     exit(0);
    302   }
    303   products.resize(1);
    304   products.at(0).push_back(ingredients.at(0).at(0));
    305   break;
    306 }
    307
    308 :(before "End Primitive Recipe Declarations")
    309 _CLOSE_SOCKET,
    310 :(before "End Primitive Recipe Numbers")
    311 put(Recipe_ordinal, "$close-socket", _CLOSE_SOCKET);
    312 :(before "End Primitive Recipe Checks")
    313 case _CLOSE_SOCKET: {
    314   if (SIZE(inst.ingredients) != 1) {
    315     raise << maybe(get(Recipe, r).name) << "'$close-socket' requires exactly two ingredient, but got '" << to_original_string(inst) << "'\n" << end();
    316     break;
    317   }
    318   if (!is_mu_number(inst.ingredients.at(0))) {
    319     raise << maybe(get(Recipe, r).name) << "first ingredient of '$close-socket' should be a number, but got '" << to_string(inst.ingredients.at(0)) << "'\n" << end();
    320     break;
    321   }
    322   if (SIZE(inst.products) != 1) {
    323     raise << maybe(get(Recipe, r).name) << "'$close-socket' requires exactly one product, but got '" << to_original_string(inst) << "'\n" << end();
    324     break;
    325   }
    326   if (inst.products.at(0).name != inst.ingredients.at(0).name) {
    327     raise << maybe(get(Recipe, r).name) << "product of '$close-socket' must be first ingredient '" << inst.ingredients.at(0).original_string << "', but got '" << inst.products.at(0).original_string << "'\n" << end();
    328     break;
    329   }
    330   break;
    331 }
    332 :(before "End Primitive Recipe Implementations")
    333 case _CLOSE_SOCKET: {
    334   long long int x = static_cast<long long int>(ingredients.at(0).at(0));
    335   socket_t* socket = reinterpret_cast<socket_t*>(x);
    336   close(socket->fd);
    337   delete socket;
    338   products.resize(1);
    339   products.at(0).push_back(0);  // make sure we can't reuse the socket
    340   break;
    341 }
    342
    343 :(before "End Includes")
    344 #include <netinet/in.h>
    345 #include <netdb.h>
    346 #include <poll.h>
    347 #include <sys/socket.h>
    348 #include <unistd.h>