#
# CDDL HEADER START
#
# The contents of this file are subject to the terms of the
# Common Development and Distribution License (the "License").
# You may not use this file except in compliance with the License.
#
# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
# See the License for the specific language governing permissions
# and limitations under the License.
#
# When distributing Covered Code, include this CDDL HEADER in each
# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
# If applicable, add the following below this CDDL HEADER, with the
# fields enclosed by brackets "[]" replaced with your own identifying
# information: Portions Copyright [yyyy] [name of copyright owner]
#
# CDDL HEADER END
#
#
#
"""
Interfaces to allow us to do RPC over pipes.
The following classes are implemented to allow pipes to be used in place of
file and socket objects:
PipeFile
PipeSocket
The following classes are implemented to allow HTTP client operations over a
pipe:
PipedHTTPResponse
PipedHTTPConnection
The following classes are implemented to allow RPC servers operations
over a pipe:
_PipedServer
_PipedTransport
_PipedHTTPRequestHandler
_PipedRequestHandler
PipedRPCServer
The following classes are implemented to allow RPC clients operations
over a pipe:
PipedServerProxy
RPC clients should be prepared to catch the following exceptions:
ProtocolError1
ProtocolError2
IOError
A RPC server can be implemented as follows:
server = PipedRPCServer(server_pipe_fd)
server.register_introspection_functions()
server.register_function(lambda x,y: x+y, 'add')
server.serve_forever()
A RPC client can be implemented as follows:
client_rpc = PipedServerProxy(client_pipe_fd)
print(client_rpc.add(1, 2))
del client_rpc
"""
import errno
import fcntl
import logging
import os
import socket
import stat
import struct
import sys
import tempfile
import threading
import traceback
# import JSON RPC libraries and objects
#
# These includes make it easier for clients to catch the specific
# exceptions that can be raised by this module.
#
# Unused import; pylint: disable=W0611
# Unused import; pylint: enable=W0611
# jsonrpclib 0.2.6's SimpleJSONRPCServer makes logging calls, but we don't
# configure logging in this file, so we attach a do-nothing handler to it to
# prevent error message being output to sys.stderr.
# debugging
"""Object which makes a pipe look like a "file" object.
Note that all data transmitted via this pipe is transmitted
indirectly. Any data written to or read from the pipe is actually
transmitted via temporary files. For sending data, the data is
written to a temporary file and then the associated file descriptor is
sent via the pipe. For receiving data we try to read a file
descriptor from the pipe and when we get one we return the data from
the temporary file associated with the file descriptor that we just
read. This is done to help ensure that processes don't block while
writing to these pipes (otherwise consumers of these interfaces would
have to create threads to constantly drain data from these pipes to
prevent clients from blocking).
This class also support additional non-file special operations like
sendfd() and recvfd()."""
# Pipes related objects should never live past an exec
"""If debugging is enabled display msg."""
return
if msg is not None:
else:
msg = ""
if self.debug_label is not None:
else:
print("{0}: {1}.{2}({3:d}){4}".format(
"""If debugging is enabled dump the contents of fd."""
return
else:
"""Required to support select.select()."""
"""Read one entire line from the pipe.
Can block waiting for input."""
# read from the fd that we received over the pipe
if data != "":
if self.__http_enc:
# Python 3`http.client`HTTPReponse`_read_status:
# requires a bytes input.
else:
return data
# the fd we received over the pipe is empty
# recieve a file descriptor from the pipe
if fd == -1:
# return data from the received fd
"""Read at most size bytes from the pipe.
Can block waiting for input."""
# read from the fd that we received over the pipe
if data != "":
return data
# the fd we received over the pipe is empty
# recieve a file descriptor from the pipe
if fd == -1:
return ""
# return data from the received fd
# For Python 3: self.fp requires a readinto method.
"""Read up to len(b) bytes into the writable buffer *b* and
return the numbers of bytes read."""
# not-context-manager for py 2.7;
# pylint: disable=E1129
with memoryview(b) as view:
"""Write a string to the pipe."""
# JSON object must be str to be used in jsonrpclib
"""Close the pipe."""
return
"""A NOP since we never do any buffering of data."""
pass
"""Send a file descriptor via the pipe."""
raise IOError(
"sendfd() called for closed {0}".format(
try:
except:
raise
"""Receive a file descriptor via the pipe."""
raise IOError(
"sendfd() called for closed {0}".format(
try:
except IOError as e:
# other end of the connection was closed
return -1
raise e
assert fd != -1
# debugging
# reset the current file pointer
return fd
"""Object which makes a pipe look like a "socket" object."""
"""Return a file-like object associated with this pipe.
The pipe will be duped for this new object so that the object
can be closed and garbage-collected independently."""
# Unused argument; pylint: disable=W0613
"""Receive data from the pipe.
Can block waiting for input."""
# Unused argument; pylint: disable=W0613
"""Send data to the Socket.
Should never really block."""
# Unused argument; pylint: disable=W0613
"""Send data to the pipe.
Should never really block."""
"""Nothing to do here. Move along."""
# Unused argument; pylint: disable=W0613
return
"""set socket opt."""
pass
# pylint seems to be panic about these.
# PipedHTTP: Class has no __init__ method; pylint: disable=W0232
# PipedHTTPResponse.begin: Attribute 'will_close' defined outside __init__;
# pylint: disable=W0201
"""Create a httplib.HTTPResponse like object that can be used with
a pipe as a transport. We override the minimum number of parent
routines necessary."""
"""Our connection will never be automatically closed, so set
will_close to False."""
return
"""Create a httplib.HTTPConnection like object that can be used with
a pipe as a transport. We override the minimum number of parent
routines necessary."""
# we use PipedHTTPResponse in place of httplib.HTTPResponse
assert port is None
# invoke parent constructor
# self.sock was initialized by httplib.HTTPConnection
# to point to a socket, overwrite it with a pipe.
# make sure the destructor gets called for our pipe
"""Close our pipe fd."""
"""Required to support select()."""
"""Create a Transport object which can create new PipedHTTP
connections via an existing pipe."""
# This is a workaround to cope with the jsonrpclib update
# (version 0.2.6) more safely. Once jsonrpclib is out in
# the OS build, we can change it to always pass a 'config'
# argument to __init__.
else:
self._extra_headers = None
# make sure the destructor gets called for our connection
if self.__pipe_file is not None:
"""Close the pipe associated with this transport."""
self.__pipe_file = None
"""Create a new PipedHTTP connection to the server. This
involves creating a new pipe, and sending one end of the pipe
to the server, and then wrapping the local end of the pipe
with a PipedHTTPConnection object. This object can then be
subsequently used to issue http requests."""
# Redefining name from outer scope; pylint: disable=W0621
assert self.__pipe_file is not None
if self.__http_enc:
# we're using http encapsulation so return a
# PipedHTTPConnection object
return PipedHTTPConnection(client_pipefd)
# we're not using http encapsulation so return a
# PipeSocket object
"""Send a request to the server."""
if self.__http_enc:
# we're using http encapsulation so just pass the
# request to our parent class.
c.send(request_body)
"""Modeled after SocketServer.TCPServer."""
server_address="localhost",
"""Required to support select.select()."""
"""Trigger a shutdown of the RPC server. This is done via a
separate thread since the shutdown() entry point is
non-reentrant."""
return
"""Shutdown the server thread."""
t.start()
"""Get a request from the client. Returns a tuple containing
the request and the client address (mirroring the return value
from self.socket.accept())."""
if fd == -1:
"""Piped RPC request handler that uses HTTP encapsulation."""
"""Prepare to handle a request."""
# StreamRequestHandler will have duped our PipeSocket via
# makefile(), so close the connection socket here.
return rv
"""Piped RPC request handler that doesn't use HTTP encapsulation."""
"""Handle one client request."""
response = ""
try:
# Access to protected member; pylint: disable=W0212
# No exception type specified; pylint: disable=W0702
except:
# The server had an unexpected exception.
# dump the error to stderr
# Return the error to the caller.
# tell the server to exit
"""Modeled after SimpleRPCServer. Differs in that
SimpleRPCServer is derived from SocketServer.TCPServer but we're
derived from _PipedServer."""
if not http_enc:
"""Check if a response is actually a fault object. If so
then it's time to die."""
return
# server encountered an error, time for seppuku
"""Check for unexpected server exceptions while handling a
request."""
# pylint: disable=W0221
# Arguments differ from overridden method;
return response
"""Check for unexpected server exceptions while handling a
request."""
# pylint: disable=W0221
# Arguments differ from overridden method;
return response
"""Check for unexpected server exceptions while handling a
request."""
# pylint: disable=W0221
# Arguments differ from overridden method;
return response
"""Create a ServerProxy object that can be used to make calls to
an RPC server on the other end of a pipe."""