The protobuf proxy prototype didn't exist in the previous proxy branch for some reason. It's currently broken and needs to be rewritten to work with the python restructuring from the last release. So currently not a working version (but one that used to kinda work to some degree with some old version).

This commit is contained in:
Benjamin Zeiss 2022-02-18 00:40:55 +01:00
parent 419dca3c4c
commit bab1556a4a
15 changed files with 559 additions and 0 deletions

View File

@ -0,0 +1,6 @@
automount_directory=/home/pi/automount
use_sudo=True
sudo=/usr/bin/sudo
mount=/usr/bin/mount
umount=/usr/bin/umount
automount_extension=.automount

View File

@ -0,0 +1,120 @@
from yapsy.IPlugin import IPlugin
from pathlib import Path
import configparser
import os
import subprocess
class RaProxyAutomountPlugin(IPlugin):
def detach(self, scsi_id, type, path):
file = os.path.basename(str(path))
automount_path = Path(self.config['main']['automount_directory']).joinpath(file)
automount_image_config_file = str(path) + self.config['main']['automount_extension']
if not Path(automount_image_config_file).is_file():
print("RASCSI proxy automount image config file [" + automount_image_config_file + "] not found. Skipping "
"mount action.")
return
automount_image_config = configparser.ConfigParser()
with open(automount_image_config_file) as stream:
automount_image_config.read_string("[main]\n" + stream.read())
if not automount_image_config.has_option("main","mount_options"):
print("RASCSI proxy automount image config file is invalid and/or does not have a valid key ["
"mount_options]. Skipping mount action.")
return
if not automount_path.exists():
try:
automount_path.mkdir(parents=True, exist_ok=True)
except Exception as e:
print(e)
try:
if self.config['main']['use_sudo']:
command = [self.config['main']['sudo'],self.config['main']['mount'], path, str(f'{automount_path}/')]
# print(command)
return_code = subprocess.call(command)
else:
command = [self.config['main']['mount'], path, str(f'{automount_path}/')]
# print(command)
return_code = subprocess.call(command)
# print("return code: " + str(return_code))
if return_code != 0:
raise Exception('mounting ' + str(path) + ' failed on detach command. Unknown state!')
print("RASCSI proxy automount detach/eject action: image [" + str(path) + "] has been mounted to [" + str(f'{automount_path}/') + "].")
except Exception as e:
print(e)
def attach(self, scsi_id, type, path):
file = os.path.basename(str(path))
automount_path = Path(self.config['main']['automount_directory']).joinpath(file)
if not automount_path.exists():
print("RASCSI proxy automount image mount directory [" + str(automount_path) + "] not found. Probably not "
"mounted. Skipping unmount"
" action.")
return
if len(os.listdir(str(automount_path)) ) > 0:
try:
if self.config['main']['use_sudo']:
command = [self.config['main']['sudo'],self.config['main']['umount'],'-l', str(f'{automount_path}/')]
# print(command)
return_code = subprocess.call(command)
else:
command = [self.config['main']['umount'],'-l', str(f'{automount_path}/')]
# print(command)
return_code = subprocess.call(command)
# print("return code: " + str(return_code))
if return_code != 0:
raise Exception('Umounting ' + str(path) + ' failed on attach command. Unknown state!')
print("RASCSI proxy automount attach/insert action: image [" + str(path) + "] has been unmounted from [" + str(f'{automount_path}/') + "].")
except Exception as e:
print(e)
try:
automount_path.rmdir()
except Exception as e:
print(e)
def init_hook(self):
print("RASCSI proxy automount plugin: init")
self.config = configparser.ConfigParser()
with open('./raproxy_plugins/automount.ini') as stream:
self.config.read_string("[main]\n" + stream.read())
path = Path(self.config['main']['automount_directory'])
if not path.exists():
path.mkdir(parents=True, exist_ok=True)
def detach_hook(self, scsi_id, type, path):
self.detach(scsi_id, type, path)
# print("automount path: " + str(path))
# print("detaching from scsi id: " + str(scsi_id))
# print("detaching file type : " + str(type))
# print("detaching file : " + str(path))
def eject_hook(self, scsi_id, type, path):
self.detach(scsi_id, type, path)
# print("automount path: " + str(path))
# print("ejecting from scsi id : " + str(scsi_id))
# print("ejecting file type : " + str(type))
# print("ejecting file : " + str(path))
def attach_hook(self, scsi_id, type, path):
self.attach(scsi_id, type, path)
# print("automount path: " + str(path))
# print("attaching on scsi id : " + str(scsi_id))
# print("attaching file type : " + str(type))
# print("attaching file : " + str(path))
def insert_hook(self, scsi_id, type, path):
self.attach(scsi_id, type, path)
# print("automount path: " + str(path))
# print("inserting on scsi id : " + str(scsi_id))
# print("inserting file type : " + str(type))
# print("inserting file : " + str(path))

