# $HeadURL$ $LastChangedRevision$ # Name of this package package JS; # Packages required by this package use strict; use warnings; use ADE; use JSON; use Socket; use Fatal qw( close unlink ); # obviate checking close()'s return code # Declare exports BEGIN { use base qw( Exporter ); our @EXPORT_OK = qw(init_server get_server_name send_message_hash_and_read_reply_hash read_message_str_and_send_reply_str %js_defined_errors); } END { } # Instantiate exported data # Initialise exported data # Instantiate private data my(%js_defined_errors); # Initialise private data %js_defined_errors = ( js_err_misc => { fmt => '%s' }, ); BEGIN { ADE::register_error_types(\%js_defined_errors); } sub get_server_address { my($errstack_ref, $server_name, $server_address_ref) = @_; ${$server_address_ref} = sprintf '/tmp/js-%s.sock', $server_name; return($ADE::OK); } sub get_server_name { my($errstack_ref, $server_name_ref) = @_; if (defined $ENV{'JS_NAME'}) { ${$server_name_ref} = $ENV{'JS_NAME'}; } elsif (defined $ENV{'LOGNAME'}) { ${$server_name_ref} = $ENV{'LOGNAME'}; } else { ADE::error($errstack_ref, 'js_err_misc', 'neither LOGNAME nor JS_NAME are set'); return($ADE::FAIL); } return($ADE::OK); } sub init_server { my($errstack_ref, $server_name, $server_handle_ref) = @_; my($server_address, $rc); if (($rc=JS::get_server_address($errstack_ref, $server_name, \$server_address)) != $ADE::OK) { return($rc); } # Create an address-less socket ADE::debug($errstack_ref, 10, 'init_server: calling socket() ...'); if (!socket ${$server_handle_ref}, Socket::PF_UNIX, Socket::SOCK_STREAM, 0) { ADE::error($errstack_ref, 'js_err_misc', 'socket() failed'); return($ADE::FAIL); } # Tie socket to an address ADE::debug($errstack_ref, 10, 'init_server: calling bind() ...'); unlink $server_address if (-S $server_address); if (!bind ${$server_handle_ref}, pack_sockaddr_un($server_address)) { ADE::error($errstack_ref, 'js_err_misc', 'bind() failed'); return($ADE::FAIL); } # Mark socket as a server socket ADE::debug($errstack_ref, 10, 'init_server: calling listen() ...'); if (!listen ${$server_handle_ref}, Socket::SOMAXCONN) { ADE::error($errstack_ref, 'js_err_misc', 'listen() failed'); return($ADE::FAIL); } ADE::debug($errstack_ref, 10, 'init_server: returning ...'); return($ADE::OK); } # Client sends message and awaits reply sub send_message_hash_and_read_reply_hash { my($errstack_ref, $server_name, $message_hash_ref, $reply_hash_ref) = @_; my($server_address, $client_handle, $rc, $serialised_reply, $serialised_message); # Get address (i.e. socket pathname) on which server should be listening if (($rc=get_server_address($errstack_ref, $server_name, \$server_address)) != $ADE::OK) { return($rc); } # Create an address-less socket ADE::debug($errstack_ref, 10, 'send_message_hash_and_read_reply_hash: calling socket() ...'); if (!socket $client_handle, Socket::PF_UNIX, Socket::SOCK_STREAM, 0) { ADE::error($errstack_ref, 'js_err_misc', 'socket() failed'); return($ADE::FAIL); } # Tell address-less socket to connect to the thing the server is listening on. ADE::debug($errstack_ref, 10, 'send_message_hash_and_read_reply_hash: calling connect() ...'); if (!connect $client_handle, pack_sockaddr_un($server_address)) { ADE::error($errstack_ref, 'js_err_misc', 'connect() failed'); return($ADE::FAIL); } $client_handle->autoflush(1); # Serialise message hash into JSON string and send $serialised_message = JSON->new->encode($message_hash_ref); ADE::debug($errstack_ref, 4, "send_message_hash_and_read_reply_hash: sending serialised message: $serialised_message"); print $client_handle $serialised_message . "\n"; # Read reply and deserialise JSON string into reply hash $serialised_reply = <$client_handle>; if (not defined $serialised_reply) { ADE::error($errstack_ref, 'js_err_misc', 'failed to read reply (hint: did jsd die?)'); return($ADE::FAIL); } chomp $serialised_reply; ADE::debug($errstack_ref, 4, "send_message_hash_and_read_reply_hash: received serialised reply: $serialised_reply"); %{$reply_hash_ref} = %{ JSON->new->decode($serialised_reply) }; # Close connection and return close $client_handle; return($ADE::OK); } sub read_message_str_and_send_reply_str { my($errstack_ref, $server_handle, $callback, $callback_args_ref) = @_; my ($client_handle, $serialised_message, $serialised_reply, $rc); my (%message_hash, %reply_hash); # Await incoming connection if ok handle below. if (accept $client_handle, $server_handle) { # If interrupted (by SIGALRM or SIGCHLD) then fine, return without doing anything; # there is something to do in the calling scope. } elsif ($!{'EINTR'}) { return($ADE::OK); # If accept *really* failed then error. } else { ADE::error($errstack_ref, 'js_err_misc', 'accept() failed'); return($ADE::FAIL); } # Read message and deserialise JSON string into message hash chomp($serialised_message = <$client_handle>); ADE::debug($errstack_ref, 4, "read_message_str_and_send_reply_str: received serialised message: $serialised_message"); %message_hash = %{ JSON->new->decode($serialised_message) }; # Call callback with incoming message hash and arbitrary parameter list; return value will be reply hash ADE::debug($errstack_ref, 10, 'read_message_str_and_send_reply_str: calling backback ...'); if (($rc=&{$callback}($errstack_ref, $callback_args_ref, \%message_hash, \%reply_hash)) != $ADE::OK) { return($rc); } # Serialise reply hash into JSON string and send $serialised_reply = JSON->new->encode(\%reply_hash); ADE::debug($errstack_ref, 4, "read_message_str_and_send_reply_str: sending serialised reply: $serialised_reply"); print $client_handle $serialised_reply . "\n"; # Close connection and return close $client_handle; return($ADE::OK); } ############################################################################## # # NON-ZERO MODULE EXIT CODE # ############################################################################## 1;