mirror of
https://github.com/akuker/RASCSI.git
synced 2024-12-26 10:30:23 +00:00
Move to protobuf for the webapp, major overhaul to easyinstall.sh, code comment translations (#229)
* Making saving and loading config files work with protobuf * Formatted the Status column, and fixed the available ID logic * Updated handling of removed status for devices without image file support * Comment update * Fixed typo * Updated logging * Updated handling of removed status for devices without image file support * Comment update * Fixed typo * Updated logging * Better handling of device status * Updated parameter handling * Updated setting default interfaces * Revert "Updated setting default interfaces" This reverts commit210abc775d
. * Revert "Updated parameter handling" This reverts commit35302addd5
. * Abort with a 404 if rascsi is not running. Use any protobuf response to determine whether rascsi is running (should hardly be required anymore due to the other change, but just in case). * Move id reservation back into __main__ * Remove check for device type when validating Removed image * Leverage device property data for better status messages * Remove redundant string sanitation when reading config csv file * Clean up device list generation * Cleanup * Remove duplicates before building valid scsi id list * Fully translated cfilesystem.h code comments to English; partially translated cfilesystem.cpp * rascsi supports reserving IDs * Updated help message * Replaced BOOL by bool * Logging update * Logging update * Cleanup * Restructure the easyinstall.sh script to combine the install/update flows, and disallow installing the webapp by itself * Remove redundant steps handled in Makefile * Add the functionality to specify connect_type through a parameter * Add validation to the argument parser allowing only STANDARD and FULLSPEC as options * Complete translation of code comments for cfilesystem.h; partial translation for cfilesystem.cpp * Cleanup * Merge parts of the Network Assistant script by sonique6784; fix the run_choice startup parameter * Improve on the network setup messages * Fix routing address * Add checks for previous configuration; cleanup * Cleanup * Remove redundant step in wired setup. Improve messages. * Cleanup * Added default parameters to device properties * Return parameters a device was set up with * Add flows for configuring custom network settings; adopting some logic by sonique6784 * Improved device initialization * Updated default parameter handling * Updated default parameter handling * Fixed typo * Comment updates * Comment update * Make iso generation work again, and add error handling to urllib actions * Manage default parameters in the respective device * Print available network interfaces. Clean up step and improve descriptive messages. * Make the script clean up previous configurations * Make the script only show relevant interfaces * Partial translation of cfilesystem.cpp * Do not pass empty parameter string * Added supports_params flag * Completely translate code comments in cfilesystem.cpp * Show rascsi-web status after installing * Refactoring * Made comparisons more consistent * Updated error handling * Updated exception handling * Made comparisons more consistent * Updated error handling * Overlooked code comment translation * Renaming * Better error handling for socket connection * Disable two NEC hd image types due to issue#232 * Comment update * NEC sectors size must be 512 bytes * Updated logging * Updated vendor name handling * Updated handling of media loading/unloading * Comment update * NEC sectors size must be 512 bytes * Updated logging * Updated vendor name handling * Updated handling of media loading/unloading * Better handling of removable disks in the web ui * Added stoppable property and stopped status * Made MO stoppable * Removed duplicate code * Removed duplicate code * Copy read-only property * Renaming * Add an assistant for reserving scsi ids * Don't show action if no device attached * Implement a device_info app path, and cut down on device columns always shown * Cleanup * Removed duplicate code, added START/STOP * Improved default parameter handling * Updated load/eject handling * Logging update * Fixed typo * Verified START/STOP UNIT * Updated logging * Updated status handling * Updated status handling * More status handling updates * Logging update * Made instance fields local variables * Removed duplicate code, added START/STOP * Improved default parameter handling * Updated load/eject handling * Logging update * Fixed typo * Verified START/STOP UNIT * Updated logging * Updated status handling * Updated status handling * More status handling updates * Logging update * Made instance fields local variables * Made disk_t private * Made some data structures private * Fixed ARM compile issue * Fast forward instead of rebase existing git repo * Fixed ctapdriver initialization issue * Reset read-only status when opening an image file * Cleanup * Cleanup * Made logging more consistent * Updated log level * Cleanup * Log load/eject on error level for testing * Revert "Log load/eject on error level for testing" This reverts commitd35a15ea8e
. * Assume drive is not ready after having been stopped * Updated status handling * Make the csv config files store all relevant device data for reading * Read 9 column csv config files * Fixed typo * Rebuild manpage * Fixed issue #234 (MODE SENSE (10) returns wrong mode parameter header) * Removed unused code * Enum data type update * Removed duplicate range check * Removed duplicate code * Removed more duplicate code * Logging update * SCCD sector size was not meant to be configurable * Better error handling for csv reading and writing * Updated configurable sector size properties * Removed assertion * Improved error handling * Updated error handling * Re-added special error handling only relevant for SASI * Added TODOs * Comment update * Added override modifier * Removed obsolete debug flag (related code was not called) * Comment and logging updates * Removed obsolete try/catch * Revert "Removed obsolete try/catch" This reverts commit39ca12d8b1
. * Comment update * Removed duplicate code * Updated error messages, use more foreach loops * Avoid storing RaSCSI generated product info in config file * Updated logging * Logging update * Save config files in json instead of csv * Fix bugs with json config loading * Refactoring & remove unused code * Refactoring * Display upper case file endings in file management list * Only show product vendor for non-RaSCSI devices in the device list * Translate code comment * Refactoring * Fix bad identation * Improve valid file extension handling * Add validation when attaching removable media * Display valid file endings under the file list * Cleanup * Don't store 0 block size * Fix indentation * Read and write config files in key:pair format * Add section for controlling logging * README update * Added block_count * Cleanup, fix typos * Support attaching CD-ROM with custom block size * Evaluate block size when inserting a media * rasctl display capacity if available * Info message update * Use kwargs for device attachment * Fix bugs in attach_image kwargs; make config file more readable * POC for attaching device with profile * Only list product types valid for the particular image file * Perform validation of HDD image size based on the product profile * Implement sidecar config files for drive images. * Added missing product name to NEC vital product data * MO block size depends on capacity only * Better error handling for device sidecar config loading * Extended property/status display * Property display update * Updated error handling * Handle image sizes in bytes internally * Revert change * Resolve bad merge Co-authored-by: Uwe Seimet <Uwe.Seimet@seimet.de>
This commit is contained in:
parent
3e7f317c49
commit
8a3642bf9a
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,6 +6,7 @@ core
|
||||
*.swp
|
||||
__pycache__
|
||||
src/web/current
|
||||
src/web/rascsi_interface_pb2.py
|
||||
src/oled_monitor/current
|
||||
src/raspberrypi/hfdisk/
|
||||
*~
|
||||
|
396
easyinstall.sh
396
easyinstall.sh
@ -20,6 +20,32 @@ logo="""
|
||||
echo -e $logo
|
||||
}
|
||||
|
||||
function showMacNetworkWired(){
|
||||
logo="""
|
||||
.-~-.-~~~-.~-.\n
|
||||
╔═══════╗ .( )\n
|
||||
║|¯¯¯¯¯|║ / \`.\n
|
||||
║|_____|║>--------------<~ . )\n
|
||||
║ . __ ║ ( :'-'\n
|
||||
╚╦═════╦╝ ~-.________.:'\n
|
||||
¯¯¯¯¯¯¯\n
|
||||
"""
|
||||
echo -e $logo
|
||||
}
|
||||
|
||||
function showMacNetworkWireless(){
|
||||
logo="""
|
||||
.-~-.-~~~-.~-.\n
|
||||
╔═══════╗ .( .( )\n
|
||||
║|¯¯¯¯¯|║ .( .( / \`.\n
|
||||
║|_____|║ .o o ~ . )\n
|
||||
║ . __ ║ '( '( ( :'-'\n
|
||||
╚╦═════╦╝ '( ~-.________.:'\n
|
||||
¯¯¯¯¯¯¯\n
|
||||
"""
|
||||
echo -e $logo
|
||||
}
|
||||
|
||||
VIRTUAL_DRIVER_PATH=/home/pi/images
|
||||
HFS_FORMAT=/usr/bin/hformat
|
||||
HFDISK_BIN=/usr/bin/hfdisk
|
||||
@ -41,18 +67,19 @@ function initialChecks() {
|
||||
fi
|
||||
}
|
||||
|
||||
# install all dependency packages for RaSCSI Service
|
||||
function installPackages() {
|
||||
sudo apt-get update && sudo apt install git libspdlog-dev libpcap-dev genisoimage python3 python3-venv nginx libpcap-dev protobuf-compiler bridge-utils python3-dev libev-dev libevdev2 -y
|
||||
}
|
||||
|
||||
# install all dependency packages for RaSCSI Service
|
||||
# compile and install RaSCSI Service
|
||||
function installRaScsi() {
|
||||
installPackages
|
||||
sudo systemctl stop rascsi
|
||||
|
||||
cd ~/RASCSI/src/raspberrypi
|
||||
make all CONNECT_TYPE=FULLSPEC
|
||||
sudo make install CONNECT_TYPE=FULLSPEC
|
||||
make clean
|
||||
make all CONNECT_TYPE=${CONNECT_TYPE-FULLSPEC}
|
||||
sudo make install CONNECT_TYPE=${CONNECT_TYPE-FULLSPEC}
|
||||
|
||||
sudoIsReady=$(sudo grep -c "rascsi" /etc/sudoers)
|
||||
|
||||
@ -71,7 +98,39 @@ www-data ALL=NOPASSWD: /sbin/shutdown, /sbin/reboot
|
||||
sudo systemctl start rascsi
|
||||
}
|
||||
|
||||
# install everything required to run an HTTP server (Nginx + Python Flask App)
|
||||
function installRaScsiWebInterface() {
|
||||
echo "Compiling the Python protobuf library..."
|
||||
[ -f ~/RASCSI/src/web/rascsi_interface.proto ] && rm ~/RASCSI/src/web/rascsi_interface.proto
|
||||
protoc -I=/home/pi/RASCSI/src/raspberrypi/ --python_out=/home/pi/RASCSI/src/web/ rascsi_interface.proto
|
||||
|
||||
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
|
||||
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
|
||||
|
||||
sudo usermod -a -G pi www-data
|
||||
|
||||
sudo systemctl reload nginx
|
||||
|
||||
echo "Installing the rascsi-web.service configuration..."
|
||||
sudo cp ~/RASCSI/src/web/service-infra/rascsi-web.service /etc/systemd/system/rascsi-web.service
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable rascsi-web
|
||||
sudo systemctl start rascsi-web
|
||||
}
|
||||
|
||||
function createImagesDir() {
|
||||
if [ -d $VIRTUAL_DRIVER_PATH ]; then
|
||||
echo "The $VIRTUAL_DRIVER_PATH directory already exists."
|
||||
else
|
||||
echo "The $VIRTUAL_DRIVER_PATH directory does not exist; creating..."
|
||||
mkdir -p $VIRTUAL_DRIVER_PATH
|
||||
chmod -R 775 $VIRTUAL_DRIVER_PATH
|
||||
fi
|
||||
}
|
||||
|
||||
function stopOldWebInterface() {
|
||||
sudo systemctl stop rascsi-web
|
||||
APACHE_STATUS=$(sudo systemctl status apache2 &> /dev/null; echo $?)
|
||||
if [ "$APACHE_STATUS" -eq 0 ] ; then
|
||||
echo "Stopping old Apache2 RaSCSI Web..."
|
||||
@ -80,28 +139,6 @@ function stopOldWebInterface() {
|
||||
fi
|
||||
}
|
||||
|
||||
# install everything required to run an HTTP server (Nginx + Python Flask App)
|
||||
function installRaScsiWebInterface() {
|
||||
stopOldWebInterface
|
||||
installPackages
|
||||
|
||||
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
|
||||
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
|
||||
|
||||
mkdir -p $VIRTUAL_DRIVER_PATH
|
||||
chmod -R 775 $VIRTUAL_DRIVER_PATH
|
||||
groups www-data
|
||||
sudo usermod -a -G pi www-data
|
||||
groups www-data
|
||||
|
||||
sudo systemctl reload nginx
|
||||
|
||||
sudo cp ~/RASCSI/src/web/service-infra/rascsi-web.service /etc/systemd/system/rascsi-web.service
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable rascsi-web
|
||||
sudo systemctl start rascsi-web
|
||||
}
|
||||
|
||||
function updateRaScsiGit() {
|
||||
echo "Updating checked out branch $GIT_REMOTE/$GIT_BRANCH"
|
||||
cd ~/RASCSI
|
||||
@ -112,8 +149,7 @@ function updateRaScsiGit() {
|
||||
stashed=1
|
||||
fi
|
||||
|
||||
git fetch $GIT_REMOTE
|
||||
git rebase $GIT_REMOTE/$GIT_BRANCH
|
||||
git pull --ff-only
|
||||
|
||||
if [ $stashed -eq 1 ]; then
|
||||
echo "Reapplying local changes..."
|
||||
@ -121,33 +157,14 @@ function updateRaScsiGit() {
|
||||
fi
|
||||
}
|
||||
|
||||
function updateRaScsi() {
|
||||
updateRaScsiGit
|
||||
installPackages
|
||||
sudo systemctl stop rascsi
|
||||
|
||||
cd ~/RASCSI/src/raspberrypi
|
||||
|
||||
make clean
|
||||
make all CONNECT_TYPE=FULLSPEC
|
||||
sudo make install CONNECT_TYPE=FULLSPEC
|
||||
sudo systemctl start rascsi
|
||||
}
|
||||
|
||||
function updateRaScsiWebInterface() {
|
||||
stopOldWebInterface
|
||||
updateRaScsiGit
|
||||
sudo cp -f ~/RASCSI/src/web/service-infra/nginx-default.conf /etc/nginx/sites-available/default
|
||||
sudo cp -f ~/RASCSI/src/web/service-infra/502.html /var/www/html/502.html
|
||||
echo "Restarting rascsi-web services..."
|
||||
sudo systemctl restart rascsi-web
|
||||
sudo systemctl restart nginx
|
||||
}
|
||||
|
||||
function showRaScsiStatus() {
|
||||
sudo systemctl status rascsi | tee
|
||||
}
|
||||
|
||||
function showRaScsiWebStatus() {
|
||||
sudo systemctl status rascsi-web | tee
|
||||
}
|
||||
|
||||
function createDrive600MB() {
|
||||
createDrive 600 "HD600"
|
||||
}
|
||||
@ -184,6 +201,22 @@ function formatDrive() {
|
||||
fi
|
||||
|
||||
# Inject hfdisk commands to create Drive with correct partitions
|
||||
# https://www.codesrc.com/mediawiki/index.php/HFSFromScratch
|
||||
# i initialize partition map
|
||||
# continue with default first block
|
||||
# C Create 1st partition with type specified next)
|
||||
# continue with default
|
||||
# 32 32 blocks (required for HFS+)
|
||||
# Driver_Partition Partition Name
|
||||
# Apple_Driver Partition Type (available types: Apple_Driver, Apple_Driver43, Apple_Free, Apple_HFS...)
|
||||
# C Create 2nd partition with type specified next
|
||||
# continue with default first block
|
||||
# continue with default block size (rest of the disk)
|
||||
# ${volumeName} Partition name provided by user
|
||||
# Apple_HFS Partition Type
|
||||
# w Write partition map to disk
|
||||
# y Confirm partition table
|
||||
# p Print partition map
|
||||
(echo i; echo ; echo C; echo ; echo 32; echo "Driver_Partition"; echo "Apple_Driver"; echo C; echo ; echo ; echo "${volumeName}"; echo "Apple_HFS"; echo w; echo y; echo p;) | $HFDISK_BIN "$diskPath"
|
||||
partitionOk=$?
|
||||
|
||||
@ -242,55 +275,208 @@ function createDrive() {
|
||||
fi
|
||||
}
|
||||
|
||||
function setupWiredNetworking() {
|
||||
echo "Setting up wired network..."
|
||||
|
||||
LAN_INTERFACE=eth0
|
||||
|
||||
echo "$LAN_INTERFACE will be configured for network forwarding with DHCP."
|
||||
echo ""
|
||||
echo "WARNING: If you continue, the IP address of your Pi may change upon reboot."
|
||||
echo "Please make sure you will not lose access to the Pi system."
|
||||
echo ""
|
||||
echo "Do you want to proceed with network configuration using the default settings? Y/n"
|
||||
read REPLY
|
||||
|
||||
if [ "$REPLY" == "N" ] || [ "$REPLY" == "n" ]; then
|
||||
echo "Available wired interfaces on this system:"
|
||||
ip -o addr show scope link | awk '{split($0, a); print $2}' | grep eth
|
||||
echo "Please type the wired interface you want to use and press Enter:"
|
||||
read -r SELECTED
|
||||
LAN_INTERFACE=$SELECTED
|
||||
fi
|
||||
|
||||
if [ $(grep -c "^denyinterfaces" /etc/dhcpcd.conf) -ge 1 ]; then
|
||||
echo "WARNING: Network forwarding may already have been configured. Proceeding will overwrite the configuration."
|
||||
echo "Press enter to continue or CTRL-C to exit"
|
||||
read REPLY
|
||||
sudo sed -i /^denyinterfaces/d /etc/dhcpcd.conf
|
||||
fi
|
||||
sudo echo "denyinterfaces $LAN_INTERFACE" >> /etc/dhcpcd.conf
|
||||
echo "Modified /etc/dhcpcd.conf"
|
||||
|
||||
# default config file is made for eth0, this will set the right net interface
|
||||
sudo bash -c 'sed s/eth0/'"$LAN_INTERFACE"'/g /home/pi/RASCSI/src/raspberrypi/os_integration/rascsi_bridge > /etc/network/interfaces.d/rascsi_bridge'
|
||||
echo "Modified /etc/network/interfaces.d/rascsi_bridge"
|
||||
|
||||
echo "Configuration completed!"
|
||||
echo "Please make sure you attach ia DaynaPORT network adapter to the RaSCSI configuration."
|
||||
echo "Either use the Web UI, or do this on the command line (assuming SCSI ID 6): \"rascsi -ID 6 -t scdp $LAN_INTERFACE\""
|
||||
echo ""
|
||||
echo "We need to reboot your Pi"
|
||||
echo "Press Enter to reboot or CTRL-C to exit"
|
||||
read
|
||||
|
||||
echo "Rebooting..."
|
||||
sleep 3
|
||||
sudo reboot
|
||||
}
|
||||
|
||||
function setupWirelessNetworking() {
|
||||
NETWORK="10.10.20"
|
||||
IP=$NETWORK.2 # Macintosh or Device IP
|
||||
NETWORK_MASK="255.255.255.0"
|
||||
CIDR="24"
|
||||
ROUTER_IP=$NETWORK.1
|
||||
ROUTING_ADDRESS=$NETWORK.0/$CIDR
|
||||
WLAN_INTERFACE="wlan0"
|
||||
|
||||
echo "$WLAN_INTERFACE will be configured for network forwarding with static IP assignment."
|
||||
echo "Configure your Macintosh or other device with the following:"
|
||||
echo "IP Address (static): $IP"
|
||||
echo "Router Address: $ROUTER_IP"
|
||||
echo "Subnet Mask: $NETWORK_MASK"
|
||||
echo "DNS Server: Any public DNS server"
|
||||
echo ""
|
||||
echo "Do you want to proceed with network configuration using the default settings? Y/n"
|
||||
read REPLY
|
||||
|
||||
if [ "$REPLY" == "N" ] || [ "$REPLY" == "n" ]; then
|
||||
echo "Available wireless interfaces on this system:"
|
||||
ip -o addr show scope link | awk '{split($0, a); print $2}' | grep wlan
|
||||
echo "Please type the wireless interface you want to use and press Enter:"
|
||||
read -r WLAN_INTERFACE
|
||||
echo "Base IP address (ex. 10.10.20):"
|
||||
read -r NETWORK
|
||||
echo "CIDR for Subnet Mask (ex. '24' for 255.255.255.0):"
|
||||
read -r CIDR
|
||||
ROUTER_IP=$NETWORK.1
|
||||
ROUTING_ADDRESS=$NETWORK.0/$CIDR
|
||||
fi
|
||||
|
||||
|
||||
if [ $(grep -c "^net.ipv4.ip_forward=1" /etc/sysctl.conf) -ge 1 ]; then
|
||||
echo "WARNING: Network forwarding may already have been configured. Proceeding will overwrite the configuration."
|
||||
echo "Press enter to continue or CTRL-C to exit"
|
||||
read REPLY
|
||||
else
|
||||
sudo bash -c 'echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf'
|
||||
echo "Modified /etc/sysctl.conf"
|
||||
fi
|
||||
|
||||
sudo iptables --flush
|
||||
sudo iptables -t nat -F
|
||||
sudo iptables -X
|
||||
sudo iptables -Z
|
||||
sudo iptables -P INPUT ACCEPT
|
||||
sudo iptables -P OUTPUT ACCEPT
|
||||
sudo iptables -P FORWARD ACCEPT
|
||||
sudo iptables -t nat -A POSTROUTING -o $WLAN_INTERFACE -s $ROUTING_ADDRESS -j MASQUERADE
|
||||
|
||||
# Check if iptables-persistent is installed
|
||||
IPTABLES_PERSISTENT=$(dpkg -s iptables-persistent | grep Status | grep -c "install ok")
|
||||
if [ $IPTABLES_PERSISTENT -eq 0 ]; then
|
||||
sudo apt-get install iptables-persistent --assume-yes
|
||||
else
|
||||
sudo iptables-save --file /etc/iptables/rules.v4
|
||||
fi
|
||||
echo "Modified /etc/iptables/rules.v4"
|
||||
|
||||
echo "Configuration completed!"
|
||||
echo ""
|
||||
echo "Please make sure you attach a DaynaPORT network adapter to the RaSCSI configuration"
|
||||
echo "Either use the Web UI, or do this on the command line (assuming SCSI ID 6): \"rascsi -ID 6 -t scdp $WLAN_INTERFACE:$ROUTER_IP/$CIDR\""
|
||||
echo ""
|
||||
echo "We need to reboot your Pi"
|
||||
echo "Press Enter to reboot or CTRL-C to exit"
|
||||
read REPLY
|
||||
|
||||
echo "Rebooting..."
|
||||
sleep 3
|
||||
sudo reboot
|
||||
}
|
||||
|
||||
function reserveScsiIds() {
|
||||
if [ ! -f /etc/systemd/system/rascsi-web.service ]; then
|
||||
echo "This feature depends on the RaSCSI Web UI being installed. Please install RaSCSI Web before continuing."
|
||||
exit
|
||||
fi
|
||||
|
||||
sudo systemctl stop rascsi-web
|
||||
echo "Please type the SCSI ID(s) that you want to reserve and press Enter:"
|
||||
echo "The input should be a string of digits without separators, e.g. \"017\" for IDs 0, 1, and 7."
|
||||
read -r RESERVED_IDS
|
||||
sudo sed -i /^ExecStart=/d /etc/systemd/system/rascsi-web.service
|
||||
sudo sed -i "8 i ExecStart=/home/pi/RASCSI/src/web/start.sh --reserved_ids=$RESERVED_IDS" /etc/systemd/system/rascsi-web.service
|
||||
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl start rascsi-web
|
||||
}
|
||||
|
||||
function runChoice() {
|
||||
case $1 in
|
||||
0)
|
||||
echo "Installing RaSCSI Service + Web interface"
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive"
|
||||
stopOldWebInterface
|
||||
updateRaScsiGit
|
||||
createImagesDir
|
||||
installPackages
|
||||
installRaScsi
|
||||
installRaScsiWebInterface
|
||||
createDrive600MB
|
||||
showRaScsiStatus
|
||||
echo "Installing RaSCSI Service + Web interface - Complete!"
|
||||
showRaScsiWebStatus
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface + 600MB Drive - Complete!"
|
||||
;;
|
||||
1)
|
||||
echo "Installing RaSCSI Service"
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface"
|
||||
stopOldWebInterface
|
||||
updateRaScsiGit
|
||||
createImagesDir
|
||||
installPackages
|
||||
installRaScsi
|
||||
installRaScsiWebInterface
|
||||
showRaScsiStatus
|
||||
echo "Installing RaSCSI Service - Complete!"
|
||||
showRaScsiWebStatus
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) + Web interface - Complete!"
|
||||
;;
|
||||
2)
|
||||
echo "Installing RaSCSI Web interface"
|
||||
installRaScsiWebInterface
|
||||
echo "Installing RaSCSI Web interface - Complete!"
|
||||
;;
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC})"
|
||||
updateRaScsiGit
|
||||
createImagesDir
|
||||
installPackages
|
||||
installRaScsi
|
||||
showRaScsiStatus
|
||||
echo "Installing / Updating RaSCSI Service (${CONNECT_TYPE-FULLSPEC}) - Complete!"
|
||||
;;
|
||||
3)
|
||||
echo "Updating RaSCSI Service + Web interface"
|
||||
updateRaScsi
|
||||
updateRaScsiWebInterface
|
||||
showRaScsiStatus
|
||||
echo "Updating RaSCSI Service + Web interface - Complete!"
|
||||
;;
|
||||
4)
|
||||
echo "Updating RaSCSI Service"
|
||||
updateRaScsi
|
||||
showRaScsiStatus
|
||||
echo "Updating RaSCSI Service - Complete!"
|
||||
;;
|
||||
5)
|
||||
echo "Updating RaSCSI Web interface"
|
||||
updateRaScsiWebInterface
|
||||
echo "Updating RaSCSI Web interface - Complete!"
|
||||
;;
|
||||
6)
|
||||
echo "Creating a 600MB drive"
|
||||
createDrive600MB
|
||||
echo "Creating a 600MB drive - Complete!"
|
||||
;;
|
||||
7)
|
||||
4)
|
||||
echo "Creating a custom drive"
|
||||
createDriveCustom
|
||||
echo "Creating a custom drive - Complete!"
|
||||
;;
|
||||
5)
|
||||
echo "Configuring wired network bridge"
|
||||
showMacNetworkWired
|
||||
setupWiredNetworking
|
||||
echo "Configuring wired network bridge - Complete!"
|
||||
;;
|
||||
6)
|
||||
echo "Configuring wifi network bridge"
|
||||
showMacNetworkWireless
|
||||
setupWirelessNetworking
|
||||
echo "Configuring wifi network bridge - Complete!"
|
||||
;;
|
||||
7)
|
||||
echo "Reserving SCSI IDs"
|
||||
reserveScsiIds
|
||||
showRaScsiWebStatus
|
||||
echo "Reserving SCSI IDs - Complete!"
|
||||
;;
|
||||
-h|--help|h|help)
|
||||
showMenu
|
||||
;;
|
||||
@ -314,25 +500,53 @@ function readChoice() {
|
||||
function showMenu() {
|
||||
echo ""
|
||||
echo "Choose among the following options:"
|
||||
echo "INSTALL"
|
||||
echo " 0) install RaSCSI Service + web interface + 600MB Drive (recommended)"
|
||||
echo " 1) install RaSCSI Service (initial)"
|
||||
echo " 2) install RaSCSI Web interface"
|
||||
echo "UPDATE"
|
||||
echo " 3) update RaSCSI Service + web interface (recommended)"
|
||||
echo " 4) update RaSCSI Service"
|
||||
echo " 5) update RaSCSI Web interface"
|
||||
echo "CREATE EMPTY DRIVE"
|
||||
echo " 6) 600MB drive (recommended)"
|
||||
echo " 7) custom drive size (up to 4000MB)"
|
||||
echo "INSTALL/UPDATE RASCSI (${CONNECT_TYPE-FULLSPEC} version)"
|
||||
echo " 0) install or update RaSCSI Service + web interface + 600MB Drive (recommended)"
|
||||
echo " 1) install or update RaSCSI Service + web interface"
|
||||
echo " 2) install or update RaSCSI Service"
|
||||
echo "CREATE EMPTY DRIVE IMAGE"
|
||||
echo " 3) 600MB drive (recommended)"
|
||||
echo " 4) custom drive size (up to 4000MB)"
|
||||
echo "NETWORK ASSISTANT"
|
||||
echo " 5) configure network forwarding over Ethernet (DHCP)"
|
||||
echo " 6) configure network forwarding over WiFi (static IP)"
|
||||
echo "MISCELLANEOUS"
|
||||
echo " 7) reserve SCSI IDs"
|
||||
}
|
||||
|
||||
# parse arguments
|
||||
while [ "$1" != "" ]; do
|
||||
PARAM=`echo $1 | awk -F= '{print $1}'`
|
||||
VALUE=`echo $1 | awk -F= '{print $2}'`
|
||||
case $PARAM in
|
||||
-c | --connect_type)
|
||||
CONNECT_TYPE=$VALUE
|
||||
;;
|
||||
-r | --run_choice)
|
||||
RUN_CHOICE=$VALUE
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown parameter \"$PARAM\""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
case $VALUE in
|
||||
FULLSPEC | STANDARD | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7)
|
||||
;;
|
||||
*)
|
||||
echo "ERROR: unknown option \"$VALUE\""
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
shift
|
||||
done
|
||||
|
||||
showRaSCSILogo
|
||||
initialChecks
|
||||
if [ -z "${1}" ]; then # $1 is unset, show menu
|
||||
|
||||
if [ -z "${RUN_CHOICE}" ]; then # RUN_CHOICE is unset, show menu
|
||||
showMenu
|
||||
readChoice
|
||||
else
|
||||
runChoice "$1"
|
||||
runChoice "$RUN_CHOICE"
|
||||
fi
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -127,7 +127,7 @@ namespace Human68k {
|
||||
};
|
||||
|
||||
struct namests_t {
|
||||
BYTE wildcard; ///< Wildcard array
|
||||
BYTE wildcard; ///< Wildcard character length
|
||||
BYTE drive; ///< Drive number
|
||||
BYTE path[65]; ///< Path (subdirectory +/)
|
||||
BYTE name[8]; ///< File name (PADDING 0x20)
|
||||
@ -332,45 +332,44 @@ enum {
|
||||
// Bit24~30 Duplicate file identification mark 0:Automatic 1~127:Chars
|
||||
};
|
||||
|
||||
/// ファイルシステム動作フラグ
|
||||
/// File system operational flag
|
||||
/**
|
||||
通常は0にする。リードオンリーでマウントしたいドライブの場合は1にする。
|
||||
それ以外の値は将来のための予約とする。
|
||||
判定が困難なデバイス(自作USBストレージとか)のための保険用。
|
||||
Normal is 0. Becomes 1 if attempting to mount in read-only mode.
|
||||
Reserving the other values for future use.
|
||||
Insurance against hard-to-detect devices such as homemade USB storage.
|
||||
*/
|
||||
enum {
|
||||
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: 強制書き込み禁止
|
||||
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: 強制リムーバブルメディア
|
||||
FSFLAG_MANUAL = 0x00000004, ///< Bit2: 強制手動イジェクト
|
||||
FSFLAG_WRITE_PROTECT = 0x00000001, ///< Bit0: Force write protect
|
||||
FSFLAG_REMOVABLE = 0x00000002, ///< Bit1: Force removable media
|
||||
FSFLAG_MANUAL = 0x00000004, ///< Bit2: Force manual eject
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// まるっとリングリスト
|
||||
/// Full ring list
|
||||
///
|
||||
/// 先頭(root.next)が最も新しいオブジェクト。
|
||||
/// 末尾(root.prev)が最も古い/未使用オブジェクト。
|
||||
/// コード効率追求のため、delete時は必ずポインタをアップキャストすること。
|
||||
/// First (root.next) is the most recent object.
|
||||
/// Last (root.prev) is the oldest / unused object.
|
||||
/// For code optimization purposes, always upcast the pointer when deleting.
|
||||
//
|
||||
//===========================================================================
|
||||
class CRing {
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CRing() { Init(); } ///< デフォルトコンストラクタ
|
||||
~CRing() { Remove(); } ///< デストラクタ final
|
||||
void Init() { next = prev = this; } ///< 初期化
|
||||
CRing() { Init(); }
|
||||
~CRing() { Remove(); }
|
||||
void Init() { next = prev = this; }
|
||||
|
||||
CRing* Next() const { return next; } ///< 次の要素を取得
|
||||
CRing* Prev() const { return prev; } ///< 前の要素を取得
|
||||
CRing* Next() const { return next; } ///< Get the next element
|
||||
CRing* Prev() const { return prev; } ///< Get the previous element
|
||||
|
||||
void Insert(CRing* pRoot)
|
||||
{
|
||||
// 該当オブジェクトを切り離し
|
||||
// Separate the relevant objects
|
||||
ASSERT(next);
|
||||
ASSERT(prev);
|
||||
next->prev = prev;
|
||||
prev->next = next;
|
||||
// リング先頭へ挿入
|
||||
// Insert into the beginning of the ring
|
||||
ASSERT(pRoot);
|
||||
ASSERT(pRoot->next);
|
||||
next = pRoot->next;
|
||||
@ -378,16 +377,16 @@ public:
|
||||
pRoot->next->prev = this;
|
||||
pRoot->next = this;
|
||||
}
|
||||
///< オブジェクト切り離し & リング先頭へ挿入
|
||||
///< Separate objects & insert into the beginning of the ring
|
||||
|
||||
void InsertTail(CRing* pRoot)
|
||||
{
|
||||
// 該当オブジェクトを切り離し
|
||||
// Separate the relevant objects
|
||||
ASSERT(next);
|
||||
ASSERT(prev);
|
||||
next->prev = prev;
|
||||
prev->next = next;
|
||||
// リング末尾へ挿入
|
||||
// Insert into the end of the ring
|
||||
ASSERT(pRoot);
|
||||
ASSERT(pRoot->prev);
|
||||
next = pRoot;
|
||||
@ -395,13 +394,13 @@ public:
|
||||
pRoot->prev->next = this;
|
||||
pRoot->prev = this;
|
||||
}
|
||||
///< オブジェクト切り離し & リング末尾へ挿入
|
||||
///< Separate objects & insert into the end of the ring
|
||||
|
||||
void InsertRing(CRing* pRoot)
|
||||
{
|
||||
if (next == prev) return;
|
||||
|
||||
// リング先頭へ挿入
|
||||
// Insert into the beginning of the ring
|
||||
ASSERT(pRoot);
|
||||
ASSERT(pRoot->next);
|
||||
pRoot->next->prev = prev;
|
||||
@ -409,554 +408,537 @@ public:
|
||||
pRoot->next = next;
|
||||
next->prev = pRoot;
|
||||
|
||||
// 自分自身を空にする
|
||||
// Empty self
|
||||
next = prev = this;
|
||||
}
|
||||
///< 自分以外のオブジェクト切り離し & リング先頭へ挿入
|
||||
///< Separate objects except self & insert into the beginning of the ring
|
||||
|
||||
void Remove()
|
||||
{
|
||||
// 該当オブジェクトを切り離し
|
||||
// Separate the relevant objects
|
||||
ASSERT(next);
|
||||
ASSERT(prev);
|
||||
next->prev = prev;
|
||||
prev->next = next;
|
||||
// 安全のため自分自身を指しておく (何度切り離しても問題ない)
|
||||
// To be safe, assign self (nothing stops you from separating any number of times)
|
||||
next = prev = this;
|
||||
}
|
||||
///< オブジェクト切り離し
|
||||
///< Separate objects
|
||||
|
||||
private:
|
||||
CRing* next; ///< 次の要素
|
||||
CRing* prev; ///< 前の要素
|
||||
CRing* next; ///< Next element
|
||||
CRing* prev; ///< Previous element
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ディレクトリエントリ ファイル名
|
||||
/// Directory Entry: File Name
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostFilename {
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CHostFilename(); ///< デフォルトコンストラクタ
|
||||
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< オフセット位置取得
|
||||
CHostFilename();
|
||||
static size_t Offset() { return offsetof(CHostFilename, m_szHost); } ///< Get offset location
|
||||
|
||||
void SetHost(const TCHAR* szHost); ///< ホスト側の名称を設定
|
||||
const TCHAR* GetHost() const { return m_szHost; } ///< ホスト側の名称を取得
|
||||
void ConvertHuman(int nCount = -1); ///< Human68k側の名称を変換
|
||||
void CopyHuman(const BYTE* szHuman); ///< Human68k側の名称を複製
|
||||
BOOL isReduce() const; ///< Human68k側の名称が加工されたか調査
|
||||
BOOL isCorrect() const { return m_bCorrect; } ///< Human68k側のファイル名規則に合致しているか調査
|
||||
const BYTE* GetHuman() const { return m_szHuman; } ///< Human68kファイル名を取得
|
||||
void SetHost(const TCHAR* szHost); ///< Set the name of the host
|
||||
const TCHAR* GetHost() const { return m_szHost; } ///< Get the name of the host
|
||||
void ConvertHuman(int nCount = -1); ///< Convert the Human68k name
|
||||
void CopyHuman(const BYTE* szHuman); ///< Copy the Human68k name
|
||||
BOOL isReduce() const; ///< Inspect if the Human68k name is generated
|
||||
BOOL isCorrect() const { return m_bCorrect; } ///< Inspect if the Human68k file name adhers to naming rules
|
||||
const BYTE* GetHuman() const { return m_szHuman; } ///< Get Human68k file name
|
||||
const BYTE* GetHumanLast() const
|
||||
{ return m_pszHumanLast; } ///< Human68kファイル名を取得
|
||||
const BYTE* GetHumanExt() const { return m_pszHumanExt; }///< Human68kファイル名を取得
|
||||
void SetEntryName(); ///< Human68kディレクトリエントリを設定
|
||||
{ return m_pszHumanLast; } ///< Get Human68k file name
|
||||
const BYTE* GetHumanExt() const { return m_pszHumanExt; }///< Get Human68k file name
|
||||
void SetEntryName(); ///< Set Human68k directory entry
|
||||
void SetEntryAttribute(BYTE nHumanAttribute)
|
||||
{ m_dirHuman.attr = nHumanAttribute; } ///< Human68kディレクトリエントリを設定
|
||||
{ m_dirHuman.attr = nHumanAttribute; } ///< Set Human68k directory entry
|
||||
void SetEntrySize(DWORD nHumanSize)
|
||||
{ m_dirHuman.size = nHumanSize; } ///< Human68kディレクトリエントリを設定
|
||||
{ m_dirHuman.size = nHumanSize; } ///< Set Human68k directory entry
|
||||
void SetEntryDate(WORD nHumanDate)
|
||||
{ m_dirHuman.date = nHumanDate; } ///< Human68kディレクトリエントリを設定
|
||||
{ m_dirHuman.date = nHumanDate; } ///< Set Human68k directory entry
|
||||
void SetEntryTime(WORD nHumanTime)
|
||||
{ m_dirHuman.time = nHumanTime; } ///< Human68kディレクトリエントリを設定
|
||||
{ m_dirHuman.time = nHumanTime; } ///< Set Human68k directory entry
|
||||
void SetEntryCluster(WORD nHumanCluster)
|
||||
{ m_dirHuman.cluster = nHumanCluster; } ///< Human68kディレクトリエントリを設定
|
||||
{ m_dirHuman.cluster = nHumanCluster; } ///< Set Human68k directory entry
|
||||
const Human68k::dirent_t* GetEntry() const
|
||||
{ return &m_dirHuman; } ///< Human68kディレクトリエントリを取得
|
||||
BOOL CheckAttribute(DWORD nHumanAttribute) const; ///< Human68kディレクトリエントリの属性判定
|
||||
{ return &m_dirHuman; } ///< Get Human68k directory entry
|
||||
BOOL CheckAttribute(DWORD nHumanAttribute) const; ///< Determine Human68k directory entry attributes
|
||||
BOOL isSameEntry(const Human68k::dirent_t* pdirHuman) const
|
||||
{ ASSERT(pdirHuman); return memcmp(&m_dirHuman, pdirHuman, sizeof(m_dirHuman)) == 0; }
|
||||
///< Human68kディレクトリエントリの一致判定
|
||||
///< Determine Human68k directory entry match
|
||||
|
||||
// パス名操作
|
||||
static const BYTE* SeparateExt(const BYTE* szHuman); ///< Human68kファイル名から拡張子を分離
|
||||
// Path name operations
|
||||
static const BYTE* SeparateExt(const BYTE* szHuman); ///< Extract extension from Human68k file name
|
||||
|
||||
private:
|
||||
static BYTE* CopyName(BYTE* pWrite, const BYTE* pFirst, const BYTE* pLast);
|
||||
///< Human68k側のファイル名要素をコピー
|
||||
///< Copy Human68k file name elements
|
||||
|
||||
const BYTE* m_pszHumanLast; ///< 該当エントリのHuman68k内部名の終端位置
|
||||
const BYTE* m_pszHumanExt; ///< 該当エントリのHuman68k内部名の拡張子位置
|
||||
BOOL m_bCorrect; ///< 該当エントリのHuman68k内部名が正しければ真
|
||||
BYTE m_szHuman[24]; ///< 該当エントリのHuman68k内部名
|
||||
Human68k::dirent_t m_dirHuman; ///< 該当エントリのHuman68k全情報
|
||||
TCHAR m_szHost[FILEPATH_MAX]; ///< 該当エントリのホスト側の名称 (可変長)
|
||||
const BYTE* m_pszHumanLast; ///< Last position of the Human68k internal name of the relevant entry
|
||||
const BYTE* m_pszHumanExt; ///< Position of the extension of the Human68k internal name of the relevant entry
|
||||
BOOL m_bCorrect; ///< TRUE if the relevant entry of the Human68k internal name is correct
|
||||
BYTE m_szHuman[24]; ///< Human68k internal name of the relevant entry
|
||||
Human68k::dirent_t m_dirHuman; ///< All information for the Human68k relevant entry
|
||||
TCHAR m_szHost[FILEPATH_MAX]; ///< The host name of the relevant entry (variable length)
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ディレクトリエントリ パス名
|
||||
/// Directory entry: path name
|
||||
///
|
||||
/// Human68k側のパス名は、必ず先頭が/で始まり、末尾が/で終わる。
|
||||
/// ユニット番号は持たない。
|
||||
/// 高速化のため、ホスト側の名称にはベースパス部分も含む。
|
||||
/// A file path in Human68k always begings with / and ends with /
|
||||
/// They don't hold unit numbers.
|
||||
/// Include the base path part of the name on the host side for a performance boost.
|
||||
//
|
||||
//===========================================================================
|
||||
/** @note
|
||||
ほとんどのHuman68kのアプリは、ファイルの更新等によってディレクトリエ
|
||||
ントリに変更が生じた際、親ディレクトリのタイムスタンプは変化しないも
|
||||
のと想定して作られている。
|
||||
ところがホスト側のファイルシステムでは親ディレクトリのタイムスタンプ
|
||||
も変化してしまうものが主流となってしまっている。
|
||||
Most Human68k applications are written in a way that expects time stamps not to
|
||||
get updated for new directories created as a result of file operations, which
|
||||
triggers updates to directory entires.
|
||||
However, on the host file system, new directories do typically get an updated time stamp.
|
||||
|
||||
このため、ディレクトリのコピー等において、アプリ側は正確にタイムスタ
|
||||
ンプ情報などを設定しているにもかかわらず、実行結果では時刻情報が上書
|
||||
きされてしまうという惨劇が起きてしまう。
|
||||
The unfortunate outcome is that when copying a directory for instance, the time stamp
|
||||
will get overwritten even if the application did not intend for the time stamp to get updated.
|
||||
|
||||
そこでディレクトリキャッシュ内にFATタイムスタンプのエミュレーション
|
||||
機能を実装した。ホスト側のファイルシステムの更新時にタイムスタンプ情
|
||||
報を復元することでHuman68k側の期待する結果と一致させる。
|
||||
Here follows an implementation of a directory cache FAT time stamp emulation feature.
|
||||
At the time of a file system update on the host side, time stamp information will be restored
|
||||
in order to achieve expected behavior on the Human68k side.
|
||||
*/
|
||||
class CHostPath: public CRing {
|
||||
/// メモリ管理用
|
||||
/// For memory management
|
||||
struct ring_t {
|
||||
CRing r; ///< 円環
|
||||
CHostFilename f; ///< 実体
|
||||
CRing r;
|
||||
CHostFilename f;
|
||||
};
|
||||
|
||||
public:
|
||||
/// 検索用バッファ
|
||||
/// Search buffer
|
||||
struct find_t {
|
||||
DWORD count; ///< 検索実行回数 + 1 (0のときは以下の値は無効)
|
||||
DWORD id; ///< 次回検索を続行するパスのエントリ識別ID
|
||||
const ring_t* pos; ///< 次回検索を続行する位置 (識別ID一致時)
|
||||
Human68k::dirent_t entry; ///< 次回検索を続行するエントリ内容
|
||||
DWORD count; ///< Search execution count + 1 (When 0 the below value is invalid)
|
||||
DWORD id; ///< Entry unique ID for the path of the next search
|
||||
const ring_t* pos; ///< Position of the next search (When identical to unique ID)
|
||||
Human68k::dirent_t entry; ///< Contents of the next seach entry
|
||||
|
||||
void Clear() { count = 0; } ///< 初期化
|
||||
void Clear() { count = 0; } ///< Initialize
|
||||
};
|
||||
|
||||
// 基本ファンクション
|
||||
CHostPath(); ///< デフォルトコンストラクタ
|
||||
~CHostPath(); ///< デストラクタ final
|
||||
void Clean(); ///< 再利用のための初期化
|
||||
CHostPath();
|
||||
~CHostPath();
|
||||
void Clean(); ///< Initialialize for reuse
|
||||
|
||||
void SetHuman(const BYTE* szHuman); ///< Human68k側の名称を直接指定する
|
||||
void SetHost(const TCHAR* szHost); ///< ホスト側の名称を直接指定する
|
||||
BOOL isSameHuman(const BYTE* szHuman) const; ///< Human68k側の名称を比較する
|
||||
BOOL isSameChild(const BYTE* szHuman) const; ///< Human68k側の名称を比較する
|
||||
const TCHAR* GetHost() const { return m_szHost; } ///< ホスト側の名称の獲得
|
||||
void SetHuman(const BYTE* szHuman); ///< Directly specify the name on the Human68k side
|
||||
void SetHost(const TCHAR* szHost); ///< Directly specify the name on the host side
|
||||
BOOL isSameHuman(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
|
||||
BOOL isSameChild(const BYTE* szHuman) const; ///< Compare the name on the Human68k side
|
||||
const TCHAR* GetHost() const { return m_szHost; } ///< Obtain the name on the host side
|
||||
const CHostFilename* FindFilename(const BYTE* szHuman, DWORD nHumanAttribute = Human68k::AT_ALL) const;
|
||||
///< ファイル名を検索
|
||||
///< Find file name
|
||||
const CHostFilename* FindFilenameWildcard(const BYTE* szHuman, DWORD nHumanAttribute, find_t* pFind) const;
|
||||
///< ファイル名を検索 (ワイルドカード対応)
|
||||
BOOL isRefresh(); ///< ファイル変更が行なわれたか確認
|
||||
void Refresh(); ///< ファイル再構成
|
||||
void Backup(); /// ホスト側のタイムスタンプを保存
|
||||
void Restore() const; /// ホスト側のタイムスタンプを復元
|
||||
void Release(); ///< 更新
|
||||
///< Find file name (with support for wildcards)
|
||||
BOOL isRefresh(); ///< Check that the file change has been done
|
||||
void Refresh(); ///< Refresh file
|
||||
void Backup(); /// Backup the time stamp on the host side
|
||||
void Restore() const; /// Restore the time stamp on the host side
|
||||
void Release(); ///< Update
|
||||
|
||||
// CHostEntryが利用する外部API
|
||||
static void InitId() { g_nId = 0; } ///< 識別ID生成用カウンタ初期化
|
||||
// CHostEntry is an external API that we use
|
||||
static void InitId() { g_nId = 0; } ///< Initialize the counter for the unique ID generation
|
||||
|
||||
private:
|
||||
static ring_t* Alloc(size_t nLength); ///< ファイル名領域確保
|
||||
static void Free(ring_t* pRing); ///< ファイル名領域解放
|
||||
static ring_t* Alloc(size_t nLength); ///< Allocate memory for the file name
|
||||
static void Free(ring_t* pRing); ///< Release memory for the file name
|
||||
static int Compare(const BYTE* pFirst, const BYTE* pLast, const BYTE* pBufFirst, const BYTE* pBufLast);
|
||||
///< 文字列比較 (ワイルドカード対応)
|
||||
///< Compare string (with support for wildcards)
|
||||
|
||||
CRing m_cRing; ///< CHostFilename連結用
|
||||
time_t m_tBackup; ///< 時刻復元用
|
||||
BOOL m_bRefresh; ///< 更新フラグ
|
||||
DWORD m_nId; ///< 識別ID (値が変化した場合は更新を意味する)
|
||||
BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< 該当エントリのHuman68k内部名
|
||||
TCHAR m_szHost[FILEPATH_MAX]; ///< 該当エントリのホスト側の名称
|
||||
CRing m_cRing; ///< For CHostFilename linking
|
||||
time_t m_tBackup; ///< For time stamp restoration
|
||||
BOOL m_bRefresh; ///< Refresh flag
|
||||
DWORD m_nId; ///< Unique ID (When the value has changed, it means an update has been made)
|
||||
BYTE m_szHuman[HUMAN68K_PATH_MAX]; ///< The internal Human68k name for the relevant entry
|
||||
TCHAR m_szHost[FILEPATH_MAX]; ///< The host side name for the relevant entry
|
||||
|
||||
static DWORD g_nId; ///< 識別ID生成用カウンタ
|
||||
static DWORD g_nId; ///< Counter for the unique ID generation
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ファイル検索処理
|
||||
/// File search processing
|
||||
///
|
||||
/// Human68k側のファイル名を内部Unicodeで処理するのは正直キツい。と
|
||||
/// いうわけで、全てBYTEに変換して処理する。変換処理はディレクトリエ
|
||||
/// ントリキャッシュが一手に担い、WINDRV側はすべてシフトJISのみで扱
|
||||
/// えるようにする。
|
||||
/// また、Human68k側名称は、完全にベースパス指定から独立させる。
|
||||
/// It's pretty much impossible to process Human68k file names as Unicode internally.
|
||||
/// So, we carry out binary conversion for processing. We leave it up to the
|
||||
/// directory entry cache to handle the conversion, which allows WINDRV to read
|
||||
/// everything as Shift-JIS. Additionally, it allows Human68k names to be
|
||||
/// fully independent of base path assignments.
|
||||
///
|
||||
/// ファイルを扱う直前に、ディレクトリエントリのキャッシュを生成する。
|
||||
/// ディレクトリエントリの生成処理は高コストのため、一度生成したエントリは
|
||||
/// 可能な限り維持して使い回す。
|
||||
/// We create directory entry cache just before file handling.
|
||||
/// Since creating directory entires is very costly, we try to reuse created entries
|
||||
/// as much as humanly possible.
|
||||
///
|
||||
/// ファイル検索は3方式。すべてCHostFiles::Find()で処理する。
|
||||
/// 1. パス名のみ検索 属性はディレクトリのみ _CHKDIR _CREATE
|
||||
/// 2. パス名+ファイル名+属性の検索 _OPEN
|
||||
/// 3. パス名+ワイルドカード+属性の検索 _FILES _NFILES
|
||||
/// 検索結果は、ディレクトリエントリ情報として保持しておく。
|
||||
/// There are three kinds of file search. They are all processed in CHostFiles::Find()
|
||||
/// 1. Search by path name only; the only attribute is 'directory'; _CHKDIR _CREATE
|
||||
/// 2. Path + file name + attribute search; _OPEN
|
||||
/// 3. Path + wildcard + attribute search; _FILES _NFILES
|
||||
/// The search results are kept as directory entry data.
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostFiles {
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CHostFiles() { SetKey(0); Init(); } ///< デフォルトコンストラクタ
|
||||
void Init(); ///< 初期化
|
||||
CHostFiles() { SetKey(0); Init(); }
|
||||
void Init();
|
||||
|
||||
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< 検索キー設定
|
||||
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< 検索キー比較
|
||||
void SetPath(const Human68k::namests_t* pNamests); ///< パス名・ファイル名を内部で生成
|
||||
BOOL isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< ルートディレクトリ判定
|
||||
void SetPathWildcard() { m_nHumanWildcard = 1; } ///< ワイルドカードによるファイル検索を有効化
|
||||
void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< パス名のみを有効化
|
||||
BOOL isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< パス名のみ設定か判定
|
||||
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
|
||||
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
|
||||
void SetPath(const Human68k::namests_t* pNamests); ///< Create path and file name internally
|
||||
BOOL isRootPath() const { return m_szHumanPath[1] == '\0'; } ///< Check if root directory
|
||||
void SetPathWildcard() { m_nHumanWildcard = 1; } ///< Enable file search using wildcards
|
||||
void SetPathOnly() { m_nHumanWildcard = 0xFF; } ///< Enable only path names
|
||||
BOOL isPathOnly() const { return m_nHumanWildcard == 0xFF; } ///< Check if set to only path names
|
||||
void SetAttribute(DWORD nHumanAttribute) { m_nHumanAttribute = nHumanAttribute; }
|
||||
///< 検索属性を設定
|
||||
BOOL Find(DWORD nUnit, class CHostEntry* pEntry); ///< Human68k側でファイルを検索しホスト側の情報を生成
|
||||
const CHostFilename* Find(CHostPath* pPath); ///< ファイル名検索
|
||||
void SetEntry(const CHostFilename* pFilename); ///< Human68k側の検索結果保存
|
||||
void SetResult(const TCHAR* szPath); ///< ホスト側の名称を設定
|
||||
void AddResult(const TCHAR* szPath); ///< ホスト側の名称にファイル名を追加
|
||||
void AddFilename(); ///< ホスト側の名称にHuman68kの新規ファイル名を追加
|
||||
///< Set search attribute
|
||||
BOOL Find(DWORD nUnit, class CHostEntry* pEntry); ///< Find files on the Human68k side, generating data on the host side
|
||||
const CHostFilename* Find(CHostPath* pPath); ///< Find file name
|
||||
void SetEntry(const CHostFilename* pFilename); ///< Store search results on the Human68k side
|
||||
void SetResult(const TCHAR* szPath); ///< Set names on the host side
|
||||
void AddResult(const TCHAR* szPath); ///< Add file name to the name on the host side
|
||||
void AddFilename(); ///< Add the new Human68k file name to the name on the host side
|
||||
|
||||
const TCHAR* GetPath() const { return m_szHostResult; } ///< ホスト側の名称を取得
|
||||
const TCHAR* GetPath() const { return m_szHostResult; } ///< Get the name on the host side
|
||||
|
||||
const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Human68kディレクトリエントリを取得
|
||||
const Human68k::dirent_t* GetEntry() const { return &m_dirHuman; }///< Get Human68k directory entry
|
||||
|
||||
DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Human68k属性を取得
|
||||
WORD GetDate() const { return m_dirHuman.date; } ///< Human68k日付を取得
|
||||
WORD GetTime() const { return m_dirHuman.time; } ///< Human68k時刻を取得
|
||||
DWORD GetSize() const { return m_dirHuman.size; } ///< Human68kファイルサイズを取得
|
||||
const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Human68kファイル名を取得
|
||||
const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Human68kファイル名検索結果を取得
|
||||
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Human68kパス名を取得
|
||||
DWORD GetAttribute() const { return m_dirHuman.attr; } ///< Get Human68k attribute
|
||||
WORD GetDate() const { return m_dirHuman.date; } ///< Get Human68k date
|
||||
WORD GetTime() const { return m_dirHuman.time; } ///< Get Human68k time
|
||||
DWORD GetSize() const { return m_dirHuman.size; } ///< Get Human68k file size
|
||||
const BYTE* GetHumanFilename() const { return m_szHumanFilename; }///< Get Human68k file name
|
||||
const BYTE* GetHumanResult() const { return m_szHumanResult; } ///< Get Human68k file name search results
|
||||
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
|
||||
|
||||
private:
|
||||
DWORD m_nKey; ///< Human68kのFILESバッファアドレス 0なら未使用
|
||||
DWORD m_nHumanWildcard; ///< Human68kのワイルドカード情報
|
||||
DWORD m_nHumanAttribute; ///< Human68kの検索属性
|
||||
CHostPath::find_t m_findNext; ///< 次回検索位置情報
|
||||
Human68k::dirent_t m_dirHuman; ///< 検索結果 Human68kファイル情報
|
||||
BYTE m_szHumanFilename[24]; ///< Human68kのファイル名
|
||||
BYTE m_szHumanResult[24]; ///< 検索結果 Human68kファイル名
|
||||
DWORD m_nKey; ///< FILES buffer address for Human68k; 0 is unused
|
||||
DWORD m_nHumanWildcard; ///< Human68k wildcard data
|
||||
DWORD m_nHumanAttribute; ///< Human68k search attribute
|
||||
CHostPath::find_t m_findNext; ///< Next search location data
|
||||
Human68k::dirent_t m_dirHuman; ///< Search results: Human68k file data
|
||||
BYTE m_szHumanFilename[24]; ///< Human68k file name
|
||||
BYTE m_szHumanResult[24]; ///< Search results: Human68k file name
|
||||
BYTE m_szHumanPath[HUMAN68K_PATH_MAX];
|
||||
///< Human68kのパス名
|
||||
TCHAR m_szHostResult[FILEPATH_MAX]; ///< 検索結果 ホスト側のフルパス名
|
||||
///< Human68k path name
|
||||
TCHAR m_szHostResult[FILEPATH_MAX]; ///< Search results: host's full path name
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ファイル検索領域 マネージャ
|
||||
/// File search memory manager
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostFilesManager {
|
||||
public:
|
||||
#ifdef _DEBUG
|
||||
// 基本ファンクション
|
||||
~CHostFilesManager(); ///< デストラクタ final
|
||||
~CHostFilesManager();
|
||||
#endif // _DEBUG
|
||||
void Init(); ///< 初期化 (ドライバ組込み時)
|
||||
void Clean(); ///< 解放 (起動・リセット時)
|
||||
void Init(); ///< Initialization (when the driver is installed)
|
||||
void Clean(); ///< Release (when starting up or resetting)
|
||||
|
||||
CHostFiles* Alloc(DWORD nKey); ///< 確保
|
||||
CHostFiles* Search(DWORD nKey); ///< 検索
|
||||
void Free(CHostFiles* pFiles); ///< 解放
|
||||
CHostFiles* Alloc(DWORD nKey);
|
||||
CHostFiles* Search(DWORD nKey);
|
||||
void Free(CHostFiles* pFiles);
|
||||
private:
|
||||
/// メモリ管理用
|
||||
/// For memory management
|
||||
struct ring_t {
|
||||
CRing r; ///< 円環
|
||||
CHostFiles f; ///< 実体
|
||||
CRing r;
|
||||
CHostFiles f;
|
||||
};
|
||||
|
||||
CRing m_cRing; ///< CHostFiles連結用
|
||||
CRing m_cRing; ///< For attaching to CHostFiles
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// FCB処理
|
||||
/// FCB processing
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostFcb {
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CHostFcb() { SetKey(0); Init(); } ///< デフォルトコンストラクタ
|
||||
~CHostFcb() { Close(); } ///< デストラクタ final
|
||||
void Init(); ///< 初期化
|
||||
CHostFcb() { SetKey(0); Init(); }
|
||||
~CHostFcb() { Close(); }
|
||||
void Init();
|
||||
|
||||
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< 検索キー設定
|
||||
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< 検索キー比較
|
||||
void SetUpdate() { m_bUpdate = TRUE; } ///< 更新
|
||||
BOOL isUpdate() const { return m_bUpdate; } ///< 更新状態取得
|
||||
BOOL SetMode(DWORD nHumanMode); ///< ファイルオープンモードを設定
|
||||
void SetFilename(const TCHAR* szFilename); ///< ファイル名を設定
|
||||
void SetHumanPath(const BYTE* szHumanPath); ///< Human68kパス名を設定
|
||||
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Human68kパス名を取得
|
||||
void SetKey(DWORD nKey) { m_nKey = nKey; } ///< Set search key
|
||||
BOOL isSameKey(DWORD nKey) const { return m_nKey == nKey; } ///< Compare search key
|
||||
void SetUpdate() { m_bUpdate = TRUE; } ///< Update
|
||||
BOOL isUpdate() const { return m_bUpdate; } ///< Get update state
|
||||
BOOL SetMode(DWORD nHumanMode); ///< Set file open mode
|
||||
void SetFilename(const TCHAR* szFilename); ///< Set file name
|
||||
void SetHumanPath(const BYTE* szHumanPath); ///< Set Human68k path name
|
||||
const BYTE* GetHumanPath() const { return m_szHumanPath; } ///< Get Human68k path name
|
||||
|
||||
BOOL Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); ///< ファイル作成
|
||||
BOOL Open(); ///< ファイルオープン
|
||||
BOOL Rewind(DWORD nOffset); ///< ファイルシーク
|
||||
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< ファイル読み込み
|
||||
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< ファイル書き込み
|
||||
BOOL Truncate(); ///< ファイル切り詰め
|
||||
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< ファイルシーク
|
||||
BOOL TimeStamp(DWORD nHumanTime); ///< ファイル時刻設定
|
||||
BOOL Close(); ///< ファイルクローズ
|
||||
BOOL Create(Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce); ///< Create file
|
||||
BOOL Open(); ///< Open file
|
||||
BOOL Rewind(DWORD nOffset); ///< Seek file
|
||||
DWORD Read(BYTE* pBuffer, DWORD nSize); ///< Read file
|
||||
DWORD Write(const BYTE* pBuffer, DWORD nSize); ///< Write file
|
||||
BOOL Truncate(); ///< Truncate file
|
||||
DWORD Seek(DWORD nOffset, DWORD nHumanSeek); ///< Seek file
|
||||
BOOL TimeStamp(DWORD nHumanTime); ///< Set file time stamp
|
||||
BOOL Close(); ///< Close file
|
||||
|
||||
private:
|
||||
DWORD m_nKey; ///< Human68kのFCBバッファアドレス (0なら未使用)
|
||||
BOOL m_bUpdate; ///< 更新フラグ
|
||||
FILE* m_pFile; ///< ホスト側のファイルオブジェクト
|
||||
const char* m_pszMode; ///< ホスト側のファイルオープンモード
|
||||
bool m_bFlag; ///< ホスト側のファイルオープンフラグ
|
||||
DWORD m_nKey; ///< Human68k FCB buffer address (0 if unused)
|
||||
BOOL m_bUpdate; ///< Update flag
|
||||
FILE* m_pFile; ///< Host side file object
|
||||
const char* m_pszMode; ///< Host side file open mode
|
||||
bool m_bFlag; ///< Host side file open flag
|
||||
BYTE m_szHumanPath[HUMAN68K_PATH_MAX];
|
||||
///< Human68kのパス名
|
||||
TCHAR m_szFilename[FILEPATH_MAX]; ///< ホスト側のファイル名
|
||||
///< Human68k path name
|
||||
TCHAR m_szFilename[FILEPATH_MAX]; ///< Host side file name
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// FCB処理 マネージャ
|
||||
/// FCB processing manager
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostFcbManager {
|
||||
public:
|
||||
#ifdef _DEBUG
|
||||
// 基本ファンクション
|
||||
~CHostFcbManager(); ///< デストラクタ final
|
||||
~CHostFcbManager();
|
||||
#endif // _DEBUG
|
||||
void Init(); ///< 初期化 (ドライバ組込み時)
|
||||
void Clean(); ///< 解放 (起動・リセット時)
|
||||
void Init(); ///< Initialization (when the driver is installed)
|
||||
void Clean(); ///< Release (when starting up or resetting)
|
||||
|
||||
CHostFcb* Alloc(DWORD nKey); ///< 確保
|
||||
CHostFcb* Search(DWORD nKey); ///< 検索
|
||||
void Free(CHostFcb* p); ///< 解放
|
||||
CHostFcb* Alloc(DWORD nKey);
|
||||
CHostFcb* Search(DWORD nKey);
|
||||
void Free(CHostFcb* p);
|
||||
|
||||
private:
|
||||
/// メモリ管理用
|
||||
/// For memory management
|
||||
struct ring_t {
|
||||
CRing r; ///< 円環
|
||||
CHostFcb f; ///< 実体
|
||||
CRing r;
|
||||
CHostFcb f;
|
||||
};
|
||||
|
||||
CRing m_cRing; ///< CHostFcb連結用
|
||||
CRing m_cRing; ///< For attaching to CHostFcb
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ホスト側ドライブ
|
||||
/// Host side drive
|
||||
///
|
||||
/// ドライブ毎に必要な情報の保持に専念し、管理はCHostEntryで行なう。
|
||||
/// Keeps the required data for each drive, managed in CHostEntry.
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostDrv
|
||||
{
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CHostDrv(); ///< デフォルトコンストラクタ
|
||||
~CHostDrv(); ///< デストラクタ final
|
||||
void Init(const TCHAR* szBase, DWORD nFlag); ///< 初期化 (デバイス起動とロード)
|
||||
CHostDrv();
|
||||
~CHostDrv();
|
||||
void Init(const TCHAR* szBase, DWORD nFlag); ///< Initialization (device startup and load)
|
||||
|
||||
BOOL isWriteProtect() const { return m_bWriteProtect; } ///< 書き込み禁止か?
|
||||
BOOL isEnable() const { return m_bEnable; } ///< アクセス可能か?
|
||||
BOOL isMediaOffline(); ///< メディアチェック
|
||||
BYTE GetMediaByte() const; ///< メディアバイトの取得
|
||||
DWORD GetStatus() const; ///< ドライブ状態の取得
|
||||
void SetEnable(BOOL bEnable); ///< メディア状態設定
|
||||
BOOL CheckMedia(); ///< メディア交換チェック
|
||||
void Update(); ///< メディア状態更新
|
||||
void Eject(); ///< イジェクト
|
||||
void GetVolume(TCHAR* szLabel); ///< ボリュームラベルの取得
|
||||
BOOL GetVolumeCache(TCHAR* szLabel) const; ///< キャッシュからボリュームラベルを取得
|
||||
DWORD GetCapacity(Human68k::capacity_t* pCapacity); ///< 容量の取得
|
||||
BOOL GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< キャッシュから容量を取得
|
||||
BOOL isWriteProtect() const { return m_bWriteProtect; }
|
||||
BOOL isEnable() const { return m_bEnable; } ///< Is it accessible?
|
||||
BOOL isMediaOffline();
|
||||
BYTE GetMediaByte() const;
|
||||
DWORD GetStatus() const;
|
||||
void SetEnable(BOOL bEnable); ///< Set media status
|
||||
BOOL CheckMedia(); ///< Check if media was changed
|
||||
void Update(); ///< Update media status
|
||||
void Eject();
|
||||
void GetVolume(TCHAR* szLabel); ///< Get volume label
|
||||
BOOL GetVolumeCache(TCHAR* szLabel) const; ///< Get volume label from cache
|
||||
DWORD GetCapacity(Human68k::capacity_t* pCapacity);
|
||||
BOOL GetCapacityCache(Human68k::capacity_t* pCapacity) const; ///< Get capacity from cache
|
||||
|
||||
// キャッシュ操作
|
||||
void CleanCache(); ///< 全てのキャッシュを更新する
|
||||
void CleanCache(const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを更新する
|
||||
void CleanCacheChild(const BYTE* szHumanPath); ///< 指定されたパス以下のキャッシュを全て更新する
|
||||
void DeleteCache(const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを削除する
|
||||
CHostPath* FindCache(const BYTE* szHuman); ///< 指定されたパスがキャッシュされているか検索する
|
||||
CHostPath* CopyCache(CHostFiles* pFiles); ///< キャッシュ情報を元に、ホスト側の名称を獲得する
|
||||
CHostPath* MakeCache(CHostFiles* pFiles); ///< ホスト側の名称の構築に必要な情報をすべて取得する
|
||||
BOOL Find(CHostFiles* pFiles); ///< ホスト側の名称を検索 (パス名+ファイル名(省略可)+属性)
|
||||
// Cache operations
|
||||
void CleanCache(); ///< Update all cache
|
||||
void CleanCache(const BYTE* szHumanPath); ///< Update cache for the specified path
|
||||
void CleanCacheChild(const BYTE* szHumanPath); ///< Update all cache below the specified path
|
||||
void DeleteCache(const BYTE* szHumanPath); ///< Delete the cache for the specified path
|
||||
CHostPath* FindCache(const BYTE* szHuman); ///< Inspect if the specified path is cached
|
||||
CHostPath* CopyCache(CHostFiles* pFiles); ///< Acquire the host side name on the basis of cache information
|
||||
CHostPath* MakeCache(CHostFiles* pFiles); ///< Get all required data to construct a host side name
|
||||
BOOL Find(CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute)
|
||||
|
||||
private:
|
||||
// パス名操作
|
||||
// Path name operations
|
||||
static const BYTE* SeparateCopyFilename(const BYTE* szHuman, BYTE* szBuffer);
|
||||
///< Human68kフルパス名から先頭の要素を分離・コピー
|
||||
///< Split and copy the first element of the Human68k full path name
|
||||
|
||||
// 排他制御
|
||||
void Lock() {}
|
||||
void Unlock() {}
|
||||
|
||||
/// メモリ管理用
|
||||
/// For memory management
|
||||
struct ring_t {
|
||||
CRing r; ///< 円環
|
||||
CHostPath f; ///< 実体
|
||||
CRing r;
|
||||
CHostPath f;
|
||||
};
|
||||
|
||||
BOOL m_bWriteProtect; ///< 書き込み禁止ならTRUE
|
||||
BOOL m_bEnable; ///< メディアが利用可能ならTRUE
|
||||
DWORD m_nRing; ///< パス名保持数
|
||||
CRing m_cRing; ///< CHostPath連結用
|
||||
Human68k::capacity_t m_capCache; ///< セクタ情報キャッシュ sectors == 0 なら未キャッシュ
|
||||
BOOL m_bVolumeCache; ///< ボリュームラベル読み込み済みならTRUE
|
||||
TCHAR m_szVolumeCache[24]; ///< ボリュームラベルキャッシュ
|
||||
TCHAR m_szBase[FILEPATH_MAX]; ///< ベースパス
|
||||
BOOL m_bWriteProtect; ///< TRUE if write-protected
|
||||
BOOL m_bEnable; ///< TRUE if media is usable
|
||||
DWORD m_nRing; ///< Number of stored path names
|
||||
CRing m_cRing; ///< For attaching to CHostPath
|
||||
Human68k::capacity_t m_capCache; ///< Sector data cache: if "sectors == 0" then not cached
|
||||
BOOL m_bVolumeCache; ///< TRUE if the volume label has been read
|
||||
TCHAR m_szVolumeCache[24]; ///< Volume label cache
|
||||
TCHAR m_szBase[FILEPATH_MAX]; ///< Base path
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ディレクトリエントリ管理
|
||||
/// Directory entry management
|
||||
//
|
||||
//===========================================================================
|
||||
class CHostEntry {
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CHostEntry(); ///< デフォルトコンストラクタ
|
||||
~CHostEntry(); ///< デストラクタ final
|
||||
void Init(); ///< 初期化 (ドライバ組込み時)
|
||||
void Clean(); ///< 解放 (起動・リセット時)
|
||||
CHostEntry();
|
||||
~CHostEntry();
|
||||
void Init(); ///< Initialization (when the driver is installed)
|
||||
void Clean(); ///< Release (when starting up or resetting)
|
||||
|
||||
// キャッシュ操作
|
||||
void CleanCache(); ///< 全てのキャッシュを更新する
|
||||
void CleanCache(DWORD nUnit); ///< 指定されたユニットのキャッシュを更新する
|
||||
void CleanCache(DWORD nUnit, const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを更新する
|
||||
void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath); ///< 指定されたパス以下のキャッシュを全て更新する
|
||||
void DeleteCache(DWORD nUnit, const BYTE* szHumanPath); ///< 指定されたパスのキャッシュを削除する
|
||||
BOOL Find(DWORD nUnit, CHostFiles* pFiles); ///< ホスト側の名称を検索 (パス名+ファイル名(省略可)+属性)
|
||||
void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< ホスト側ファイルシステム状態変化通知
|
||||
// Cache operations
|
||||
void CleanCache(); ///< Update all cache
|
||||
void CleanCache(DWORD nUnit); ///< Update cache for the specified unit
|
||||
void CleanCache(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache for the specified path
|
||||
void CleanCacheChild(DWORD nUnit, const BYTE* szHumanPath); ///< Update cache below the specified path
|
||||
void DeleteCache(DWORD nUnit, const BYTE* szHumanPath); ///< Delete cache for the specified path
|
||||
BOOL Find(DWORD nUnit, CHostFiles* pFiles); ///< Find host side name (path + file name (can be abbreviated) + attribute)
|
||||
void ShellNotify(DWORD nEvent, const TCHAR* szPath); ///< Notify status change in the host side file system
|
||||
|
||||
// ドライブオブジェクト操作
|
||||
void SetDrv(DWORD nUnit, CHostDrv* pDrv); ///< ドライブ設定
|
||||
BOOL isWriteProtect(DWORD nUnit) const; ///< 書き込み禁止か?
|
||||
BOOL isEnable(DWORD nUnit) const; ///< アクセス可能か?
|
||||
BOOL isMediaOffline(DWORD nUnit); ///< メディアチェック
|
||||
BYTE GetMediaByte(DWORD nUnit) const; ///< メディアバイトの取得
|
||||
DWORD GetStatus(DWORD nUnit) const; ///< ドライブ状態の取得
|
||||
BOOL CheckMedia(DWORD nUnit); ///< メディア交換チェック
|
||||
void Eject(DWORD nUnit); ///< イジェクト
|
||||
void GetVolume(DWORD nUnit, TCHAR* szLabel); ///< ボリュームラベルの取得
|
||||
BOOL GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< キャッシュからボリュームラベルを取得
|
||||
DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< 容量の取得
|
||||
// Drive object operations
|
||||
void SetDrv(DWORD nUnit, CHostDrv* pDrv);
|
||||
BOOL isWriteProtect(DWORD nUnit) const;
|
||||
BOOL isEnable(DWORD nUnit) const; ///< Is it accessible?
|
||||
BOOL isMediaOffline(DWORD nUnit);
|
||||
BYTE GetMediaByte(DWORD nUnit) const;
|
||||
DWORD GetStatus(DWORD nUnit) const; ///< Get drive status
|
||||
BOOL CheckMedia(DWORD nUnit); ///< Media change check
|
||||
void Eject(DWORD nUnit);
|
||||
void GetVolume(DWORD nUnit, TCHAR* szLabel); ///< Get volume label
|
||||
BOOL GetVolumeCache(DWORD nUnit, TCHAR* szLabel) const; ///< Get volume label from cache
|
||||
DWORD GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity);
|
||||
BOOL GetCapacityCache(DWORD nUnit, Human68k::capacity_t* pCapacity) const;
|
||||
///< キャッシュからクラスタサイズを取得
|
||||
///< Get cluster size from cache
|
||||
|
||||
/// 定数
|
||||
enum {
|
||||
DriveMax = 10 ///< ドライブ最大候補数
|
||||
DriveMax = 10 ///< Max number of drive candidates
|
||||
};
|
||||
|
||||
private:
|
||||
CHostDrv* m_pDrv[DriveMax]; ///< ホスト側ドライブオブジェクト
|
||||
DWORD m_nTimeout; ///< 最後にタイムアウトチェックを行なった時刻
|
||||
CHostDrv* m_pDrv[DriveMax]; ///< Host side drive object
|
||||
DWORD m_nTimeout; ///< Last time a timeout check was carried out
|
||||
};
|
||||
|
||||
//===========================================================================
|
||||
//
|
||||
/// ホスト側ファイルシステム
|
||||
/// Host side file system
|
||||
//
|
||||
//===========================================================================
|
||||
/** @note
|
||||
現在の見解。
|
||||
Current state of affairs:
|
||||
|
||||
XM6の設計思想とは反するが、class Windrvまたはclass CWindrvに直接
|
||||
class CFileSysへのポインタを持たせる方法を模索するべきである。
|
||||
これにより、以下のメリットが得られる。
|
||||
While it violates the design philosophy of XM6, we should find a way for
|
||||
'class Windrv' and 'class CWindrv' to have a direct pointer to 'class CFileSys'.
|
||||
This way, we get the following benefits.
|
||||
|
||||
メリットその1。
|
||||
コマンドハンドラの大量のメソッド群を一ヶ所で集中管理できる。
|
||||
コマンドハンドラはホスト側の仕様変更などの要因によって激しく変化する
|
||||
可能性が高いため、メソッドの追加削除や引数の変更などのメンテナンスが
|
||||
大幅に楽になる。
|
||||
Benefit no. 1
|
||||
Makes it possible to manage a large number of command handler methods in one place.
|
||||
There is a high chance the command handlers will change drastically because of
|
||||
host system architectural changes, so we will save a huge amount of maintenance work
|
||||
in the long run.
|
||||
|
||||
メリットその2。
|
||||
仮想関数のテーブル生成・参照処理に関する処理コードを駆逐できる。
|
||||
XM6では複数のファイルシステムオブジェクトを同時に使うような実装は
|
||||
ありえない。つまりファイルシステムオブジェクトにポリモーフィズムは
|
||||
まったく必要ないどころか、ただクロックの無駄となっているだけである。
|
||||
Benefit no. 2
|
||||
We would get rid of virtual funcion code for processing table creation and lookup.
|
||||
It is not feasible to implement code in XM6 for simultaneous use of file system objects.
|
||||
Therefore file system object polymorphism is a waste of CPU cycles.
|
||||
|
||||
試しに変えてみた。実際効率上がった。
|
||||
windrv.h内のFILESYS_FAST_STRUCTUREの値を変えてコンパイラの吐くソース
|
||||
を比較すれば一目瞭然。何故私がこんな長文を書こうと思ったのかを理解で
|
||||
きるはず。
|
||||
I made the change as an experiment. Performance did improve.
|
||||
The improvement was obvious from looking at the source the compiler spit out
|
||||
after changing the FILESYS_FAST_STRUCTURE value in windrv.h.
|
||||
You may understand now why I decided to rant here.
|
||||
|
||||
一方ロシアはclass CWindrv内にclass CFileSysを直接設置した。
|
||||
(本当はclass CHostを廃止して直接置きたい……良い方法はないものか……)
|
||||
The easy solution is to put the content of 'class CFileSys' into 'class CWindrv'.
|
||||
(To be honest, I really want to deprecate 'class CHost'... I wonder if there's a good way...)
|
||||
*/
|
||||
class CFileSys
|
||||
{
|
||||
public:
|
||||
// 基本ファンクション
|
||||
CFileSys(); ///< デフォルトコンストラクタ
|
||||
virtual ~CFileSys() {}; ///< デストラクタ
|
||||
CFileSys();
|
||||
virtual ~CFileSys() {};
|
||||
|
||||
// 初期化・終了
|
||||
void Reset(); ///< リセット (全クローズ)
|
||||
void Init(); ///< 初期化 (デバイス起動とロード)
|
||||
void Reset(); ///< Reset (close all)
|
||||
void Init(); ///< Initialization (device startup and load)
|
||||
|
||||
// コマンドハンドラ
|
||||
DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - デバイス起動
|
||||
int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $41 - ディレクトリチェック
|
||||
int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $42 - ディレクトリ作成
|
||||
int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $43 - ディレクトリ削除
|
||||
// Command handlers
|
||||
DWORD InitDevice(const Human68k::argument_t* pArgument); ///< $40 - Device startup
|
||||
int CheckDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $41 - Directory check
|
||||
int MakeDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $42 - Create directory
|
||||
int RemoveDir(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $43 - Delete directory
|
||||
int Rename(DWORD nUnit, const Human68k::namests_t* pNamests, const Human68k::namests_t* pNamestsNew);
|
||||
///< $44 - ファイル名変更
|
||||
int Delete(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $45 - ファイル削除
|
||||
///< $44 - Change file name
|
||||
int Delete(DWORD nUnit, const Human68k::namests_t* pNamests); ///< $45 - Delete file
|
||||
int Attribute(DWORD nUnit, const Human68k::namests_t* pNamests, DWORD nHumanAttribute);
|
||||
///< $46 - ファイル属性取得/設定
|
||||
///< $46 - Get / set file attribute
|
||||
int Files(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::files_t* pFiles);
|
||||
///< $47 - ファイル検索
|
||||
int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - ファイル次検索
|
||||
///< $47 - Find file
|
||||
int NFiles(DWORD nUnit, DWORD nKey, Human68k::files_t* pFiles); ///< $48 - Find next file
|
||||
int Create(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb, DWORD nHumanAttribute, BOOL bForce);
|
||||
///< $49 - ファイル作成
|
||||
///< $49 - Create file
|
||||
int Open(DWORD nUnit, DWORD nKey, const Human68k::namests_t* pNamests, Human68k::fcb_t* pFcb);
|
||||
///< $4A - ファイルオープン
|
||||
int Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb); ///< $4B - ファイルクローズ
|
||||
///< $4A - Open file
|
||||
int Close(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb); ///< $4B - Close file
|
||||
int Read(DWORD nKey, Human68k::fcb_t* pFcb, BYTE* pAddress, DWORD nSize);
|
||||
///< $4C - ファイル読み込み
|
||||
///< $4C - Read file
|
||||
int Write(DWORD nKey, Human68k::fcb_t* pFcb, const BYTE* pAddress, DWORD nSize);
|
||||
///< $4D - ファイル書き込み
|
||||
int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - ファイルシーク
|
||||
///< $4D - Write file
|
||||
int Seek(DWORD nKey, Human68k::fcb_t* pFcb, DWORD nSeek, int nOffset); ///< $4E - Seek file
|
||||
DWORD TimeStamp(DWORD nUnit, DWORD nKey, Human68k::fcb_t* pFcb, DWORD nHumanTime);
|
||||
///< $4F - ファイル時刻取得/設定
|
||||
int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< $50 - 容量取得
|
||||
int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive); ///< $51 - ドライブ状態検査/制御
|
||||
int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb); ///< $52 - DPB取得
|
||||
int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - セクタ読み込み
|
||||
int DiskWrite(DWORD nUnit); ///< $54 - セクタ書き込み
|
||||
///< $4F - Get / set file timestamp
|
||||
int GetCapacity(DWORD nUnit, Human68k::capacity_t* pCapacity); ///< $50 - Get capacity
|
||||
int CtrlDrive(DWORD nUnit, Human68k::ctrldrive_t* pCtrlDrive); ///< $51 - Inspect / control drive status
|
||||
int GetDPB(DWORD nUnit, Human68k::dpb_t* pDpb); ///< $52 - Get DPB
|
||||
int DiskRead(DWORD nUnit, BYTE* pBuffer, DWORD nSector, DWORD nSize); ///< $53 - Read sectors
|
||||
int DiskWrite(DWORD nUnit); ///< $54 - Write sectors
|
||||
int Ioctrl(DWORD nUnit, DWORD nFunction, Human68k::ioctrl_t* pIoctrl); ///< $55 - IOCTRL
|
||||
int Flush(DWORD nUnit); ///< $56 - フラッシュ
|
||||
int CheckMedia(DWORD nUnit); ///< $57 - メディア交換チェック
|
||||
int Lock(DWORD nUnit); ///< $58 - 排他制御
|
||||
int Flush(DWORD nUnit); ///< $56 - Flush
|
||||
int CheckMedia(DWORD nUnit); ///< $57 - Media change check
|
||||
int Lock(DWORD nUnit); ///< $58 - Lock
|
||||
|
||||
void SetOption(DWORD nOption); ///< オプション設定
|
||||
DWORD GetOption() const { return m_nOption; } ///< オプション取得
|
||||
DWORD GetDefault() const { return m_nOptionDefault; } ///< デフォルトオプション取得
|
||||
static DWORD GetFileOption() { return g_nOption; } ///< ファイル名変換オプション取得
|
||||
void SetOption(DWORD nOption); ///< Set option
|
||||
DWORD GetOption() const { return m_nOption; } ///< Get option
|
||||
DWORD GetDefault() const { return m_nOptionDefault; } ///< Get default options
|
||||
static DWORD GetFileOption() { return g_nOption; } ///< Get file name change option
|
||||
void ShellNotify(DWORD nEvent, const TCHAR* szPath)
|
||||
{ m_cEntry.ShellNotify(nEvent, szPath); } ///< ホスト側ファイルシステム状態変化通知
|
||||
{ m_cEntry.ShellNotify(nEvent, szPath); } ///< Notify host side file system status change
|
||||
|
||||
/// 定数
|
||||
enum {
|
||||
DriveMax = CHostEntry::DriveMax ///< ドライブ最大候補数
|
||||
DriveMax = CHostEntry::DriveMax ///< Max number of drive candidates
|
||||
};
|
||||
|
||||
private:
|
||||
// 内部補助用
|
||||
void InitOption(const Human68k::argument_t* pArgument); ///< オプション初期化
|
||||
BOOL FilesVolume(DWORD nUnit, Human68k::files_t* pFiles); ///< ボリュームラベル取得
|
||||
void InitOption(const Human68k::argument_t* pArgument);
|
||||
BOOL FilesVolume(DWORD nUnit, Human68k::files_t* pFiles); ///< Get volume label
|
||||
|
||||
DWORD m_nUnits; ///< 現在のドライブオブジェクト数 (レジューム毎に変化)
|
||||
DWORD m_nUnits; ///< Number of current drive objects (Changes for every resume)
|
||||
|
||||
DWORD m_nOption; ///< 現在の動作フラグ
|
||||
DWORD m_nOptionDefault; ///< リセット時の動作フラグ
|
||||
DWORD m_nOption; ///< Current runtime flag
|
||||
DWORD m_nOptionDefault; ///< Runtime flag at reset
|
||||
|
||||
DWORD m_nDrives; ///< ベースパス状態復元用の候補数 (0なら毎回スキャン)
|
||||
DWORD m_nDrives; ///< Number of candidates for base path status restoration (scan every time if 0)
|
||||
|
||||
DWORD m_nKernel; ///< カーネルチェック用カウンタ
|
||||
DWORD m_nKernelSearch; ///< NULデバイスの先頭アドレス
|
||||
DWORD m_nKernel; ///< Counter for kernel check
|
||||
DWORD m_nKernelSearch; ///< Initial address for NUL device
|
||||
|
||||
DWORD m_nHostSectorCount; ///< 擬似セクタ番号
|
||||
DWORD m_nHostSectorCount; ///< Virtual sector identifier
|
||||
|
||||
CHostFilesManager m_cFiles; ///< ファイル検索領域
|
||||
CHostFcbManager m_cFcb; ///< FCB操作領域
|
||||
CHostEntry m_cEntry; ///< ドライブオブジェクトとディレクトリエントリ
|
||||
CHostFilesManager m_cFiles; ///< File search memory
|
||||
CHostFcbManager m_cFcb; ///< FCB operation memory
|
||||
CHostEntry m_cEntry; ///< Drive object and directory entry
|
||||
|
||||
DWORD m_nHostSectorBuffer[XM6_HOST_PSEUDO_CLUSTER_MAX];
|
||||
///< 擬似セクタの指すファイル実体
|
||||
///< Entity that the virtual sector points to
|
||||
|
||||
DWORD m_nFlag[DriveMax]; ///< ベースパス状態復元用の動作フラグ候補
|
||||
TCHAR m_szBase[DriveMax][FILEPATH_MAX]; ///< ベースパス状態復元用の候補
|
||||
static DWORD g_nOption; ///< ファイル名変換フラグ
|
||||
DWORD m_nFlag[DriveMax]; ///< Candidate runtime flag for base path restoration
|
||||
TCHAR m_szBase[DriveMax][FILEPATH_MAX]; ///< Candidate for base path restoration
|
||||
static DWORD g_nOption; ///< File name change flag
|
||||
};
|
||||
|
@ -99,7 +99,7 @@ BOOL Fileio::Open(const char *fname, OpenMode mode, BOOL directIO)
|
||||
break;
|
||||
|
||||
case ReadWrite:
|
||||
// CD-ROMからの読み込みはRWが成功してしまう
|
||||
// Make sure RW does not succeed when reading from CD-ROM
|
||||
if (access(fname, 0x06) != 0) {
|
||||
return FALSE;
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import fnmatch
|
||||
import os
|
||||
import subprocess
|
||||
import time
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
import logging
|
||||
|
||||
from ractl_cmds import (
|
||||
attach_image,
|
||||
@ -14,6 +11,42 @@ from ractl_cmds import (
|
||||
from settings import *
|
||||
|
||||
|
||||
def list_files():
|
||||
from fnmatch import translate
|
||||
valid_file_types = list(VALID_FILE_SUFFIX)
|
||||
valid_file_types = ["*." + s for s in valid_file_types]
|
||||
valid_file_types = r"|".join([translate(x) for x in valid_file_types])
|
||||
|
||||
from re import match, IGNORECASE
|
||||
|
||||
files_list = []
|
||||
for path, dirs, files in os.walk(base_dir):
|
||||
# Only list valid file types
|
||||
files = [f for f in files if match(valid_file_types, f, IGNORECASE)]
|
||||
files_list.extend(
|
||||
[
|
||||
(
|
||||
os.path.join(path, file),
|
||||
"{:,.1f}".format(
|
||||
os.path.getsize(os.path.join(path, file)) / float(1 << 20),
|
||||
),
|
||||
os.path.getsize(os.path.join(path, file)),
|
||||
)
|
||||
for file in files
|
||||
]
|
||||
)
|
||||
return files_list
|
||||
|
||||
|
||||
def list_config_files():
|
||||
files_list = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for file in files:
|
||||
if file.endswith(".json"):
|
||||
files_list.append(file)
|
||||
return files_list
|
||||
|
||||
|
||||
def create_new_image(file_name, type, size):
|
||||
if file_name == "":
|
||||
file_name = "new_image." + str(int(time.time())) + "." + type
|
||||
@ -42,14 +75,6 @@ def unzip_file(file_name):
|
||||
return True
|
||||
|
||||
|
||||
def rascsi_service(action):
|
||||
# start/stop/restart
|
||||
return (
|
||||
subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def download_file_to_iso(scsi_id, url):
|
||||
import urllib.request
|
||||
|
||||
@ -60,14 +85,19 @@ def download_file_to_iso(scsi_id, url):
|
||||
tmp_full_path = tmp_dir + file_name
|
||||
iso_filename = base_dir + file_name + ".iso"
|
||||
|
||||
urllib.request.urlretrieve(url, tmp_full_path)
|
||||
try:
|
||||
urllib.request.urlretrieve(url, tmp_full_path)
|
||||
except:
|
||||
# TODO: Capture a more descriptive error message
|
||||
return {"status": False, "msg": "Error loading the URL"}
|
||||
|
||||
# iso_filename = make_cd(tmp_full_path, None, None) # not working yet
|
||||
iso_proc = subprocess.run(
|
||||
["genisoimage", "-hfs", "-o", iso_filename, tmp_full_path], capture_output=True
|
||||
)
|
||||
if iso_proc.returncode != 0:
|
||||
return iso_proc
|
||||
return attach_image(scsi_id, iso_filename, "SCCD")
|
||||
return {"status": False, "msg": iso_proc}
|
||||
return attach_image(scsi_id, type="SCCD", image=iso_filename)
|
||||
|
||||
|
||||
def download_image(url):
|
||||
@ -76,50 +106,69 @@ def download_image(url):
|
||||
file_name = url.split("/")[-1]
|
||||
full_path = base_dir + file_name
|
||||
|
||||
urllib.request.urlretrieve(url, full_path)
|
||||
|
||||
def write_config_csv(file_name):
|
||||
import csv
|
||||
|
||||
# This method takes the output of 'rasctl -l' and parses it into csv format:
|
||||
# 0: ID
|
||||
# 1: Unit Number (unused in rascsi-web)
|
||||
# 2: Device Type
|
||||
# 3: Device Status (includes the path to a loaded image file)
|
||||
# TODO: Remove the dependence on rasctl; e.g. when implementing protobuf for rascsi-web
|
||||
try:
|
||||
with open(file_name, "w") as csv_file:
|
||||
writer = csv.writer(csv_file)
|
||||
for device in list_devices():
|
||||
if device["type"] != "-":
|
||||
device_info = list (device.values())
|
||||
# Match a *nix file path inside column 3, cutting out the last chunk that starts with a space
|
||||
filesearch = re.search("(^(/[^/ ]*)+)(\s.*)*$", device_info[3])
|
||||
if filesearch is None:
|
||||
device_info[3] = ""
|
||||
else:
|
||||
device_info[3] = filesearch.group(1)
|
||||
writer.writerow(device_info)
|
||||
return True
|
||||
urllib.request.urlretrieve(url, full_path)
|
||||
return {"status": True, "msg": "Downloaded the URL"}
|
||||
except:
|
||||
print ("Could not open file for writing: ", file_name)
|
||||
return False
|
||||
|
||||
def read_config_csv(file_name):
|
||||
import csv
|
||||
# TODO: Capture a more descriptive error message
|
||||
return {"status": False, "msg": "Error loading the URL"}
|
||||
|
||||
|
||||
def write_config(file_name):
|
||||
from json import dump
|
||||
try:
|
||||
with open(file_name) as csv_file:
|
||||
with open(file_name, "w") as json_file:
|
||||
devices = list_devices()[0]
|
||||
for device in devices:
|
||||
# Remove keys that we don't want to store in the file
|
||||
del device["status"]
|
||||
del device["file"]
|
||||
# It's cleaner not to store an empty parameter for every device without media
|
||||
if device["image"] == "":
|
||||
device["image"] = None
|
||||
# RaSCSI product names will be generated on the fly by RaSCSI
|
||||
if device["vendor"] == "RaSCSI":
|
||||
device["vendor"] = device["product"] = device["revision"] = None
|
||||
# A block size of 0 is how RaSCSI indicates N/A for block size
|
||||
if device["block_size"] == 0:
|
||||
device["block_size"] = None
|
||||
# Convert to a data type that can be serialized
|
||||
device["params"] = list(device["params"])
|
||||
dump(devices, json_file, indent=4)
|
||||
return {"status": True, "msg": f"Successfully wrote to file: {file_name}"}
|
||||
#TODO: more verbose error handling of file system errors
|
||||
except:
|
||||
logging.error(f"Could not write to file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not write to file: {file_name}"}
|
||||
|
||||
|
||||
def read_config(file_name):
|
||||
from json import load
|
||||
try:
|
||||
with open(file_name) as json_file:
|
||||
detach_all()
|
||||
config_reader = csv.reader(csv_file)
|
||||
#TODO: Remove hard-coded string sanitation (e.g. after implementing protobuf)
|
||||
exclude_list = ("X68000 HOST BRIDGE", "DaynaPort SCSI/Link", " (WRITEPROTECT)", "NO MEDIA")
|
||||
for row in config_reader:
|
||||
image_name = row[3]
|
||||
for e in exclude_list:
|
||||
image_name = image_name.replace(e, "")
|
||||
attach_image(row[0], image_name, row[2])
|
||||
return True
|
||||
devices = load(json_file)
|
||||
for row in devices:
|
||||
process = attach_image(row["id"], device_type=row["device_type"], image=row["image"], unit=int(row["un"]), \
|
||||
params=row["params"], vendor=row["vendor"], product=row["product"], \
|
||||
revision=row["revision"], block_size=row["block_size"])
|
||||
if process["status"] == True:
|
||||
return {"status": process["status"], "msg": f"Successfully read from file: {file_name}"}
|
||||
else:
|
||||
return {"status": process["status"], "msg": process["msg"]}
|
||||
#TODO: more verbose error handling of file system errors
|
||||
except:
|
||||
print ("Could not access file: ", file_name)
|
||||
return False
|
||||
logging.error(f"Could not read file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not read file: {file_name}"}
|
||||
|
||||
|
||||
def read_device_config(file_name):
|
||||
from json import load
|
||||
try:
|
||||
with open(file_name) as json_file:
|
||||
conf = load(json_file)
|
||||
return {"status": True, "msg": f"Read data from file: {file_name}", "conf": conf}
|
||||
#TODO: more verbose error handling of file system errors
|
||||
except:
|
||||
logging.error(f"Could not read file: {file_name}")
|
||||
return {"status": False, "msg": f"Could not read file: {file_name}"}
|
||||
|
@ -29,3 +29,12 @@ def running_version():
|
||||
.strip()
|
||||
)
|
||||
return ra_web_version + " " + pi_version
|
||||
|
||||
|
||||
def is_bridge_setup():
|
||||
from subprocess import run
|
||||
process = run(["brctl", "show"], capture_output=True)
|
||||
output = process.stdout.decode("utf-8")
|
||||
if "rascsi_bridge" in output:
|
||||
return True
|
||||
return False
|
@ -1,164 +1,328 @@
|
||||
import fnmatch
|
||||
import subprocess
|
||||
import re
|
||||
import logging
|
||||
|
||||
from settings import *
|
||||
import rascsi_interface_pb2 as proto
|
||||
|
||||
|
||||
valid_file_suffix = ["*.hda", "*.hdn", "*.hdi", "*.nhd", "*.hdf", "*.hds", "*.hdr", "*.iso", "*.cdr", "*.toast", "*.img", "*.zip"]
|
||||
valid_file_types = r"|".join([fnmatch.translate(x) for x in valid_file_suffix])
|
||||
def get_server_info():
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.SERVER_INFO
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
version = str(result.server_info.major_version) + "." +\
|
||||
str(result.server_info.minor_version) + "." +\
|
||||
str(result.server_info.patch_version)
|
||||
log_levels = result.server_info.log_levels
|
||||
current_log_level = result.server_info.current_log_level
|
||||
return {"status": result.status, "version": version, "log_levels": log_levels, "current_log_level": current_log_level}
|
||||
|
||||
|
||||
def validate_scsi_id(scsi_id):
|
||||
from re import match
|
||||
if match("[0-7]", str(scsi_id)) != None:
|
||||
return {"status": True, "msg": "Valid SCSI ID."}
|
||||
else:
|
||||
return {"status": False, "msg": "Invalid SCSI ID. Should be a number between 0-7"}
|
||||
|
||||
|
||||
def is_active():
|
||||
process = subprocess.run(["systemctl", "is-active", "rascsi"], capture_output=True)
|
||||
return process.stdout.decode("utf-8").strip() == "active"
|
||||
|
||||
|
||||
def list_files():
|
||||
files_list = []
|
||||
for path, dirs, files in os.walk(base_dir):
|
||||
# Only list valid file types
|
||||
files = [f for f in files if re.match(valid_file_types, f)]
|
||||
files_list.extend(
|
||||
[
|
||||
(
|
||||
os.path.join(path, file),
|
||||
# TODO: move formatting to template
|
||||
"{:,.0f}".format(
|
||||
os.path.getsize(os.path.join(path, file)) / float(1 << 20)
|
||||
)
|
||||
+ " MB",
|
||||
)
|
||||
for file in files
|
||||
]
|
||||
)
|
||||
return files_list
|
||||
|
||||
|
||||
def list_config_files():
|
||||
files_list = []
|
||||
for root, dirs, files in os.walk(base_dir):
|
||||
for file in files:
|
||||
if file.endswith(".csv"):
|
||||
files_list.append(file)
|
||||
return files_list
|
||||
|
||||
|
||||
def get_valid_scsi_ids(devices, invalid_list):
|
||||
def get_valid_scsi_ids(devices, invalid_list, occupied_ids):
|
||||
for device in devices:
|
||||
if device["file"] != "NO MEDIA" and device["file"] != "-":
|
||||
invalid_list.append(int(device["id"]))
|
||||
# Make it possible to insert images on top of a
|
||||
# removable media device currently without an image attached
|
||||
if "No Media" in device["status"]:
|
||||
occupied_ids.remove(device["id"])
|
||||
|
||||
valid_list = list(range(8))
|
||||
for id in invalid_list:
|
||||
try:
|
||||
valid_list.remove(int(id))
|
||||
except:
|
||||
logging.warning("Invalid SCSI id " + str(id))
|
||||
valid_list.reverse()
|
||||
return valid_list
|
||||
# Combine lists and remove duplicates
|
||||
invalid_ids = list(set(invalid_list + occupied_ids))
|
||||
valid_ids = list(range(8))
|
||||
for id in invalid_ids:
|
||||
valid_ids.remove(int(id))
|
||||
valid_ids.reverse()
|
||||
return valid_ids
|
||||
|
||||
|
||||
def get_type(scsi_id):
|
||||
return list_devices()[int(scsi_id)]["type"]
|
||||
device = proto.PbDeviceDefinition()
|
||||
device.id = int(scsi_id)
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEVICE_INFO
|
||||
command.devices.append(device)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
# Assuming that only one PbDevice object is present in the response
|
||||
try:
|
||||
result_type = proto.PbDeviceType.Name(result.device_info.devices[0].type)
|
||||
return {"status": result.status, "msg": result.msg, "device_type": result_type}
|
||||
except:
|
||||
return {"status": result.status, "msg": result.msg, "device_type": None}
|
||||
|
||||
|
||||
def attach_image(scsi_id, image, image_type):
|
||||
if image_type == "SCCD" and get_type(scsi_id) == "SCCD":
|
||||
return insert(scsi_id, image)
|
||||
def attach_image(scsi_id, **kwargs):
|
||||
|
||||
# Handling the inserting of media into an attached removable type device
|
||||
currently_attached = get_type(scsi_id)["device_type"]
|
||||
device_type = kwargs.get("device_type", None)
|
||||
|
||||
if device_type in REMOVABLE_DEVICE_TYPES and currently_attached in REMOVABLE_DEVICE_TYPES:
|
||||
if currently_attached != device_type:
|
||||
return {"status": False, "msg": f"Cannot insert an image for {device_type} into a {currently_attached} device."}
|
||||
else:
|
||||
return insert(scsi_id, kwargs.get("image", ""))
|
||||
# Handling attaching a new device
|
||||
else:
|
||||
return subprocess.run(
|
||||
["rasctl", "-c", "attach", "-t", image_type, "-i", scsi_id, "-f", image],
|
||||
capture_output=True,
|
||||
)
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
if "device_type" in kwargs.keys():
|
||||
logging.warning(kwargs["device_type"])
|
||||
devices.type = proto.PbDeviceType.Value(str(kwargs["device_type"]))
|
||||
if "unit" in kwargs.keys():
|
||||
devices.unit = kwargs["unit"]
|
||||
if "image" in kwargs.keys():
|
||||
if kwargs["image"] not in [None, ""]:
|
||||
devices.params.append(kwargs["image"])
|
||||
if "params" in kwargs.keys():
|
||||
for p in kwargs["params"]:
|
||||
devices.params.append(p)
|
||||
if "vendor" in kwargs.keys():
|
||||
if kwargs["vendor"] not in [None, ""]:
|
||||
devices.vendor = kwargs["vendor"]
|
||||
if "product" in kwargs.keys():
|
||||
if kwargs["product"] not in [None, ""]:
|
||||
devices.product = kwargs["product"]
|
||||
if "revision" in kwargs.keys():
|
||||
if kwargs["revision"] not in [None, ""]:
|
||||
devices.revision = kwargs["revision"]
|
||||
if "block_size" in kwargs.keys():
|
||||
if kwargs["block_size"] not in [None, ""]:
|
||||
devices.block_size = int(kwargs["block_size"])
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.ATTACH
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def detach_by_id(scsi_id):
|
||||
return subprocess.run(["rasctl", "-c" "detach", "-i", scsi_id], capture_output=True)
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DETACH
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def detach_all():
|
||||
for scsi_id in range(0, 8):
|
||||
subprocess.run(["rasctl", "-c" "detach", "-i", str(scsi_id)])
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DETACH_ALL
|
||||
|
||||
|
||||
def disconnect_by_id(scsi_id):
|
||||
return subprocess.run(
|
||||
["rasctl", "-c", "disconnect", "-i", scsi_id], capture_output=True
|
||||
)
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def eject_by_id(scsi_id):
|
||||
return subprocess.run(["rasctl", "-i", scsi_id, "-c", "eject"])
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.EJECT
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def insert(scsi_id, image):
|
||||
return subprocess.run(
|
||||
["rasctl", "-i", scsi_id, "-c", "insert", "-f", image], capture_output=True
|
||||
)
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
devices.params.append(image)
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.INSERT
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def attach_daynaport(scsi_id):
|
||||
return subprocess.run(
|
||||
["rasctl", "-i", scsi_id, "-c", "attach", "-t", "scdp"],
|
||||
capture_output=True,
|
||||
)
|
||||
devices = proto.PbDeviceDefinition()
|
||||
devices.id = int(scsi_id)
|
||||
devices.type = proto.PbDeviceType.SCDP
|
||||
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.ATTACH
|
||||
command.devices.append(devices)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def is_bridge_setup(interface):
|
||||
process = subprocess.run(["brctl", "show"], capture_output=True)
|
||||
output = process.stdout.decode("utf-8")
|
||||
if "rascsi_bridge" in output and interface in output:
|
||||
return True
|
||||
return False
|
||||
def list_devices(scsi_id=None):
|
||||
from os import path
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.DEVICE_INFO
|
||||
|
||||
# If method is called with scsi_id parameter, return the info on those devices
|
||||
# Otherwise, return the info on all attached devices
|
||||
if scsi_id != None:
|
||||
device = proto.PbDeviceDefinition()
|
||||
device.id = int(scsi_id)
|
||||
command.devices.append(device)
|
||||
|
||||
def daynaport_setup_bridge(interface):
|
||||
return subprocess.run(
|
||||
[f"{base_dir}../RASCSI/src/raspberrypi/setup_bridge.sh", interface],
|
||||
capture_output=True,
|
||||
)
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
|
||||
|
||||
def rascsi_service(action):
|
||||
# start/stop/restart
|
||||
return (
|
||||
subprocess.run(["sudo", "/bin/systemctl", action, "rascsi.service"]).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def list_devices():
|
||||
device_list = []
|
||||
occupied_ids = []
|
||||
n = 0
|
||||
while n < len(result.device_info.devices):
|
||||
did = result.device_info.devices[n].id
|
||||
dun = result.device_info.devices[n].unit
|
||||
dtype = proto.PbDeviceType.Name(result.device_info.devices[n].type)
|
||||
dstat = result.device_info.devices[n].status
|
||||
dprop = result.device_info.devices[n].properties
|
||||
|
||||
# Building the status string
|
||||
# TODO: This formatting should probably be moved elsewhere
|
||||
dstat_msg = []
|
||||
if dprop.read_only == True:
|
||||
dstat_msg.append("Read-Only")
|
||||
if dstat.protected == True and dprop.protectable == True:
|
||||
dstat_msg.append("Write-Protected")
|
||||
if dstat.removed == True and dprop.removable == True:
|
||||
dstat_msg.append("No Media")
|
||||
if dstat.locked == True and dprop.lockable == True:
|
||||
dstat_msg.append("Locked")
|
||||
|
||||
dpath = result.device_info.devices[n].file.name
|
||||
dfile = path.basename(dpath)
|
||||
dparam = result.device_info.devices[n].params
|
||||
dven = result.device_info.devices[n].vendor
|
||||
dprod = result.device_info.devices[n].product
|
||||
drev = result.device_info.devices[n].revision
|
||||
dblock = result.device_info.devices[n].block_size
|
||||
|
||||
device_list.append({"id": did, "un": dun, "device_type": dtype, \
|
||||
"status": ", ".join(dstat_msg), "image": dpath, "file": dfile, "params": dparam,\
|
||||
"vendor": dven, "product": dprod, "revision": drev, "block_size": dblock})
|
||||
occupied_ids.append(did)
|
||||
n += 1
|
||||
return device_list, occupied_ids
|
||||
|
||||
|
||||
def sort_and_format_devices(device_list, occupied_ids):
|
||||
# Add padding devices and sort the list
|
||||
for id in range(8):
|
||||
device_list.append({"id": str(id), "un": "-", "type": "-", "file": "-"})
|
||||
output = subprocess.run(["rasctl", "-l"], capture_output=True).stdout.decode(
|
||||
"utf-8"
|
||||
)
|
||||
for line in output.splitlines():
|
||||
# Valid line to process, continue
|
||||
if (
|
||||
not line.startswith("+")
|
||||
and not line.startswith("| ID |")
|
||||
and (
|
||||
not line.startswith("No device is installed.")
|
||||
or line.startswith("No images currently attached.")
|
||||
)
|
||||
and len(line) > 0
|
||||
):
|
||||
line.rstrip()
|
||||
device = {}
|
||||
segments = line.split("|")
|
||||
if len(segments) > 4:
|
||||
idx = int(segments[1].strip())
|
||||
device_list[idx]["id"] = str(idx)
|
||||
device_list[idx]["un"] = segments[2].strip()
|
||||
device_list[idx]["type"] = segments[3].strip()
|
||||
device_list[idx]["file"] = segments[4].strip()
|
||||
if id not in occupied_ids:
|
||||
device_list.append({"id": id, "type": "-", \
|
||||
"status": "-", "file": "-", "product": "-"})
|
||||
# Sort list of devices by id
|
||||
device_list.sort(key=lambda dic: str(dic["id"]))
|
||||
|
||||
return device_list
|
||||
|
||||
|
||||
def reserve_scsi_ids(reserved_scsi_ids):
|
||||
scsi_ids = ",".join(list(reserved_scsi_ids))
|
||||
return subprocess.run(["rasctl", "-r", scsi_ids])
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.RESERVE
|
||||
command.params.append(reserved_scsi_ids)
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def set_log_level(log_level):
|
||||
'''Sends a command to the server to change the log level. Takes target log level as an argument'''
|
||||
command = proto.PbCommand()
|
||||
command.operation = proto.PbOperation.LOG_LEVEL
|
||||
command.params.append(str(log_level))
|
||||
|
||||
data = send_pb_command(command.SerializeToString())
|
||||
result = proto.PbResult()
|
||||
result.ParseFromString(data)
|
||||
return {"status": result.status, "msg": result.msg}
|
||||
|
||||
|
||||
def send_pb_command(payload):
|
||||
# Host and port number where rascsi is listening for socket connections
|
||||
HOST = 'localhost'
|
||||
PORT = 6868
|
||||
|
||||
counter = 0
|
||||
tries = 100
|
||||
error_msg = ""
|
||||
|
||||
import socket
|
||||
while counter < tries:
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
s.connect((HOST, PORT))
|
||||
return send_over_socket(s, payload)
|
||||
except socket.error as error:
|
||||
counter += 1
|
||||
logging.warning("The RaSCSI service is not responding - attempt " + \
|
||||
str(counter) + "/" + str(tries))
|
||||
error_msg = str(error)
|
||||
|
||||
logging.error(error_msg)
|
||||
|
||||
# After failing all attempts, throw a 404 error
|
||||
from flask import abort
|
||||
abort(404, "Failed to connect to RaSCSI at " + str(HOST) + ":" + str(PORT) + \
|
||||
" with error: " + error_msg + ". Is the RaSCSI service running?")
|
||||
|
||||
|
||||
def send_over_socket(s, payload):
|
||||
from struct import pack, unpack
|
||||
|
||||
# Prepending a little endian 32bit header with the message size
|
||||
s.send(pack("<i", len(payload)))
|
||||
s.send(payload)
|
||||
|
||||
# Receive the first 4 bytes to get the response header
|
||||
response = s.recv(4)
|
||||
if len(response) >= 4:
|
||||
# Extracting the response header to get the length of the response message
|
||||
response_length = unpack("<i", response)[0]
|
||||
# Reading in chunks, to handle a case where the response message is very large
|
||||
chunks = []
|
||||
bytes_recvd = 0
|
||||
while bytes_recvd < response_length:
|
||||
chunk = s.recv(min(response_length - bytes_recvd, 2048))
|
||||
if chunk == b'':
|
||||
from flask import abort
|
||||
logging.error("Read an empty chunk from the socket. Socket connection may have dropped unexpectedly.")
|
||||
abort(503, "Lost connection to RaSCSI. Please go back and try again. If the issue persists, please report a bug.")
|
||||
chunks.append(chunk)
|
||||
bytes_recvd = bytes_recvd + len(chunk)
|
||||
response_message = b''.join(chunks)
|
||||
return response_message
|
||||
else:
|
||||
from flask import abort
|
||||
logging.error("The response from RaSCSI did not contain a protobuf header.")
|
||||
abort(500, "Did not get a valid response from RaSCSI. Please go back and try again. If the issue persists, please report a bug.")
|
||||
|
@ -10,3 +10,4 @@ rsrcfork==1.8.0
|
||||
waitress==1.4.4
|
||||
zope.event==4.5.0
|
||||
zope.interface==5.1.2
|
||||
protobuf>=3.17.3
|
||||
|
@ -1,4 +1,14 @@
|
||||
import os
|
||||
from os import getenv
|
||||
|
||||
base_dir = os.getenv("BASE_DIR", "/home/pi/images/")
|
||||
MAX_FILE_SIZE = os.getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb
|
||||
base_dir = getenv("BASE_DIR", "/home/pi/images/")
|
||||
DEFAULT_CONFIG = base_dir + "default.json"
|
||||
MAX_FILE_SIZE = getenv("MAX_FILE_SIZE", 1024 * 1024 * 1024 * 2) # 2gb
|
||||
|
||||
HARDDRIVE_FILE_SUFFIX = ("hda", "hdn", "hdi", "nhd", "hdf", "hds")
|
||||
CDROM_FILE_SUFFIX = ("iso", "cdr", "toast", "img")
|
||||
REMOVABLE_FILE_SUFFIX = ("hdr",)
|
||||
ARCHIVE_FILE_SUFFIX = ("zip",)
|
||||
VALID_FILE_SUFFIX = HARDDRIVE_FILE_SUFFIX + REMOVABLE_FILE_SUFFIX + \
|
||||
CDROM_FILE_SUFFIX + ARCHIVE_FILE_SUFFIX
|
||||
|
||||
REMOVABLE_DEVICE_TYPES = ("SCCD", "SCRM", "SCMO")
|
||||
|
@ -41,4 +41,9 @@ table, tr, td {
|
||||
color: white;
|
||||
font-size:20px;
|
||||
background-color:green;
|
||||
}
|
||||
}
|
||||
|
||||
td.inactive {
|
||||
text-align:center;
|
||||
background-color:tan;
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block header %}
|
||||
{% if active %}
|
||||
{% if server_info["status"] == True %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: green; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Running</span>
|
||||
{% else %}
|
||||
<span style="display: inline-block; width: 100%; color: white; background-color: red; text-align: center; vertical-align: center; font-family: Arial, Helvetica, sans-serif;">Service Stopped</span>
|
||||
@ -21,12 +21,11 @@
|
||||
|
||||
{% block content %}
|
||||
<h2>Current RaSCSI Configuration</h2>
|
||||
<p>The <em>default</em> configuration will be loaded when the Web UI starts up.</p>
|
||||
<p>
|
||||
<form action="/config/load" method="post">
|
||||
<select name="name" >
|
||||
{% for config in config_files %}
|
||||
<option value="{{config}}">{{config.replace(".csv", '')}}</option>
|
||||
<option value="{{config}}">{{config.replace(".json", '')}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" name="load" value="Load" />
|
||||
@ -42,39 +41,60 @@
|
||||
<input type="submit" value="Detach All" />
|
||||
</form>
|
||||
</p>
|
||||
<p><small>The <em>default</em> configuration will be loaded when the Web UI starts up, if available.</small></p>
|
||||
|
||||
<table cellpadding="3" border="black">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><b>ID</b></td>
|
||||
<td><b>Type</b></td>
|
||||
<td><b>Status</b></td>
|
||||
<td><b>File</b></td>
|
||||
<td><b>Action</b></td>
|
||||
<td><b>Product</b></td>
|
||||
<td><b>Actions</b></td>
|
||||
</tr>
|
||||
{% for device in devices %}
|
||||
<tr>
|
||||
{% if device.id not in reserved_scsi_ids %}
|
||||
{% if device["id"]|string() not in reserved_scsi_ids %}
|
||||
<td style="text-align:center">{{device.id}}</td>
|
||||
<td style="text-align:center">{{device.type}}</td>
|
||||
<td>{{device.file}}</td>
|
||||
<td>
|
||||
{% if device.type == "SCCD" and device.file != "NO MEDIA" %}
|
||||
<td style="text-align:center">{{device.device_type}}</td>
|
||||
<td style="text-align:center">{{device.status}}</td>
|
||||
<td style="text-align:left">{{device.file}}</td>
|
||||
{% if device.vendor == "RaSCSI" %}
|
||||
<td style="text-align:center">{{device.product}}</td>
|
||||
{% else %}
|
||||
<td style="text-align:center">{{device.vendor}} {{device.product}}</td>
|
||||
{% endif %}
|
||||
<td style="text-align:left">
|
||||
{% if device.device_type in removable_device_types and "No Media" not in device.status %}
|
||||
<form action="/scsi/eject" method="post" onsubmit="return confirm('Eject Disk?')">
|
||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||
<input type="submit" value="Eject" />
|
||||
</form>
|
||||
<form action="/scsi/info" method="post">
|
||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||
<input type="submit" value="Info" />
|
||||
</form>
|
||||
{% elif device.device_type in ["-"] %}
|
||||
<div>-</div>
|
||||
{% else %}
|
||||
<form action="/scsi/detach" method="post" onsubmit="return confirm('Detach Disk?')">
|
||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||
<input type="submit" value="Detach" />
|
||||
</form>
|
||||
<form action="/scsi/info" method="post">
|
||||
<input type="hidden" name="scsi_id" value="{{device.id}}">
|
||||
<input type="submit" value="Info" />
|
||||
</form>
|
||||
{% endif %}
|
||||
</td>
|
||||
{% else %}
|
||||
<td style="text-align:center">{{device.id}}</td>
|
||||
<td style="text-align:center">-</td>
|
||||
<td>Reserved ID</td>
|
||||
<td>-</td>
|
||||
<td class="inactive">{{device.id}}</td>
|
||||
<td class="inactive"></td>
|
||||
<td class="inactive">Reserved ID</td>
|
||||
<td class="inactive"></td>
|
||||
<td class="inactive"></td>
|
||||
<td class="inactive"></td>
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@ -96,24 +116,27 @@
|
||||
<td style="text-align:center">
|
||||
<form action="/files/download" method="post">
|
||||
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
|
||||
<input type="submit" value="{{file[1]}} ↓" />
|
||||
<input type="submit" value="{{file[1]}} MB ↓" />
|
||||
</form>
|
||||
</td>
|
||||
<td>
|
||||
<form action="/scsi/attach" method="post">
|
||||
<input type="hidden" name="file_name" value="{{file[0]}}">
|
||||
<input type="hidden" name="file_size" value="{{file[2]}}">
|
||||
<select name="scsi_id">
|
||||
{% for id in scsi_ids %}
|
||||
<option value="{{id}}">{{id}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
{% if not file[0].lower().endswith(archive_file_suffix) %}
|
||||
<input type="submit" value="Attach" />
|
||||
{% endif %}
|
||||
</form>
|
||||
<form action="/files/delete" method="post" onsubmit="return confirm('Delete file?')">
|
||||
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
|
||||
<input type="submit" value="Delete" />
|
||||
</form>
|
||||
{% if file[0].endswith('.zip') or file[0].endswith('.ZIP') %}
|
||||
{% if file[0].lower().endswith(archive_file_suffix) %}
|
||||
<form action="/files/unzip" method="post">
|
||||
<input type="hidden" name="image" value="{{file[0].replace(base_dir, '')}}">
|
||||
<input type="submit" value="Unzip" />
|
||||
@ -124,10 +147,11 @@
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<p><small>Supported file types: {{valid_file_suffix|string()}}</small></p>
|
||||
|
||||
<hr/>
|
||||
<h2>Attach Ethernet Adapter</h2>
|
||||
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. Host drivers required.</p>
|
||||
<p>Emulates a SCSI DaynaPORT Ethernet Adapter. <a href="https://github.com/akuker/RASCSI/wiki/Dayna-Port-SCSI-Link">Host drivers required.</a></p>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
@ -146,9 +170,7 @@
|
||||
{% if bridge_configured %}
|
||||
<small>Bridge is currently configured!</small>
|
||||
{% else %}
|
||||
<form action="/daynaport/setup" method="post">
|
||||
<input type="submit" value="Create Bridge" />
|
||||
</form>
|
||||
<small>Bridge is automatically configured when a network adapter is attached.</small>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
@ -223,13 +245,14 @@
|
||||
<input type="text" placeholder="File name" name="file_name"/>
|
||||
<label for="type">Type:</label>
|
||||
<select name="type">
|
||||
<option value="hda">SCSI Hard Disk image (APPLE GENUINE)</option>
|
||||
<option value="hdn">SCSI Hard Disk image (NEC GENUINE)</option>
|
||||
<option value="hdi">SCSI Hard Disk image (Anex86 HD image)</option>
|
||||
<option value="nhd">SCSI Hard Disk image (T98Next HD image)</option>
|
||||
<option value="hds">SCSI Hard Disk image (Generic - recommended for Atari computers)</option>
|
||||
<option value="hdr">SCSI Removable Media Disk image (Generic)</option>
|
||||
<option value="hdf">SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000)</option>
|
||||
<option value="hda">SCSI Hard Disk image (APPLE GENUINE) [.hda]</option>
|
||||
<option value="hdn">SCSI Hard Disk image (NEC GENUINE) [.hdn]</option>
|
||||
<!-- Disabling due to https://github.com/akuker/RASCSI/issues/232
|
||||
<option value="hdi">SCSI Hard Disk image (Anex86 HD image) [.hdi]</option>
|
||||
<option value="nhd">SCSI Hard Disk image (T98Next HD image) [.nhd]</option> -->
|
||||
<option value="hds">SCSI Hard Disk image (Generic - recommended for Atari computers) [.hds]</option>
|
||||
<option value="hdr">SCSI Removable Media Disk image (Generic) [.hdr]</option>
|
||||
<option value="hdf">SASI Hard Disk image (XM6 SASI HD image - typically only used with X68000) [.hdf]</option>
|
||||
</select>
|
||||
<label for="size">Size(MB):</label>
|
||||
<input type="number" placeholder="Size(MB)" name="size"/>
|
||||
@ -240,6 +263,47 @@
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Logging</h2>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/logs/show" method="post">
|
||||
<label for="lines">Log Lines:</label>
|
||||
<input type="text" placeholder="200" name="lines"/>
|
||||
<label for="scope">Scope:</label>
|
||||
<select name="scope">
|
||||
<option value="default">default</option>
|
||||
<option value="rascsi">rascsi.service</option>
|
||||
<option value="rascsi-web">rascsi-web.service</option>
|
||||
</select>
|
||||
<input type="submit" value="Show Logs" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Server Log Level</h2>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
<td style="border: none; vertical-align:top;">
|
||||
<form action="/logs/level" method="post">
|
||||
<label for="level">Log Level:</label>
|
||||
<select name="level">
|
||||
{% for level in server_info["log_levels"] %}
|
||||
<option value="{{level}}">{{level}}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="submit" value="Set Log Level" />
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<hr/>
|
||||
|
||||
<h2>Raspberry Pi Operations</h2>
|
||||
<table style="border: none">
|
||||
<tr style="border: none">
|
||||
@ -261,9 +325,11 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% block footer %}
|
||||
<center><tt>{{version}}</tt></center>
|
||||
<center><a href="/logs">Logs</a></center>
|
||||
<center><tt>RaSCSI version: <strong>{{server_info["version"]}}</strong></tt></center>
|
||||
<center><tt>Server log level: <strong>{{server_info["current_log_level"]}}</strong></tt></center>
|
||||
<center><tt>{{version}}</tt></center>
|
||||
{% endblock %}
|
||||
|
273
src/web/web.py
273
src/web/web.py
@ -1,35 +1,37 @@
|
||||
import io
|
||||
import re
|
||||
import sys
|
||||
|
||||
from flask import Flask, render_template, request, flash, url_for, redirect, send_file, send_from_directory
|
||||
|
||||
from file_cmds import (
|
||||
list_files,
|
||||
list_config_files,
|
||||
create_new_image,
|
||||
download_file_to_iso,
|
||||
delete_file,
|
||||
unzip_file,
|
||||
download_image,
|
||||
write_config_csv,
|
||||
read_config_csv,
|
||||
write_config,
|
||||
read_config,
|
||||
read_device_config,
|
||||
)
|
||||
from pi_cmds import (
|
||||
shutdown_pi,
|
||||
reboot_pi,
|
||||
running_version,
|
||||
rascsi_service,
|
||||
is_bridge_setup,
|
||||
)
|
||||
from pi_cmds import shutdown_pi, reboot_pi, running_version, rascsi_service
|
||||
from ractl_cmds import (
|
||||
attach_image,
|
||||
list_devices,
|
||||
is_active,
|
||||
list_files,
|
||||
sort_and_format_devices,
|
||||
detach_by_id,
|
||||
eject_by_id,
|
||||
get_valid_scsi_ids,
|
||||
attach_daynaport,
|
||||
is_bridge_setup,
|
||||
daynaport_setup_bridge,
|
||||
list_config_files,
|
||||
detach_all,
|
||||
valid_file_suffix,
|
||||
valid_file_types,
|
||||
reserve_scsi_ids,
|
||||
get_server_info,
|
||||
validate_scsi_id,
|
||||
set_log_level,
|
||||
)
|
||||
from settings import *
|
||||
|
||||
@ -38,21 +40,28 @@ app = Flask(__name__)
|
||||
|
||||
@app.route("/")
|
||||
def index():
|
||||
devices = list_devices()
|
||||
reserved_scsi_ids = app.config.get("RESERVED_SCSI_IDS")
|
||||
scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids))
|
||||
unsorted_devices, occupied_ids = list_devices()
|
||||
devices = sort_and_format_devices(unsorted_devices, occupied_ids)
|
||||
scsi_ids = get_valid_scsi_ids(devices, list(reserved_scsi_ids), occupied_ids)
|
||||
return render_template(
|
||||
"index.html",
|
||||
bridge_configured=is_bridge_setup("eth0"),
|
||||
bridge_configured=is_bridge_setup(),
|
||||
devices=devices,
|
||||
active=is_active(),
|
||||
files=list_files(),
|
||||
config_files=list_config_files(),
|
||||
base_dir=base_dir,
|
||||
scsi_ids=scsi_ids,
|
||||
reserved_scsi_ids=reserved_scsi_ids,
|
||||
reserved_scsi_ids=[reserved_scsi_ids],
|
||||
max_file_size=MAX_FILE_SIZE,
|
||||
version=running_version(),
|
||||
server_info=get_server_info(),
|
||||
valid_file_suffix=VALID_FILE_SUFFIX,
|
||||
removable_device_types=REMOVABLE_DEVICE_TYPES,
|
||||
harddrive_file_suffix=HARDDRIVE_FILE_SUFFIX,
|
||||
cdrom_file_suffix=CDROM_FILE_SUFFIX,
|
||||
removable_file_suffix=REMOVABLE_FILE_SUFFIX,
|
||||
archive_file_suffix=ARCHIVE_FILE_SUFFIX,
|
||||
)
|
||||
|
||||
@app.route('/pwa/<path:path>')
|
||||
@ -62,10 +71,15 @@ def send_pwa_files(path):
|
||||
@app.route("/config/save", methods=["POST"])
|
||||
def config_save():
|
||||
file_name = request.form.get("name") or "default"
|
||||
file_name = f"{base_dir}{file_name}.csv"
|
||||
file_name = f"{base_dir}{file_name}.json"
|
||||
|
||||
write_config_csv(file_name)
|
||||
flash(f"Saved config to {file_name}!")
|
||||
process = write_config(file_name)
|
||||
if process["status"] == True:
|
||||
flash(f"Saved config to {file_name}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to saved config to {file_name}!", "error")
|
||||
flash(f"{process['msg']}", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@ -75,10 +89,12 @@ def config_load():
|
||||
file_name = f"{base_dir}{file_name}"
|
||||
|
||||
if "load" in request.form:
|
||||
if read_config_csv(file_name):
|
||||
process = read_config(file_name)
|
||||
if process["status"] == True:
|
||||
flash(f"Loaded config from {file_name}!")
|
||||
else:
|
||||
flash(f"Failed to load {file_name}!", "error")
|
||||
flash(f"{process['msg']}", "error")
|
||||
elif "delete" in request.form:
|
||||
if delete_file(file_name):
|
||||
flash(f"Deleted config {file_name}!")
|
||||
@ -88,16 +104,20 @@ def config_load():
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/logs")
|
||||
def logs():
|
||||
import subprocess
|
||||
@app.route("/logs/show", methods=["POST"])
|
||||
def show_logs():
|
||||
lines = request.form.get("lines") or "200"
|
||||
scope = request.form.get("scope") or "default"
|
||||
|
||||
lines = request.args.get("lines") or "100"
|
||||
process = subprocess.run(["journalctl", "-n", lines], capture_output=True)
|
||||
from subprocess import run
|
||||
if scope != "default":
|
||||
process = run(["journalctl", "-n", lines, "-u", scope], capture_output=True)
|
||||
else:
|
||||
process = run(["journalctl", "-n", lines], capture_output=True)
|
||||
|
||||
if process.returncode == 0:
|
||||
headers = {"content-type": "text/plain"}
|
||||
return process.stdout.decode("utf-8"), 200, headers
|
||||
return process.stdout.decode("utf-8"), int(lines), headers
|
||||
else:
|
||||
flash("Failed to get logs")
|
||||
flash(process.stdout.decode("utf-8"), "stdout")
|
||||
@ -105,86 +125,112 @@ def logs():
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/logs/level", methods=["POST"])
|
||||
def log_level():
|
||||
level = request.form.get("level") or "info"
|
||||
|
||||
process = set_log_level(level)
|
||||
if process["status"] == True:
|
||||
flash(f"Log level set to {level}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to set log level to {level}!", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/daynaport/attach", methods=["POST"])
|
||||
def daynaport_attach():
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
|
||||
validate = validate_scsi_id(scsi_id)
|
||||
if validate["status"] == False:
|
||||
flash(validate["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
process = attach_daynaport(scsi_id)
|
||||
if process.returncode == 0:
|
||||
if process["status"] == True:
|
||||
flash(f"Attached DaynaPORT to SCSI id {scsi_id}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to attach DaynaPORT to SCSI id {scsi_id}!", "error")
|
||||
flash(process.stdout.decode("utf-8"), "stdout")
|
||||
flash(process.stderr.decode("utf-8"), "stderr")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/daynaport/setup", methods=["POST"])
|
||||
def daynaport_setup():
|
||||
# Future use for wifi
|
||||
interface = request.form.get("interface") or "eth0"
|
||||
process = daynaport_setup_bridge(interface)
|
||||
if process.returncode == 0:
|
||||
flash(f"Configured DaynaPORT bridge on {interface}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to configure DaynaPORT bridge on {interface}!", "error")
|
||||
flash(process.stdout.decode("utf-8"), "stdout")
|
||||
flash(process.stderr.decode("utf-8"), "stderr")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/scsi/attach", methods=["POST"])
|
||||
def attach():
|
||||
file_name = request.form.get("file_name")
|
||||
file_size = request.form.get("file_size")
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
|
||||
# Validate image type by suffix
|
||||
print("file_name", file_name)
|
||||
print("valid_file_types: ", valid_file_types)
|
||||
if re.match(valid_file_types, file_name):
|
||||
if file_name.lower().endswith((".iso", ".cdr", ".toast", ".img")):
|
||||
image_type = "SCCD"
|
||||
else:
|
||||
image_type = "SCHD"
|
||||
else:
|
||||
flash(f"Unknown file type. Valid files are: {', '.join(valid_file_suffix)}", "error")
|
||||
validate = validate_scsi_id(scsi_id)
|
||||
if validate["status"] == False:
|
||||
flash(validate["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
# Validate the SCSI ID
|
||||
if re.match("[0-7]", str(scsi_id)) == None:
|
||||
flash(f"Invalid SCSI ID. Should be a number between 0-7", "error")
|
||||
return redirect(url_for("index"))
|
||||
kwargs = {"image": file_name}
|
||||
|
||||
process = attach_image(scsi_id, file_name, image_type)
|
||||
if process.returncode == 0:
|
||||
# Attempt to load the device config sidecar file:
|
||||
# same base path but .rascsi instead of the original suffix.
|
||||
from pathlib import Path
|
||||
device_config = Path(base_dir + str(Path(file_name).stem) + ".rascsi")
|
||||
if device_config.is_file():
|
||||
process = read_device_config(device_config)
|
||||
if process["status"] == False:
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
conf = process["conf"]
|
||||
conf_file_size = conf["blocks"] * conf["block_size"]
|
||||
if conf_file_size != 0 and conf_file_size > int(file_size):
|
||||
flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
|
||||
flash(f"The file size {file_size} bytes needs to be at least {conf_file_size} bytes.", "error")
|
||||
return redirect(url_for("index"))
|
||||
kwargs["device_type"] = conf["device_type"]
|
||||
kwargs["vendor"] = conf["vendor"]
|
||||
kwargs["product"] = conf["product"]
|
||||
kwargs["revision"] = conf["revision"]
|
||||
kwargs["block_size"] = conf["block_size"]
|
||||
# Validate image type by file name suffix as fallback
|
||||
elif file_name.lower().endswith(CDROM_FILE_SUFFIX):
|
||||
kwargs["device_type"] = "SCCD"
|
||||
elif file_name.lower().endswith(REMOVABLE_FILE_SUFFIX):
|
||||
kwargs["device_type"] = "SCRM"
|
||||
elif file_name.lower().endswith(HARDDRIVE_FILE_SUFFIX):
|
||||
kwargs["device_type"] = "SCHD"
|
||||
|
||||
process = attach_image(scsi_id, **kwargs)
|
||||
if process["status"] == True:
|
||||
flash(f"Attached {file_name} to SCSI id {scsi_id}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to attach {file_name} to SCSI id {scsi_id}!", "error")
|
||||
flash(process.stdout.decode("utf-8"), "stdout")
|
||||
flash(process.stderr.decode("utf-8"), "stderr")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/scsi/detach_all", methods=["POST"])
|
||||
def detach_all_devices():
|
||||
detach_all()
|
||||
flash("Detached all SCSI devices!")
|
||||
return redirect(url_for("index"))
|
||||
process = detach_all()
|
||||
if process["status"] == True:
|
||||
flash("Detached all SCSI devices!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash("Failed to detach all SCSI devices!", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/scsi/detach", methods=["POST"])
|
||||
def detach():
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
process = detach_by_id(scsi_id)
|
||||
if process.returncode == 0:
|
||||
flash("Detached SCSI id " + scsi_id + "!")
|
||||
if process["status"] == True:
|
||||
flash(f"Detached SCSI id {scsi_id}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash("Failed to detach SCSI id " + scsi_id + "!", "error")
|
||||
flash(process.stdout, "stdout")
|
||||
flash(process.stderr, "stderr")
|
||||
flash(f"Failed to detach SCSI id {scsi_id}!", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@ -192,15 +238,35 @@ def detach():
|
||||
def eject():
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
process = eject_by_id(scsi_id)
|
||||
if process.returncode == 0:
|
||||
flash("Ejected scsi id " + scsi_id + "!")
|
||||
if process["status"] == True:
|
||||
flash(f"Ejected scsi id {scsi_id}!")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash("Failed to eject SCSI id " + scsi_id + "!", "error")
|
||||
flash(process.stdout, "stdout")
|
||||
flash(process.stderr, "stderr")
|
||||
flash(f"Failed to eject SCSI id {scsi_id}!", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@app.route("/scsi/info", methods=["POST"])
|
||||
def device_info():
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
# Extracting the 0th dictionary in list index 0
|
||||
device = list_devices(scsi_id)[0][0]
|
||||
if str(device["id"]) == scsi_id:
|
||||
flash("=== DEVICE INFO ===")
|
||||
flash(f"SCSI ID: {device['id']}")
|
||||
flash(f"Unit: {device['un']}")
|
||||
flash(f"Type: {device['device_type']}")
|
||||
flash(f"Status: {device['status']}")
|
||||
flash(f"File: {device['image']}")
|
||||
flash(f"Parameters: {device['params']}")
|
||||
flash(f"Vendor: {device['vendor']}")
|
||||
flash(f"Product: {device['product']}")
|
||||
flash(f"Revision: {device['revision']}")
|
||||
flash(f"Block Size: {device['block_size']}")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to get device info for SCSI id {scsi_id}!", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
@app.route("/pi/reboot", methods=["POST"])
|
||||
def restart():
|
||||
@ -231,23 +297,27 @@ def download_file():
|
||||
scsi_id = request.form.get("scsi_id")
|
||||
url = request.form.get("url")
|
||||
process = download_file_to_iso(scsi_id, url)
|
||||
if process.returncode == 0:
|
||||
flash("File Downloaded")
|
||||
if process["status"] == True:
|
||||
flash(f"File Downloaded and Attached to SCSI id {scsi_id}")
|
||||
flash(process["msg"])
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash("Failed to download file", "error")
|
||||
flash(process.stdout, "stdout")
|
||||
flash(process.stderr, "stderr")
|
||||
flash(f"Failed to download and attach file {url}", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/files/download_image", methods=["POST"])
|
||||
def download_img():
|
||||
url = request.form.get("url")
|
||||
# TODO: error handling
|
||||
download_image(url)
|
||||
flash("File Downloaded")
|
||||
return redirect(url_for("index"))
|
||||
process = download_image(url)
|
||||
if process["status"] == True:
|
||||
flash(f"File Downloaded from {url}")
|
||||
return redirect(url_for("index"))
|
||||
else:
|
||||
flash(f"Failed to download file {url}", "error")
|
||||
flash(process["msg"], "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
|
||||
@app.route("/files/upload/<filename>", methods=["POST"])
|
||||
@ -256,19 +326,22 @@ def upload_file(filename):
|
||||
flash("No file provided.", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
file_path = os.path.join(app.config["UPLOAD_FOLDER"], filename)
|
||||
if os.path.isfile(file_path):
|
||||
from os import path
|
||||
file_path = path.join(app.config["UPLOAD_FOLDER"], filename)
|
||||
if path.isfile(file_path):
|
||||
flash(f"{filename} already exists.", "error")
|
||||
return redirect(url_for("index"))
|
||||
|
||||
from io import DEFAULT_BUFFER_SIZE
|
||||
binary_new_file = "bx"
|
||||
with open(file_path, binary_new_file, buffering=io.DEFAULT_BUFFER_SIZE) as f:
|
||||
chunk_size = io.DEFAULT_BUFFER_SIZE
|
||||
with open(file_path, binary_new_file, buffering=DEFAULT_BUFFER_SIZE) as f:
|
||||
chunk_size = DEFAULT_BUFFER_SIZE
|
||||
while True:
|
||||
chunk = request.stream.read(chunk_size)
|
||||
if len(chunk) == 0:
|
||||
break
|
||||
f.write(chunk)
|
||||
# TODO: display an informative success message
|
||||
return redirect(url_for("index", filename=filename))
|
||||
|
||||
|
||||
@ -322,17 +395,25 @@ if __name__ == "__main__":
|
||||
app.secret_key = "rascsi_is_awesome_insecure_secret_key"
|
||||
app.config["SESSION_TYPE"] = "filesystem"
|
||||
app.config["UPLOAD_FOLDER"] = base_dir
|
||||
os.makedirs(app.config["UPLOAD_FOLDER"], exist_ok=True)
|
||||
|
||||
from os import makedirs
|
||||
makedirs(app.config["UPLOAD_FOLDER"], exist_ok=True)
|
||||
app.config["MAX_CONTENT_LENGTH"] = MAX_FILE_SIZE
|
||||
if len(sys.argv) >= 2:
|
||||
app.config["RESERVED_SCSI_IDS"] = str(sys.argv[1])
|
||||
|
||||
from sys import argv
|
||||
if len(argv) >= 2:
|
||||
# Reserved ids format is a string of digits such as '017'
|
||||
app.config["RESERVED_SCSI_IDS"] = str(argv[1])
|
||||
# Reserve SCSI IDs on the backend side to prevent use
|
||||
reserve_scsi_ids(app.config.get("RESERVED_SCSI_IDS"))
|
||||
else:
|
||||
app.config["RESERVED_SCSI_IDS"] = ""
|
||||
|
||||
# Load the configuration in default.cvs, if it exists
|
||||
read_config_csv(f"{base_dir}default.csv")
|
||||
# Load the default configuration file, if found
|
||||
from pathlib import Path
|
||||
default_config = Path(DEFAULT_CONFIG)
|
||||
if default_config.is_file():
|
||||
read_config(default_config)
|
||||
|
||||
import bjoern
|
||||
print("Serving rascsi-web...")
|
||||
|
Loading…
Reference in New Issue
Block a user