View File

@ -0,0 +1,10 @@
[Core]
Name = RASCSI proxy automount plugin
Module = automount
[Documentation]
Author = RASCSI Contributors
Version = 0.1
Website = https://github.com/akuker/RASCSI
Description = RASCSI proxy automount plugin

View File

@ -0,0 +1,22 @@
from yapsy.IPlugin import IPlugin
class RaProxyEchoPlugin(IPlugin):
def init_hook(self):
print("RASCSI proxy echo plugin: init")
def detach_hook(self, scsi_id, type, path):
print("detaching from scsi id: " + str(scsi_id))
print("detaching file type : " + str(type))
print("detaching file : " + str(path))
def eject_hook(self, scsi_id, type, path):
print("ejecting from scsi id : " + str(scsi_id))
print("ejecting file type : " + str(type))
print("ejecting file : " + str(path))
def attach_hook(self, scsi_id, type, path):
print("attaching on scsi id : " + str(scsi_id))
print("attaching file type : " + str(type))
print("attaching file : " + str(path))

View File

@ -0,0 +1,10 @@
[Core]
Name = RASCSI proxy echo plugin
Module = echo
[Documentation]
Author = RASCSI Contributors
Version = 0.1
Website = https://github.com/akuker/RASCSI
Description = RASCSI proxy echo plugin

View File

@ -0,0 +1,11 @@
#!/bin/sh
scsi_id=$1
type=$2
path=$3
echo "shell script: attaching from scsi id: " ${scsi_id}
echo "shell script: attaching file type : " ${type}
echo "shell script: attaching file : " ${path}
exit 0

View File

@ -0,0 +1,11 @@
#!/bin/sh
scsi_id=$1
type=$2
path=$3
echo "shell script: detaching from scsi id: " ${scsi_id}
echo "shell script: detaching file type : " ${type}
echo "shell script: detaching file : " ${path}
exit 0

View File

@ -0,0 +1,11 @@
#!/bin/sh
scsi_id=$1
type=$2
path=$3
echo "shell script: ejecting from scsi id : " ${scsi_id}
echo "shell script: ejecting file type : " ${type}
echo "shell script: ejecting file : " ${path}
exit 0

View File

@ -0,0 +1,9 @@
#!/bin/sh
scsi_id=$1
type=$2
path=$3
echo "RASCSI proxy shellscript plugin: init"
exit 0

View File

@ -0,0 +1,17 @@
from yapsy.IPlugin import IPlugin
import subprocess
class RaProxyShellscriptPlugin(IPlugin):
def init_hook(self):
subprocess.call(['./raproxy_plugins/raproxy_init.sh'])
def detach_hook(self, scsi_id, type, path):
subprocess.call(['./raproxy_plugins/raproxy_detach.sh',str(scsi_id),str(type),str(path)])
def eject_hook(self, scsi_id, type, path):
subprocess.call(['./raproxy_plugins/raproxy_eject.sh',str(scsi_id),str(type),str(path)])
def attach_hook(self, scsi_id, type, path):
subprocess.call(['./raproxy_plugins/raproxy_attach.sh',str(scsi_id),str(type),str(path)])

View File

@ -0,0 +1,10 @@
[Core]
Name = RASCSI Proxy shell script plugin
Module = shellscript
[Documentation]
Author = RASCSI Contributors
Version = 0.1
Website = https://github.com/akuker/RASCSI
Description = RASCSI Proxy Shellscript Plugin

View File

@ -0,0 +1,224 @@
#! /usr/bin/python
import rascsi_interface_pb2 as proto
import socket
import socket_cmds
import time
from struct import pack, unpack
from yapsy.PluginManager import PluginManager
#########################
def send_pb_command(payload, host='localhost', port=6868):
"""
Takes a (str) containing a serialized protobuf as argument.
Establishes a socket connection with RaSCSI.
"""
counter = 0
tries = 20
error_msg = ""
import socket
while counter < tries:
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
sock.connect((host, port))
return socket_cmds.send_over_socket(sock, payload)
except socket.error as error:
counter += 1
logging.warning("The RaSCSI service is not responding - attempt %s/%s",
str(counter), str(tries))
error_msg = str(error)
sleep(0.2)
logging.error(error_msg)
# After failing all attempts, throw a 404 error
abort(404, _(
u"The RaSCSI Web Interface failed to connect to RaSCSI at %(host)s:%(port)s "
u"with error: %(error_msg)s. The RaSCSI process is not running or may have crashed.",
host=host, port=port, error_msg=error_msg,
)
)
#########################
def get_image_files_info(host='localhost',port=6868):
"""
Sends a DEFAULT_IMAGE_FILES_INFO command to the server.
Returns a dict with:
- (bool) status
- (str) images_dir, path to images dir
- (list) of (str) image_files
- (int) scan_depth, the current scan depth
"""
command = proto.PbCommand()
command.operation = proto.PbOperation.DEFAULT_IMAGE_FILES_INFO
data = send_pb_command(command.SerializeToString(),host,port)
result = proto.PbResult()
result.ParseFromString(data)
images_dir = result.image_files_info.default_image_folder
image_files = result.image_files_info.image_files
scan_depth = result.image_files_info.depth
return {
"status": result.status,
"images_dir": images_dir,
"image_files": image_files,
"scan_depth": scan_depth,
}
#########################
def socket_read_n(conn, n):
""" Read exactly n bytes from the socket.
Raise RuntimeError if the connection closed before
n bytes were read.
"""
buf = bytearray(b'')
while n > 0:
data = conn.recv(n)
if data == '':
raise RuntimeError('unexpected connection close')
buf.extend(data)
n -= len(data)
return bytes(buf)
#########################
def deserialize_message(conn, message):
# read the header with the size of the protobuf data
buf = socket_read_n(conn, 4)
size = (int(buf[3]) << 24) + (int(buf[2]) << 16) + (int(buf[1]) << 8) + int(buf[0])
buf = socket_read_n(conn, size)
message.ParseFromString(buf)
#########################
def serialize_message(conn, data):
conn.sendall(pack("<i", len(data)))
conn.sendall(data)
#########################
def get_device_id_info(scsi_id, host='localhost', port=6868):
command = proto.PbCommand()
command.operation = proto.PbOperation.DEVICES_INFO
device = proto.PbDeviceDefinition()
device.id = int(scsi_id)
command.devices.append(device)
data = send_pb_command(command.SerializeToString(),host,port)
result = proto.PbResult()
result.ParseFromString(data)
return result
######################################################################
# Main
######################################################################
def main():
rascsi_host = 'localhost'
rascsi_port = 6869
rascsi_proxy_listen_address = '0.0.0.0'
rascsi_proxy_listen_port = 6868
####
manager = PluginManager()
manager.setPluginPlaces(["plugins"])
manager.collectPlugins()
for plugin in manager.getAllPlugins():
print("RaSCSI proxy plugin found: " + str(plugin.name))
for plugin in manager.getAllPlugins():
plugin.plugin_object.init_hook()
address = (rascsi_proxy_listen_address, rascsi_proxy_listen_port)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(address)
server_socket.listen(1)
while True:
try:
connection, client_address = server_socket.accept()
# print('connection from', client_address)
buf = socket_read_n(connection, 6)
magic = buf[0:6]
if magic != b'RASCSI':
print("Invalid magic")
command = proto.PbCommand()
deserialize_message(connection, command)
if command.operation == proto.PbOperation.ATTACH:
images = get_image_files_info(rascsi_host, rascsi_port)
for device in command.devices:
type = proto.PbDeviceType.Name(device.type)
filepath = images['images_dir'] + "/" + device.params['file']
# print("type: " + str(type))
# print("path: " + str(filepath))
for plugin in manager.getAllPlugins():
plugin.plugin_object.attach_hook(device.id, type, filepath)
data = send_pb_command(command.SerializeToString(), rascsi_host, rascsi_port)
serialize_message(connection, data)
elif command.operation == proto.PbOperation.INSERT:
images = get_image_files_info(rascsi_host, rascsi_port)
for device in command.devices:
type = proto.PbDeviceType.Name(device.type)
filepath = images['images_dir'] + "/" + device.params['file']
# print("type: " + str(type))
# print("path: " + str(filepath))
for plugin in manager.getAllPlugins():
plugin.plugin_object.insert_hook(device.id, type, filepath)
data = send_pb_command(command.SerializeToString(), rascsi_host, rascsi_port)
serialize_message(connection, data)
elif command.operation == proto.PbOperation.DETACH:
for device in command.devices:
device_details = get_device_id_info(device.id, rascsi_host, rascsi_port)
type = proto.PbDeviceType.Name(device_details.devices_info.devices[0].type)
filepath = device_details.devices_info.devices[0].file.name
# print("type: " + str(type))
# print("path: " + str(filepath))
data = send_pb_command(command.SerializeToString(), rascsi_host, rascsi_port)
serialize_message(connection, data)
for plugin in manager.getAllPlugins():
plugin.plugin_object.detach_hook(device.id, type, filepath)
elif command.operation == proto.PbOperation.EJECT:
for device in command.devices:
device_details = get_device_id_info(device.id, rascsi_host, rascsi_port)
type = proto.PbDeviceType.Name(device_details.devices_info.devices[0].type)
filepath = device_details.devices_info.devices[0].file.name
# print("type: " + str(type))
# print("path: " + str(filepath))
data = send_pb_command(command.SerializeToString(), rascsi_host, rascsi_port)
serialize_message(connection, data)
for plugin in manager.getAllPlugins():
plugin.plugin_object.eject_hook(device.id, type, filepath)
else:
data = send_pb_command(command.SerializeToString(), rascsi_host, rascsi_port)
serialize_message(connection, data)
finally:
connection.close()
if __name__ == "__main__":
main()

View File

@ -0,0 +1,2 @@
protobuf==3.19.1
yapsy==1.12.2

View File

@ -0,0 +1,12 @@
[Unit]
Description=RaSCSI-Proxy service
After=network.target
[Service]
Type=simple
Restart=always
ExecStart=/home/pi/RASCSI/src/proxy/start.sh
SyslogIdentifier=RASCSIPROXY
[Install]
WantedBy=multi-user.target

84
python/proxy/start.sh Normal file
View File

@ -0,0 +1,84 @@
#!/usr/bin/env bash
set -e
# set -x # Uncomment to Debug
cd "$(dirname "$0")"
# verify packages installed
ERROR=0
if ! command -v python3 &> /dev/null ; then
echo "python3 could not be found"
echo "Run 'sudo apt install python3' to fix."
ERROR=1
fi
if ! python3 -m venv --help &> /dev/null ; then
echo "venv could not be found"
echo "Run 'sudo apt install python3-venv' to fix."
ERROR=1
fi
if [ $ERROR = 1 ] ; then
echo
echo "Fix errors and re-run ./start.sh"
exit 1
fi
# Test for two known broken venv states
if test -e venv; then
GOOD_VENV=true
! test -e venv/bin/activate && GOOD_VENV=false
pip3 list 1> /dev/null
test $? -eq 1 && GOOD_VENV=false
if ! "$GOOD_VENV"; then
echo "Deleting bad python venv"
sudo rm -rf venv
fi
fi
# Create the venv if it doesn't exist
if ! test -e venv; then
echo "Creating python venv for RaSCSI proxy"
python3 -m venv venv
echo "Activating venv"
source venv/bin/activate
echo "Installing requirements.txt"
pip3 install wheel
pip3 install -r requirements.txt
git rev-parse HEAD > current
fi
source venv/bin/activate
# Detect if someone updates the git repo - we need to re-run pip3 install.
set +e
git rev-parse --is-inside-work-tree &> /dev/null
if [[ $? -eq 0 ]]; then
set -e
if ! test -e current; then
git rev-parse > current
elif [ "$(cat current)" != "$(git rev-parse HEAD)" ]; then
echo "New version detected, updating libraries from requirements.txt"
pip3 install -r requirements.txt
git rev-parse HEAD > current
fi
else
echo "Warning: Not running from a valid git repository. Will not be able to update the code."
fi
set -e
# parse arguments
while [ "$1" != "" ]; do
PARAM=$(echo "$1" | awk -F= '{print $1}')
VALUE=$(echo "$1" | awk -F= '{print $2}')
case $PARAM in
-p | --port)
PORT="--port $VALUE"
;;
*)
echo "ERROR: unknown parameter \"$PARAM\""
exit 1
;;
esac
shift
done
echo "Starting RaSCSI proxy..."
python3 rascsi_proxy.py ${PORT}